Skip to content

Commit 9d3d9c0

Browse files
added multicheck, new test cases
1 parent 4d1f4ef commit 9d3d9c0

4 files changed

Lines changed: 338 additions & 26 deletions

File tree

scheme/scheme.go

Lines changed: 138 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -141,21 +141,22 @@ type Scheme interface {
141141
}
142142

143143
const (
144-
SchemeAnyName = "SchemeAny"
145-
SchemeStringName = "SchemeString"
146-
SchemeBytesName = "SchemeBytes"
147-
SchemeMapName = "SchemeMap"
148-
SchemeTypeOnlyName = "SchemeTypeOnly"
149-
SchemeBoolName = "SchemeBool"
150-
SchemeInt8Name = "SchemeInt8"
151-
SchemeInt16Name = "SchemeInt16"
152-
SchemeInt32Name = "SchemeInt32"
153-
SchemeInt64Name = "SchemeInt64"
154-
SchemeFloat32Name = "SchemeFloat32"
155-
SchemeFloat64Name = "SchemeFloat64"
156-
SchemeNamedChainName = "SchemeNamedChain"
157-
SchemeMapUnorderedName = "SchemeMapUnordered"
158-
ChainName = "SchemeChain"
144+
SchemeAnyName = "SchemeAny"
145+
SchemeStringName = "SchemeString"
146+
SchemeBytesName = "SchemeBytes"
147+
SchemeMapName = "SchemeMap"
148+
SchemeTypeOnlyName = "SchemeTypeOnly"
149+
SchemeBoolName = "SchemeBool"
150+
SchemeInt8Name = "SchemeInt8"
151+
SchemeInt16Name = "SchemeInt16"
152+
SchemeInt32Name = "SchemeInt32"
153+
SchemeInt64Name = "SchemeInt64"
154+
SchemeFloat32Name = "SchemeFloat32"
155+
SchemeFloat64Name = "SchemeFloat64"
156+
SchemeNamedChainName = "SchemeNamedChain"
157+
SchemeMapUnorderedName = "SchemeMapUnordered"
158+
SchemeMultiCheckNamesSchemeNamed = "SchemeMultiCheckNamesSchemeNamed"
159+
ChainName = "SchemeChain"
159160

160161
TupleSchemeName = "TupleScheme"
161162
TupleSchemeNamedName = "TupleSchemeNamed"
@@ -1557,18 +1558,12 @@ type TupleSchemeNamed struct {
15571558
}
15581559

15591560
func STupleNamed(fieldNames []string, schema ...Scheme) TupleSchemeNamed {
1560-
if len(fieldNames) != len(schema) {
1561-
panic("STupleNamed: fieldNames and schema length mismatch")
1562-
}
15631561

15641562
return TupleSchemeNamed{FieldNames: fieldNames, Schema: schema, Nullable: true}
15651563
}
15661564

15671565
// Strict named tuple: exact field count
15681566
func STupleNamedVal(fieldNames []string, schema ...Scheme) TupleSchemeNamed {
1569-
if len(fieldNames) != len(schema) {
1570-
panic("STupleNamedVal: fieldNames and schema length mismatch")
1571-
}
15721567
return TupleSchemeNamed{
15731568
FieldNames: fieldNames,
15741569
Schema: schema,
@@ -1580,9 +1575,6 @@ func STupleNamedVal(fieldNames []string, schema ...Scheme) TupleSchemeNamed {
15801575

15811576
// Flexible named tuple: allows repeats/extra fields
15821577
func STupleNamedValFlattened(fieldNames []string, schema ...Scheme) TupleSchemeNamed {
1583-
if len(fieldNames) != len(schema) {
1584-
panic("STupleNamedValFlattened: fieldNames and schema length mismatch")
1585-
}
15861578
return TupleSchemeNamed{
15871579
FieldNames: fieldNames,
15881580
Schema: schema,
@@ -1597,6 +1589,9 @@ func (s TupleSchemeNamed) IsNullable() bool {
15971589
}
15981590

15991591
func (s TupleSchemeNamed) Validate(seq *access.SeqGetAccess) error {
1592+
if len(s.FieldNames) != len(s.Schema) {
1593+
return NewSchemeError(ErrConstraintViolated, TupleSchemeNamedName, "", 0, SizeExact{Actual: len(s.FieldNames), Exact: len(s.Schema)})
1594+
}
16001595
pos := seq.CurrentIndex()
16011596
_, err := precheck(TupleSchemeNamedName, pos, seq, types.TypeTuple, -1, s.IsNullable())
16021597
if err != nil {
@@ -1624,6 +1619,9 @@ func (s TupleSchemeNamed) Validate(seq *access.SeqGetAccess) error {
16241619
}
16251620

16261621
func (s TupleSchemeNamed) Decode(seq *access.SeqGetAccess) (any, error) {
1622+
if len(s.FieldNames) != len(s.Schema) {
1623+
return nil, NewSchemeError(ErrConstraintViolated, TupleSchemeNamedName, "", 0, SizeExact{Actual: len(s.FieldNames), Exact: len(s.Schema)})
1624+
}
16271625
pos := seq.CurrentIndex()
16281626
_, err := precheck(TupleSchemeNamedName, pos, seq, types.TypeTuple, -1, s.IsNullable())
16291627
if err != nil {
@@ -1665,13 +1663,15 @@ func (s TupleSchemeNamed) Decode(seq *access.SeqGetAccess) (any, error) {
16651663
}
16661664

16671665
func (s TupleSchemeNamed) Encode(put *access.PutAccess, val any) error {
1668-
1666+
if len(s.FieldNames) != len(s.Schema) {
1667+
return NewSchemeError(ErrConstraintViolated, TupleSchemeNamedName, "", 0, SizeExact{Actual: len(s.FieldNames), Exact: len(s.Schema)})
1668+
}
16691669
if mapKV, ok := val.(map[string]any); ok {
16701670

16711671
nested := put.BeginTuple()
16721672
defer put.EndNested(nested)
16731673
for i, key := range s.FieldNames {
1674-
if sch, ok := s.Schema[i].(SRepeatScheme); ok {
1674+
if sch, ok := s.Schema[i].(SRepeatScheme); ok && s.Flatten {
16751675

16761676
minx := sch.min
16771677
max := sch.max
@@ -1846,3 +1846,115 @@ outer:
18461846
}
18471847
return nil
18481848
}
1849+
1850+
// SchemeMultiCheckNamesScheme is a convenience scheme: every field is a SchemeBool.
1851+
type SchemeMultiCheckNamesScheme struct {
1852+
FieldNames []string
1853+
Nullable bool
1854+
}
1855+
1856+
func SMultiCheckNames(fieldNames []string) SchemeMultiCheckNamesScheme {
1857+
return SchemeMultiCheckNamesScheme{
1858+
FieldNames: fieldNames,
1859+
Nullable: true,
1860+
}
1861+
}
1862+
1863+
func (s SchemeMultiCheckNamesScheme) IsNullable() bool {
1864+
return s.Nullable
1865+
}
1866+
1867+
func (s SchemeMultiCheckNamesScheme) Validate(seq *access.SeqGetAccess) error {
1868+
pos := seq.CurrentIndex()
1869+
_, err := precheck(SchemeMultiCheckNamesSchemeNamed, pos, seq, types.TypeTuple, -1, s.IsNullable())
1870+
if err != nil {
1871+
return err
1872+
}
1873+
sub, err := seq.PeekNestedSeq()
1874+
if err != nil {
1875+
return NewSchemeError(ErrInvalidFormat, SchemeMultiCheckNamesSchemeNamed, "", pos, err)
1876+
}
1877+
if sub.ArgCount() != len(s.FieldNames) {
1878+
return NewSchemeError(ErrConstraintViolated, SchemeMultiCheckNamesSchemeNamed, "", pos,
1879+
SizeExact{Actual: sub.ArgCount(), Exact: len(s.FieldNames)})
1880+
}
1881+
for range s.FieldNames {
1882+
err := SchemeBool{}.Validate(sub)
1883+
if err != nil {
1884+
return NewSchemeError(ErrInvalidFormat, SchemeMultiCheckNamesSchemeNamed, "", pos, err)
1885+
}
1886+
}
1887+
if err := seq.Advance(); err != nil {
1888+
return NewSchemeError(ErrUnexpectedEOF, SchemeMultiCheckNamesSchemeNamed, "", pos, err)
1889+
}
1890+
return nil
1891+
}
1892+
1893+
func (s SchemeMultiCheckNamesScheme) Decode(seq *access.SeqGetAccess) (any, error) {
1894+
pos := seq.CurrentIndex()
1895+
_, err := precheck(SchemeMultiCheckNamesSchemeNamed, pos, seq, types.TypeTuple, -1, s.IsNullable())
1896+
if err != nil {
1897+
return nil, err
1898+
}
1899+
1900+
selected := make([]string, 0)
1901+
1902+
sub, err := seq.PeekNestedSeq()
1903+
if err != nil {
1904+
return nil, NewSchemeError(ErrInvalidFormat, SchemeMultiCheckNamesSchemeNamed, "", pos, err)
1905+
}
1906+
if sub.ArgCount() != len(s.FieldNames) {
1907+
return nil, NewSchemeError(ErrConstraintViolated, SchemeMultiCheckNamesSchemeNamed, "", pos,
1908+
SizeExact{Actual: sub.ArgCount(), Exact: len(s.FieldNames)})
1909+
}
1910+
1911+
for _, name := range s.FieldNames {
1912+
v, err := SchemeBool{}.Decode(sub)
1913+
if err != nil {
1914+
return nil, NewSchemeError(ErrInvalidFormat, SchemeMultiCheckNamesSchemeNamed, name, pos, err)
1915+
}
1916+
if b, ok := v.(bool); ok && b {
1917+
selected = append(selected, name)
1918+
}
1919+
}
1920+
1921+
if err := seq.Advance(); err != nil {
1922+
return nil, NewSchemeError(ErrUnexpectedEOF, SchemeMultiCheckNamesSchemeNamed, "", pos, err)
1923+
}
1924+
1925+
// Return only the slice of selected names
1926+
return selected, nil
1927+
}
1928+
1929+
func (s SchemeMultiCheckNamesScheme) Encode(put *access.PutAccess, val any) error {
1930+
1931+
set := make(map[string]struct{}, len(s.FieldNames))
1932+
switch v := val.(type) {
1933+
case []string:
1934+
for _, name := range v {
1935+
set[name] = struct{}{}
1936+
}
1937+
case []interface{}:
1938+
for _, elem := range v {
1939+
str, ok := elem.(string)
1940+
if !ok {
1941+
return NewSchemeError(ErrEncode, SchemeMultiCheckNamesSchemeNamed, "", -1, ErrTypeMisMatch)
1942+
}
1943+
set[str] = struct{}{}
1944+
}
1945+
default:
1946+
return NewSchemeError(ErrEncode, SchemeMultiCheckNamesSchemeNamed, "", -1, ErrTypeMisMatch)
1947+
}
1948+
1949+
nested := put.BeginTuple()
1950+
defer put.EndNested(nested)
1951+
1952+
for _, key := range s.FieldNames {
1953+
_, checked := set[key]
1954+
err := SchemeBool{}.Encode(nested, checked)
1955+
if err != nil {
1956+
return NewSchemeError(ErrInvalidFormat, SchemeMultiCheckNamesSchemeNamed, key, -1, err)
1957+
}
1958+
}
1959+
return nil
1960+
}

scheme/scheme_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,3 +1176,57 @@ func TestEncodePackedTuplesNamed(t *testing.T) {
11761176
assert.Equal(t, expected, actual,
11771177
"Encoder should produce packed tuples matching expected buffer")
11781178
}
1179+
1180+
func TestSchemeMultiCheckNamesScheme(t *testing.T) {
1181+
// Suppose we have three checkboxes: "read", "write", "execute"
1182+
fieldNames := []string{"read", "write", "execute"}
1183+
chain := SChain(
1184+
SMultiCheckNames(fieldNames),
1185+
)
1186+
1187+
// Pack a tuple of bools: read=true, write=false, execute=true
1188+
actual := pack.Pack(
1189+
pack.PackTuple(
1190+
pack.PackBool(true),
1191+
pack.PackBool(false),
1192+
pack.PackBool(true),
1193+
),
1194+
)
1195+
1196+
// Validate
1197+
err := ValidateBuffer(actual, chain)
1198+
assert.NoError(t, err, "Validation should succeed for packed structure")
1199+
1200+
// Decode
1201+
ret, err := DecodeBuffer(actual, chain)
1202+
assert.NoError(t, err, "Decoding should succeed for packed structure")
1203+
1204+
// Expected slice of selected names
1205+
expected := []string{"read", "execute"}
1206+
1207+
// Assert type and values
1208+
selected, ok := ret.([]string)
1209+
assert.True(t, ok, "decoded value should be []string")
1210+
assert.ElementsMatch(t, expected, selected, "selected names should match expected")
1211+
}
1212+
1213+
func TestSchemeMultiCheckNamesScheme_Encode(t *testing.T) {
1214+
fieldNames := []string{"read", "write", "execute"}
1215+
chain := SChain(
1216+
SMultiCheckNames(fieldNames),
1217+
)
1218+
1219+
// Encode []string{"write"} → should produce tuple [false,true,false]
1220+
val := []string{"write"}
1221+
encoded, err := EncodeValue(val, chain)
1222+
assert.NoError(t, err, "Encoding should succeed")
1223+
1224+
// Decode back
1225+
decoded, err := DecodeBuffer(encoded, chain)
1226+
assert.NoError(t, err, "Decoding should succeed")
1227+
1228+
expected := []string{"write"}
1229+
selected, ok := decoded.([]string)
1230+
assert.True(t, ok, "decoded value should be []string")
1231+
assert.ElementsMatch(t, expected, selected, "round-trip should preserve selected names")
1232+
}

scheme/schemebuilder_json.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,12 @@ func BuildScheme(js SchemeJSON) Scheme {
143143
return SMapUnorderedOptional(mapped)
144144
}
145145
return SMapUnordered(mapped)
146+
case "multicheck":
147+
if len(js.FieldNames) > 0 {
146148

149+
return SMultiCheckNames(js.FieldNames)
150+
}
151+
return SMultiCheckNames([]string{})
147152
default:
148153
panic("unknown scheme type: " + js.Type)
149154
}

0 commit comments

Comments
 (0)