github.com/kaptinlin/jsonschema@v0.4.6/default_funcs_test.go (about) 1 package jsonschema 2 3 import ( 4 "fmt" 5 "math/rand" 6 "testing" 7 "time" 8 ) 9 10 func TestDefaultFunc_DefaultNowFunc(t *testing.T) { 11 tests := []struct { 12 name string 13 args []any 14 wantType string 15 }{ 16 { 17 name: "default RFC3339", 18 args: []any{}, 19 wantType: "string", 20 }, 21 { 22 name: "custom format", 23 args: []any{"2006-01-02"}, 24 wantType: "string", 25 }, 26 { 27 name: "another custom format", 28 args: []any{"15:04:05"}, 29 wantType: "string", 30 }, 31 } 32 33 for _, tt := range tests { 34 t.Run(tt.name, func(t *testing.T) { 35 result, err := DefaultNowFunc(tt.args...) 36 if err != nil { 37 t.Errorf("DefaultNowFunc() error = %v", err) 38 return 39 } 40 41 if _, ok := result.(string); !ok { 42 t.Errorf("DefaultNowFunc() result type = %T, want string", result) 43 } 44 }) 45 } 46 } 47 48 func TestParseFunctionCall(t *testing.T) { 49 tests := []struct { 50 name string 51 input string 52 want *FunctionCall 53 wantErr bool 54 }{ 55 { 56 name: "simple function no args", 57 input: "now()", 58 want: &FunctionCall{Name: "now", Args: []any{}}, 59 }, 60 { 61 name: "function with string arg", 62 input: "now(unix)", 63 want: &FunctionCall{Name: "now", Args: []any{"unix"}}, 64 }, 65 { 66 name: "function with multiple args", 67 input: "func(arg1, 42, 3.14)", 68 want: &FunctionCall{Name: "func", Args: []any{"arg1", int64(42), float64(3.14)}}, 69 }, 70 { 71 name: "not a function call", 72 input: "just a string", 73 want: nil, 74 }, 75 { 76 name: "empty string", 77 input: "", 78 want: nil, 79 }, 80 { 81 name: "invalid format", 82 input: "func(", 83 want: nil, 84 }, 85 } 86 87 for _, tt := range tests { 88 t.Run(tt.name, func(t *testing.T) { 89 got, err := parseFunctionCall(tt.input) 90 if (err != nil) != tt.wantErr { 91 t.Errorf("parseFunctionCall() error = %v, wantErr %v", err, tt.wantErr) 92 return 93 } 94 95 if tt.want == nil { 96 if got != nil { 97 t.Errorf("parseFunctionCall() = %v, want nil", got) 98 } 99 return 100 } 101 102 if got == nil { 103 t.Errorf("parseFunctionCall() = nil, want %v", tt.want) 104 return 105 } 106 107 if got.Name != tt.want.Name { 108 t.Errorf("parseFunctionCall() Name = %v, want %v", got.Name, tt.want.Name) 109 } 110 111 if len(got.Args) != len(tt.want.Args) { 112 t.Errorf("parseFunctionCall() Args length = %v, want %v", len(got.Args), len(tt.want.Args)) 113 return 114 } 115 116 for i, arg := range got.Args { 117 if arg != tt.want.Args[i] { 118 t.Errorf("parseFunctionCall() Args[%d] = %v, want %v", i, arg, tt.want.Args[i]) 119 } 120 } 121 }) 122 } 123 } 124 125 func TestCompiler_RegisterDefaultFunc(t *testing.T) { 126 compiler := NewCompiler() 127 128 // Test registration 129 testFunc := func(args ...any) (any, error) { 130 return "test_result", nil 131 } 132 133 compiler.RegisterDefaultFunc("test", testFunc) 134 135 // Test retrieval 136 fn, exists := compiler.getDefaultFunc("test") 137 if !exists { 138 t.Error("Expected function to be registered") 139 } 140 141 result, err := fn() 142 if err != nil { 143 t.Errorf("Function call failed: %v", err) 144 } 145 146 if result != "test_result" { 147 t.Errorf("Function result = %v, want test_result", result) 148 } 149 } 150 151 // customIDFunc generates a simple random ID for testing 152 func customIDFunc(args ...any) (any, error) { 153 return fmt.Sprintf("id_%d_%d", time.Now().Unix(), rand.Intn(10000)), nil 154 } 155 156 func TestDynamicDefaultValues_Integration(t *testing.T) { 157 compiler := NewCompiler() 158 159 // Register functions 160 compiler.RegisterDefaultFunc("now", DefaultNowFunc) 161 compiler.RegisterDefaultFunc("randomId", customIDFunc) 162 163 // Create schema with dynamic defaults 164 schemaJSON := `{ 165 "type": "object", 166 "properties": { 167 "id": { 168 "type": "string", 169 "default": "randomId()" 170 }, 171 "createdAt": { 172 "type": "string", 173 "default": "now()" 174 }, 175 "updatedAt": { 176 "type": "string", 177 "default": "now(2006-01-02 15:04:05)" 178 }, 179 "status": { 180 "type": "string", 181 "default": "active" 182 }, 183 "version": { 184 "type": "string", 185 "default": "unregistered_func()" 186 } 187 } 188 }` 189 190 schema, err := compiler.Compile([]byte(schemaJSON)) 191 if err != nil { 192 t.Fatalf("Failed to compile schema: %v", err) 193 } 194 195 // Test with partial data 196 inputData := map[string]interface{}{ 197 "status": "pending", 198 } 199 200 var result map[string]interface{} 201 err = schema.Unmarshal(&result, inputData) 202 if err != nil { 203 t.Fatalf("Failed to unmarshal: %v", err) 204 } 205 206 // Verify results 207 if result["status"] != "pending" { 208 t.Errorf("status = %v, want pending", result["status"]) 209 } 210 211 // Check that random ID was generated 212 if id, ok := result["id"].(string); !ok || len(id) == 0 { 213 t.Errorf("id should be a non-empty string, got %v", result["id"]) 214 } else if id[:3] != "id_" { 215 t.Errorf("id should start with 'id_', got %v", id) 216 } 217 218 // Check that timestamp was generated 219 if createdAt, ok := result["createdAt"].(string); !ok || len(createdAt) == 0 { 220 t.Errorf("createdAt should be a non-empty string, got %v", result["createdAt"]) 221 } 222 223 // Check that updated timestamp was generated 224 if updatedAt, ok := result["updatedAt"].(string); !ok || len(updatedAt) == 0 { 225 t.Errorf("updatedAt should be a non-empty string, got %v", result["updatedAt"]) 226 } 227 228 // Check that unregistered function falls back to literal 229 if version, ok := result["version"].(string); !ok || version != "unregistered_func()" { 230 t.Errorf("version should be literal string 'unregistered_func()', got %v", result["version"]) 231 } 232 233 t.Logf("Result: %+v", result) 234 } 235 236 func TestSchemaSetCompiler(t *testing.T) { 237 compiler := NewCompiler() 238 compiler.RegisterDefaultFunc("test", func(args ...any) (any, error) { 239 return "test_value", nil 240 }) 241 242 schema := Object( 243 Prop("field", String(Default("test()"))), 244 ).SetCompiler(compiler) 245 246 data := map[string]interface{}{} 247 var result map[string]interface{} 248 err := schema.Unmarshal(&result, data) 249 if err != nil { 250 t.Errorf("Unmarshal() error = %v", err) 251 return 252 } 253 if result["field"] != "test_value" { 254 t.Errorf("result['field'] = %v, want test_value", result["field"]) 255 } 256 } 257 258 func TestSchemaCompilerIsolation(t *testing.T) { 259 compiler1 := NewCompiler() 260 compiler1.RegisterDefaultFunc("func1", func(args ...any) (any, error) { 261 return "value1", nil 262 }) 263 264 compiler2 := NewCompiler() 265 compiler2.RegisterDefaultFunc("func2", func(args ...any) (any, error) { 266 return "value2", nil 267 }) 268 269 schema1 := Object( 270 Prop("field1", String(Default("func1()"))), 271 ).SetCompiler(compiler1) 272 273 schema2 := Object( 274 Prop("field2", String(Default("func2()"))), 275 ).SetCompiler(compiler2) 276 277 // Verify isolation 278 data1 := map[string]interface{}{} 279 data2 := map[string]interface{}{} 280 var result1, result2 map[string]interface{} 281 err := schema1.Unmarshal(&result1, data1) 282 if err != nil { 283 t.Errorf("schema1.Unmarshal() error = %v", err) 284 return 285 } 286 err = schema2.Unmarshal(&result2, data2) 287 if err != nil { 288 t.Errorf("schema2.Unmarshal() error = %v", err) 289 return 290 } 291 292 if result1["field1"] != "value1" { 293 t.Errorf("result1['field1'] = %v, want value1", result1["field1"]) 294 } 295 if result2["field2"] != "value2" { 296 t.Errorf("result2['field2'] = %v, want value2", result2["field2"]) 297 } 298 } 299 300 func TestSchemaSetCompilerChaining(t *testing.T) { 301 compiler := NewCompiler() 302 compiler.RegisterDefaultFunc("test", func(args ...any) (any, error) { 303 return "chained", nil 304 }) 305 306 // Test method chaining 307 schema := Object( 308 Prop("field", String(Default("test()"))), 309 ).SetCompiler(compiler) 310 311 data := map[string]interface{}{} 312 var result map[string]interface{} 313 err := schema.Unmarshal(&result, data) 314 if err != nil { 315 t.Errorf("Unmarshal() error = %v", err) 316 return 317 } 318 if result["field"] != "chained" { 319 t.Errorf("result['field'] = %v, want chained", result["field"]) 320 } 321 } 322 323 func TestSchemaCompilerInheritance(t *testing.T) { 324 compiler := NewCompiler() 325 compiler.RegisterDefaultFunc("test", func(args ...any) (any, error) { 326 return "inherited", nil 327 }) 328 329 // Parent Schema sets Compiler, child Schema should inherit 330 schema := Object( 331 Prop("child", String(Default("test()"))), // Child Schema doesn't set compiler 332 ).SetCompiler(compiler) // Only set on parent Schema 333 334 data := map[string]interface{}{} 335 var result map[string]interface{} 336 err := schema.Unmarshal(&result, data) 337 if err != nil { 338 t.Errorf("Unmarshal() error = %v", err) 339 return 340 } 341 if result["child"] != "inherited" { 342 t.Errorf("result['child'] = %v, want inherited", result["child"]) 343 } 344 }