Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions shapes/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ import { createShape, metadata, Shape, ShapeDecodeError } from "../common/mod.ts
export function option<SI, SO>($some: Shape<SI, SO>): Shape<SI | undefined, SO | undefined>
export function option<SI, SO, N>($some: Shape<SI, SO>, none: N): Shape<SI | N, SO | N>
export function option<SI, SO, N>($some: Shape<SI, SO>, none?: N): Shape<SI | N, SO | N> {
if ($some.metadata.some((x) => x.factory === option && x.args[1] === none)) {
throw new Error("Nested option shape will not roundtrip correctly")
}
return createShape({
metadata: metadata("$.option", option<SI, SO, N>, $some, ...(none === undefined ? [] : [none!]) as [N]),
staticSize: 1 + $some.staticSize,
Expand All @@ -18,13 +15,8 @@ export function option<SI, SO, N>($some: Shape<SI, SO>, none?: N): Shape<SI | N,
switch (buffer.array[buffer.index++]) {
case 0:
return none as N
case 1: {
const value = $some.subDecode(buffer)
if (value === none) {
throw new ShapeDecodeError(this, buffer, "Some(None) will not roundtrip correctly")
}
return value
}
case 1:
return $some.subDecode(buffer)
default:
throw new ShapeDecodeError(this, buffer, "Option discriminant neither 0 nor 1")
}
Expand Down
83 changes: 72 additions & 11 deletions shapes/test/option.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

import * as $ from "../../mod.ts"
import { assertThrows, testInvalid, testShape } from "../../test-util.ts"
import { assertEquals, testInvalid, testShape } from "../../test-util.ts"

testShape($.option($.str), ["HELLO!"])
testShape($.option($.u8), [1])
Expand All @@ -9,14 +10,74 @@ testShape($.option($.str, null), ["hi", "low", null])

testInvalid($.option($.bool), [123])

Deno.test("option roundtrip error", () => {
assertThrows(() => $.option($.option($.u8)))
assertThrows(() => $.option($.withMetadata($.metadata("$foo"), $.option($.u8))))
assertThrows(() => {
const $foo = $.option($.u8)
$foo.metadata = []
$.option($foo)
// Some(None)
.decode(new Uint8Array([1, 0]))
})
Deno.test("nested option support", () => {
// Create nested option shapes
const $nestedOption = $.option($.option($.u8));
const $nestedOptionWithCustomNone = $.option($.option($.str, null), undefined);

// Test Some(Some(value))
const someValue = 42;
const encodedSomeSome = $nestedOption.encode(someValue);
assertEquals(encodedSomeSome[0], 1); // Outer discriminant is 1 (Some)
assertEquals(encodedSomeSome[1], 1); // Inner discriminant is 1 (Some)
assertEquals(encodedSomeSome[2], someValue); // Value is preserved
assertEquals($nestedOption.decode(encodedSomeSome), someValue); // Roundtrips correctly

// Test Some(None)
const encodedSomeNone = new Uint8Array([1, 0]); // Manually create Some(None)
assertEquals($nestedOption.decode(encodedSomeNone), undefined); // Decodes to undefined

// Test None
const encodedNone = new Uint8Array([0]); // Just outer None
assertEquals($nestedOption.decode(encodedNone), undefined); // Decodes to undefined
})

// Add more comprehensive tests for nested options
Deno.test("nested option with values", () => {
// Test with u8
const $nestedU8 = $.option($.option($.u8));
const u8Value = 42;
const encodedU8 = $nestedU8.encode(u8Value);
assertEquals($nestedU8.decode(encodedU8), u8Value);

// Test with string
const $nestedStr = $.option($.option($.str));
const strValue = "hello";
const encodedStr = $nestedStr.encode(strValue);
assertEquals($nestedStr.decode(encodedStr), strValue);

// Test with boolean
const $nestedBool = $.option($.option($.bool));
const boolValue = true;
const encodedBool = $nestedBool.encode(boolValue);
assertEquals($nestedBool.decode(encodedBool), boolValue);
});

Deno.test("nested option with custom none values", () => {
// Test with custom none values
const $nestedCustom = $.option($.option($.str, null), undefined);
const strValue = "test";
const encodedStr = $nestedCustom.encode(strValue);
assertEquals($nestedCustom.decode(encodedStr), strValue);

const nullValue = null;
const encodedNull = $nestedCustom.encode(nullValue);
assertEquals($nestedCustom.decode(encodedNull), nullValue);

const undefinedValue = undefined;
const encodedUndefined = $nestedCustom.encode(undefinedValue);
assertEquals(encodedUndefined, new Uint8Array([0]));
assertEquals($nestedCustom.decode(encodedUndefined), undefinedValue);
});

Deno.test("deep nested option (3 levels)", () => {
// Test deeper nesting (3 levels)
const $deepNested = $.option($.option($.option($.u8)));
const value = 123;
const encoded = $deepNested.encode(value);
assertEquals($deepNested.decode(encoded), value);

const undefinedValue = undefined;
const encodedUndefined = $deepNested.encode(undefinedValue);
assertEquals($deepNested.decode(encodedUndefined), undefinedValue);
});
Loading