Skip to content

Commit 50526ab

Browse files
Added SLang and also Custom json Scheme for users
1 parent 94e2d95 commit 50526ab

File tree

4 files changed

+140
-2
lines changed

4 files changed

+140
-2
lines changed

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
module github.com/quickwritereader/PackOS
22

3-
go 1.23
3+
go 1.24.0
4+
5+
toolchain go1.24.12
46

57
require (
68
github.com/goccy/go-json v0.10.5
@@ -18,5 +20,6 @@ require (
1820
github.com/pmezard/go-difflib v1.0.0 // indirect
1921
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
2022
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
23+
golang.org/x/text v0.33.0 // indirect
2124
gopkg.in/yaml.v3 v3.0.1 // indirect
2225
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ github.com/ymz-ncnk/mok v0.2.0 h1:4xOvkG0EXDlr05ZTSeRzM2q5mbyNbALRqRoI+cTwUyc=
2828
github.com/ymz-ncnk/mok v0.2.0/go.mod h1:VDVVGULp0vdJeD27SgJghOFGyD7w3nX7SDh8TOlvIsY=
2929
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
3030
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
31+
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
32+
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
3133
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
3234
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
3335
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

scheme/scheme.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/quickwritereader/PackOS/access"
1515
"github.com/quickwritereader/PackOS/types"
16+
"golang.org/x/text/language"
1617
)
1718

1819
type ErrorCode int
@@ -32,7 +33,7 @@ const (
3233
ErrStringMatch // exact match failed
3334
ErrStringEmail // email format validation failed
3435
ErrStringURL // URL/URI format validation failed
35-
36+
ErrStringLang // language tag format validation failed
3637
// Numeric validation codes
3738
ErrOutOfRange // integer value out of allowed range
3839
ErrDateOutOfRange // timestamp/date value out of allowed range
@@ -65,6 +66,8 @@ func (e ErrorCode) String() string {
6566
return "ErrStringEmail"
6667
case ErrStringURL:
6768
return "ErrStringURL"
69+
case ErrStringLang:
70+
return "ErrStringLang"
6871
case ErrOutOfRange:
6972
return "ErrOutOfRange"
7073
case ErrDateOutOfRange:
@@ -2040,6 +2043,34 @@ func SURI(optional bool) Scheme {
20402043
)
20412044
}
20422045

2046+
// SLang validates language codes using golang.org/x/text/language
2047+
func SLang(optional bool) Scheme {
2048+
s := SString
2049+
if optional {
2050+
s.Optional()
2051+
}
2052+
return s.CheckFunc(
2053+
ErrStringLang, // define your own error type similar to ErrStringURL
2054+
"Language Code",
2055+
func(payloadStr string) bool {
2056+
payloadStr = strings.TrimSpace(payloadStr)
2057+
if len(payloadStr) != 2 {
2058+
return false
2059+
}
2060+
2061+
// Try parsing with x/text/language
2062+
tag, err := language.Parse(payloadStr)
2063+
if err != nil {
2064+
return false
2065+
}
2066+
2067+
_, conf := tag.Base()
2068+
return conf != language.No
2069+
2070+
},
2071+
)
2072+
}
2073+
20432074
// SDate constrains an int64 payload to a date range (Unix seconds)
20442075
// and decodes into time.Time.
20452076
func SDate(nullable bool, from, to time.Time) Scheme {

scheme/schemebuilder_json.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,98 @@ type SchemeJSON struct {
3232
Extra map[string]any `json:"extra,omitempty"`
3333
}
3434

35+
// Registry of custom scheme builders.
36+
// Key: type name (case-sensitive), Value: builder function.
37+
var customSchemeBuilders = map[string]func(SchemeJSON) Scheme{}
38+
39+
// RegisterSchemeType registers a custom Scheme builder for a given type name.
40+
//
41+
// Usage:
42+
//
43+
// scheme.RegisterSchemeType("MyCustomType", func(js scheme.SchemeJSON) scheme.Scheme {
44+
// // Build your own Scheme based on js
45+
// return SString.WithWidth(js.Width) // or any custom logic
46+
// })
47+
//
48+
// Notes:
49+
// - Type names are case-sensitive ("MyCustomType" ≠ "mycustomtype").
50+
// - Panics if the type name is already registered (built-in or custom).
51+
// - Use UnregisterSchemeType to remove a custom type.
52+
//
53+
// This allows users to extend BuildScheme with their own types without
54+
// modifying the core switch.
55+
func RegisterSchemeType(typeName string, builder func(SchemeJSON) Scheme) {
56+
if typeName == "" {
57+
panic("cannot register empty type name")
58+
}
59+
if _, exists := customSchemeBuilders[typeName]; exists {
60+
panic("scheme type already registered: " + typeName)
61+
}
62+
customSchemeBuilders[typeName] = builder
63+
}
64+
65+
// UnregisterSchemeType removes a previously registered custom Scheme builder.
66+
//
67+
// Usage:
68+
//
69+
// scheme.UnregisterSchemeType("MyCustomType")
70+
//
71+
// If the type name is not found, the function does nothing.
72+
func UnregisterSchemeType(typeName string) {
73+
delete(customSchemeBuilders, typeName)
74+
}
75+
76+
// BuildScheme constructs a Scheme instance from a SchemeJSON definition.
77+
//
78+
// It inspects the `Type` field of the provided SchemeJSON and returns the
79+
// corresponding Scheme. Built-in types include:
80+
//
81+
// - "bool" → SBool / SNullBool
82+
// - "int8" → SInt8 / SNullInt8
83+
// - "int16" → SInt16 with optional Range
84+
// - "int32" → SInt32 with optional Range
85+
// - "int64" → SInt64 with optional Range
86+
// - "date" → SDate with optional DateFrom/DateTo
87+
// - "float32" → SFloat32 / SNullFloat32
88+
// - "float64" → SFloat64 / SNullFloat64
89+
// - "string" → SString with optional width, exact, prefix, suffix, pattern
90+
// - "email" → SEmail
91+
// - "uri" → SURI
92+
// - "lang" → SLang
93+
// - "bytes" → SBytes / SVariableBytes
94+
// - "any" → SAny
95+
// - "tuple" → STuple / STupleNamed / STupleVal (with flatten/variableLength)
96+
// - "repeat" → SRepeat
97+
// - "map" → SMap
98+
// - "mapUnordered" → SMapUnordered / SMapUnorderedOptional
99+
// - "mapRepeat" → SMapRepeatRange
100+
// - "multicheck" → SMultiCheckNames
101+
// - "enum" → SEnum
102+
// - "color" → SColor
103+
//
104+
// If the type is not recognized, BuildScheme checks the custom registry
105+
// (see RegisterSchemeType) before panicking.
106+
//
107+
// Usage:
108+
//
109+
// js := SchemeJSON{Type: "string", Width: 20, Prefix: "ID_"}
110+
// s := BuildScheme(js)
111+
// s now validates strings up to 20 chars starting with "ID_"
112+
//
113+
// Custom type example:
114+
//
115+
// scheme.RegisterSchemeType("MyCustomType", func(js scheme.SchemeJSON) scheme.Scheme {
116+
// return SString.Pattern("[A-Z]{3}[0-9]{2}")
117+
// })
118+
// custom := BuildScheme(SchemeJSON{Type: "MyCustomType"})
119+
//
120+
// Notes:
121+
// - Type names are case-sensitive.
122+
// - Nullable fields are respected where applicable.
123+
// - RangeMin/RangeMax apply to numeric types.
124+
// - DateFrom/DateTo must be RFC3339 strings.
125+
// - For "mapUnordered", FieldNames and Schema must align in length.
126+
// - For "mapRepeat", Schema must contain exactly two entries.
35127
func BuildScheme(js SchemeJSON) Scheme {
36128
switch js.Type {
37129
case "bool":
@@ -117,6 +209,8 @@ func BuildScheme(js SchemeJSON) Scheme {
117209
return SEmail(js.Nullable)
118210
case "uri":
119211
return SURI(js.Nullable)
212+
case "lang":
213+
return SLang(js.Nullable)
120214
case "bytes":
121215
if js.Width > 0 {
122216
return SBytes(js.Width)
@@ -175,10 +269,18 @@ func BuildScheme(js SchemeJSON) Scheme {
175269
case "color":
176270
return SColor(js.Nullable)
177271
default:
272+
// Check custom registry before panicking
273+
if builder, ok := customSchemeBuilders[js.Type]; ok {
274+
return builder(js)
275+
}
178276
panic("unknown scheme type: " + js.Type)
179277
}
180278
}
181279

280+
// buildSchemas is an internal helper that converts a slice of SchemeJSON
281+
// definitions into a slice of Scheme instances by delegating to BuildScheme.
282+
// It preserves the order of the input list and is primarily used by composite
283+
// types (tuple, map, repeat, etc.) when constructing nested schemas.
182284
func buildSchemas(list []SchemeJSON) []Scheme {
183285
out := make([]Scheme, len(list))
184286
for i, sub := range list {

0 commit comments

Comments
 (0)