Skip to content

Commit f4ffb15

Browse files
authored
fix: enable nested option (#3)
* enable nested option * update specs
1 parent ce10b45 commit f4ffb15

2 files changed

Lines changed: 74 additions & 21 deletions

File tree

shapes/option.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ import { createShape, metadata, Shape, ShapeDecodeError } from "../common/mod.ts
33
export function option<SI, SO>($some: Shape<SI, SO>): Shape<SI | undefined, SO | undefined>
44
export function option<SI, SO, N>($some: Shape<SI, SO>, none: N): Shape<SI | N, SO | N>
55
export function option<SI, SO, N>($some: Shape<SI, SO>, none?: N): Shape<SI | N, SO | N> {
6-
if ($some.metadata.some((x) => x.factory === option && x.args[1] === none)) {
7-
throw new Error("Nested option shape will not roundtrip correctly")
8-
}
96
return createShape({
107
metadata: metadata("$.option", option<SI, SO, N>, $some, ...(none === undefined ? [] : [none!]) as [N]),
118
staticSize: 1 + $some.staticSize,
@@ -18,13 +15,8 @@ export function option<SI, SO, N>($some: Shape<SI, SO>, none?: N): Shape<SI | N,
1815
switch (buffer.array[buffer.index++]) {
1916
case 0:
2017
return none as N
21-
case 1: {
22-
const value = $some.subDecode(buffer)
23-
if (value === none) {
24-
throw new ShapeDecodeError(this, buffer, "Some(None) will not roundtrip correctly")
25-
}
26-
return value
27-
}
18+
case 1:
19+
return $some.subDecode(buffer)
2820
default:
2921
throw new ShapeDecodeError(this, buffer, "Option discriminant neither 0 nor 1")
3022
}

shapes/test/option.test.ts

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
12
import * as $ from "../../mod.ts"
2-
import { assertThrows, testInvalid, testShape } from "../../test-util.ts"
3+
import { assertEquals, testInvalid, testShape } from "../../test-util.ts"
34

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

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

12-
Deno.test("option roundtrip error", () => {
13-
assertThrows(() => $.option($.option($.u8)))
14-
assertThrows(() => $.option($.withMetadata($.metadata("$foo"), $.option($.u8))))
15-
assertThrows(() => {
16-
const $foo = $.option($.u8)
17-
$foo.metadata = []
18-
$.option($foo)
19-
// Some(None)
20-
.decode(new Uint8Array([1, 0]))
21-
})
13+
Deno.test("nested option support", () => {
14+
// Create nested option shapes
15+
const $nestedOption = $.option($.option($.u8));
16+
const $nestedOptionWithCustomNone = $.option($.option($.str, null), undefined);
17+
18+
// Test Some(Some(value))
19+
const someValue = 42;
20+
const encodedSomeSome = $nestedOption.encode(someValue);
21+
assertEquals(encodedSomeSome[0], 1); // Outer discriminant is 1 (Some)
22+
assertEquals(encodedSomeSome[1], 1); // Inner discriminant is 1 (Some)
23+
assertEquals(encodedSomeSome[2], someValue); // Value is preserved
24+
assertEquals($nestedOption.decode(encodedSomeSome), someValue); // Roundtrips correctly
25+
26+
// Test Some(None)
27+
const encodedSomeNone = new Uint8Array([1, 0]); // Manually create Some(None)
28+
assertEquals($nestedOption.decode(encodedSomeNone), undefined); // Decodes to undefined
29+
30+
// Test None
31+
const encodedNone = new Uint8Array([0]); // Just outer None
32+
assertEquals($nestedOption.decode(encodedNone), undefined); // Decodes to undefined
2233
})
34+
35+
// Add more comprehensive tests for nested options
36+
Deno.test("nested option with values", () => {
37+
// Test with u8
38+
const $nestedU8 = $.option($.option($.u8));
39+
const u8Value = 42;
40+
const encodedU8 = $nestedU8.encode(u8Value);
41+
assertEquals($nestedU8.decode(encodedU8), u8Value);
42+
43+
// Test with string
44+
const $nestedStr = $.option($.option($.str));
45+
const strValue = "hello";
46+
const encodedStr = $nestedStr.encode(strValue);
47+
assertEquals($nestedStr.decode(encodedStr), strValue);
48+
49+
// Test with boolean
50+
const $nestedBool = $.option($.option($.bool));
51+
const boolValue = true;
52+
const encodedBool = $nestedBool.encode(boolValue);
53+
assertEquals($nestedBool.decode(encodedBool), boolValue);
54+
});
55+
56+
Deno.test("nested option with custom none values", () => {
57+
// Test with custom none values
58+
const $nestedCustom = $.option($.option($.str, null), undefined);
59+
const strValue = "test";
60+
const encodedStr = $nestedCustom.encode(strValue);
61+
assertEquals($nestedCustom.decode(encodedStr), strValue);
62+
63+
const nullValue = null;
64+
const encodedNull = $nestedCustom.encode(nullValue);
65+
assertEquals($nestedCustom.decode(encodedNull), nullValue);
66+
67+
const undefinedValue = undefined;
68+
const encodedUndefined = $nestedCustom.encode(undefinedValue);
69+
assertEquals(encodedUndefined, new Uint8Array([0]));
70+
assertEquals($nestedCustom.decode(encodedUndefined), undefinedValue);
71+
});
72+
73+
Deno.test("deep nested option (3 levels)", () => {
74+
// Test deeper nesting (3 levels)
75+
const $deepNested = $.option($.option($.option($.u8)));
76+
const value = 123;
77+
const encoded = $deepNested.encode(value);
78+
assertEquals($deepNested.decode(encoded), value);
79+
80+
const undefinedValue = undefined;
81+
const encodedUndefined = $deepNested.encode(undefinedValue);
82+
assertEquals($deepNested.decode(encodedUndefined), undefinedValue);
83+
});

0 commit comments

Comments
 (0)