-
Notifications
You must be signed in to change notification settings - Fork 40
Expand file tree
/
Copy pathzogSchema.go
More file actions
158 lines (138 loc) · 4.9 KB
/
Copy pathzogSchema.go
File metadata and controls
158 lines (138 loc) · 4.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package zog
import (
p "github.com/Oudwins/zog/pkgs/internals"
zss "github.com/Oudwins/zog/pkgs/zss/core"
"github.com/Oudwins/zog/zconst"
)
// The ZogSchema is the interface all schemas must implement
// This is most useful for internal use. If you are looking to pass schemas around, use the ComplexZogSchema or PrimitiveZogSchema interfaces if possible.
type ZogSchema interface {
process(ctx *p.SchemaCtx)
validate(ctx *p.SchemaCtx)
getType() zconst.ZogType
setCoercer(c CoercerFunc)
toZSS(*ZSSSerializeCtx) *zss.ZSSSchema
}
// This is a common interface for all complex schemas (i.e structs, slices, pointers...)
// You can use this to pass any complex schema around
type ComplexZogSchema interface {
ZogSchema
Parse(val any, dest any, options ...ExecOption) ZogIssueList
}
// This is a common interface for all primitive schemas (i.e strings, numbers, booleans, time.Time...)
// You can use this to pass any primitive schema around
type PrimitiveZogSchema[T p.ZogPrimitive] interface {
ZogSchema
Parse(val any, dest *T, options ...ExecOption) ZogIssueList
}
// Shape Parts Export
// Function signature for transforms. Takes the value pointer and the context and returns an optional error.
type Transform[T any] p.Transform[T]
// Function signature for issue formatters. Takes the issue and the context and returns the formatted issue.
type IssueFmtFunc = p.IssueFmtFunc
// Function signature for tests. Takes the value and the context and returns a boolean.
// This used to be a function you could pass to the schema.Test method -> `s.Test(z.TFunc(fn))`. But that has been deprecated. Use `schema.TFunc(fn)` instead.
type TFunc[T any] p.TFunc[T]
// Function signature for bool tests. Takes the value and the context and returns a boolean. This is the function passed to the TestFunc method.
type BoolTFunc[T any] p.BoolTFunc[T]
// Creates a reusable testFunc you can add to schemas by doing schema.Test(z.TestFunc()). Has the same API as schema.TestFunc() so it is recommended you use that one for non reusable tests.
func TestFunc[T any](IssueCode zconst.ZogIssueCode, fn BoolTFunc[T], options ...p.TestOption) Test[T] {
return Test[T](*p.NewTestFunc(IssueCode, p.BoolTFunc[T](fn), options...))
}
// ! PRIMITIVE PROCESSING -> Not userspace code
func primitiveParsing[T p.ZogPrimitive](ctx *p.SchemaCtx, processors []p.ZProcessor[*T], defaultFunc func() T, required *p.Test[*T], catchFunc func() T, coercer CoercerFunc, isZeroFunc p.IsZeroValueFunc) {
ctx.CanCatch = catchFunc != nil
destPtr, ok := ctx.ValPtr.(*T)
if !ok {
ctx.Errors.Add(ctx.IssueFromInvalidType("pointer matching primitive schema type", ctx.ValPtr, "parsing a primitive schema"))
return
}
// 2. cast data to string & handle default/required
isZeroVal := isZeroFunc(ctx.Data, ctx)
if isZeroVal {
if defaultFunc != nil {
*destPtr = defaultFunc()
} else if required == nil {
// This handles optional case
return
} else {
// is required & zero value
// required
if ctx.CanCatch {
*destPtr = catchFunc()
return
} else {
ctx.AddIssue(ctx.IssueFromTest(required, *destPtr))
return
}
}
} else {
v, err := coercer(ctx.Data)
if err != nil {
if ctx.CanCatch {
*destPtr = catchFunc()
return
}
ctx.AddIssue(ctx.IssueFromCoerce(err))
return
}
x, ok := v.(T)
if !ok {
p.Panicf(p.PanicTypeCastCoercer, ctx.String(), ctx.DType, v)
}
*destPtr = x
}
for _, processor := range processors {
ctx.Processor = processor
processor.ZProcess(destPtr, ctx)
if ctx.Exit {
if ctx.CanCatch {
*destPtr = catchFunc()
return
}
return
}
}
}
func primitiveValidation[T p.ZogPrimitive](ctx *p.SchemaCtx, processors []p.ZProcessor[*T], defaultFunc func() T, required *p.Test[*T], catchFunc func() T) {
ctx.CanCatch = catchFunc != nil
valPtr, ok := ctx.ValPtr.(*T)
if !ok {
// We have to go directly to the exec context as that is what formats. We cannot use ctx because it will try to catch the issue and this is an uncatchable issue
// since we cannot set the value as its not of type *T
ctx.ExecCtx.AddIssue(ctx.IssueFromInvalidType("pointer matching primitive schema type", ctx.ValPtr, "validating a primitive schema"))
return
}
// 2. cast data to string & handle default/required
// Warning. This uses generic IsZeroValue because for Validate we treat zero values as invalid for required fields. This is different from Parse.
isZeroVal := p.IsZeroValue(*valPtr)
if isZeroVal {
if defaultFunc != nil {
*valPtr = defaultFunc()
} else if required == nil {
// This handles optional case
return
} else {
// is required & zero value
// required
if ctx.CanCatch {
*valPtr = catchFunc()
return
} else {
ctx.AddIssue(ctx.IssueFromTest(required, *valPtr))
return
}
}
}
for _, processor := range processors {
ctx.Processor = processor
processor.ZProcess(valPtr, ctx)
if ctx.Exit {
if ctx.CanCatch {
*valPtr = catchFunc()
return
}
return
}
}
}