@@ -14,10 +14,18 @@ import (
14
14
15
15
// describes a type for comparison in tests.
16
16
type typ struct {
17
- Type string
18
- Format string
19
- Props []attr
20
- SkipProps bool
17
+ Type string
18
+ Format string
19
+ Props []attr
20
+ SkipProps bool
21
+ AdditionalProperties * additionalPropsType // nil means no additionalProperties check
22
+ }
23
+
24
+ // additionalPropsType describes additionalProperties for testing
25
+ type additionalPropsType struct {
26
+ Type string // "string", "array", "object", "" (for reference)
27
+ Items * additionalPropsType // for array items
28
+ Ref string // for references like "#/components/schemas/MapData"
21
29
}
22
30
23
31
type attr struct {
@@ -253,6 +261,197 @@ func matchesSchemaWithPrefix(t *testing.T, ctx string, s *openapi.Schema, types
253
261
}
254
262
matchesSchemaWithPrefix (t , ctx , v , types , p , n + ": " )
255
263
}
264
+
265
+ // Check additionalProperties
266
+ if tt .AdditionalProperties != nil {
267
+ validateAdditionalProperties (t , ctx , s .AdditionalProperties , types , tt .AdditionalProperties , prefix )
268
+ }
269
+ }
270
+ }
271
+
272
+ func TestMapTypes (t * testing.T ) {
273
+ svcName := "test-service"
274
+
275
+ testCases := []struct {
276
+ Name string
277
+ DSL func ()
278
+ Expected typ
279
+ }{
280
+ {
281
+ Name : "map_int_array_string" ,
282
+ DSL : dsls .MapIntKeyBodyDSL (svcName , "map_int_array_string" ),
283
+ Expected : typ {
284
+ Type : "object" ,
285
+ Props : []attr {{Name : "intmap" , Val : typ {
286
+ Type : "object" ,
287
+ AdditionalProperties : & additionalPropsType {
288
+ Type : "array" ,
289
+ Items : & additionalPropsType {Type : "string" },
290
+ },
291
+ }}},
292
+ },
293
+ },
294
+ {
295
+ Name : "map_int_array_object" ,
296
+ DSL : dsls .MapIntKeyObjectBodyDSL (svcName , "map_int_array_object" ),
297
+ Expected : typ {
298
+ Type : "object" ,
299
+ Props : []attr {{Name : "intmap" , Val : typ {
300
+ Type : "object" ,
301
+ AdditionalProperties : & additionalPropsType {
302
+ Type : "array" ,
303
+ Items : & additionalPropsType {Ref : "#/components/schemas/MapData" },
304
+ },
305
+ }}},
306
+ },
307
+ },
308
+ {
309
+ Name : "map_int_string" ,
310
+ DSL : dsls .MapIntKeyStringBodyDSL (svcName , "map_int_string" ),
311
+ Expected : typ {
312
+ Type : "object" ,
313
+ Props : []attr {{Name : "intmap" , Val : typ {
314
+ Type : "object" ,
315
+ AdditionalProperties : & additionalPropsType {Type : "string" },
316
+ }}},
317
+ },
318
+ },
319
+ {
320
+ Name : "map_int_object_direct" ,
321
+ DSL : dsls .MapIntKeyObjectDirectBodyDSL (svcName , "map_int_object_direct" ),
322
+ Expected : typ {
323
+ Type : "object" ,
324
+ Props : []attr {{Name : "intmap" , Val : typ {
325
+ Type : "object" ,
326
+ AdditionalProperties : & additionalPropsType {Ref : "#/components/schemas/MapData" },
327
+ }}},
328
+ },
329
+ },
330
+ {
331
+ Name : "map_string_int" ,
332
+ DSL : dsls .MapStringKeyIntBodyDSL (svcName , "map_string_int" ),
333
+ Expected : typ {
334
+ Type : "object" ,
335
+ Props : []attr {{Name : "stringmap" , Val : typ {
336
+ Type : "object" ,
337
+ AdditionalProperties : & additionalPropsType {Type : "integer" },
338
+ }}},
339
+ },
340
+ },
341
+ {
342
+ Name : "map_string_object_direct" ,
343
+ DSL : dsls .MapStringKeyObjectDirectBodyDSL (svcName , "map_string_object_direct" ),
344
+ Expected : typ {
345
+ Type : "object" ,
346
+ Props : []attr {{Name : "stringmap" , Val : typ {
347
+ Type : "object" ,
348
+ AdditionalProperties : & additionalPropsType {Ref : "#/components/schemas/MapData" },
349
+ }}},
350
+ },
351
+ },
352
+ {
353
+ Name : "map_string_array_object" ,
354
+ DSL : dsls .MapStringKeyArrayObjectBodyDSL (svcName , "map_string_array_object" ),
355
+ Expected : typ {
356
+ Type : "object" ,
357
+ Props : []attr {{Name : "stringmap" , Val : typ {
358
+ Type : "object" ,
359
+ AdditionalProperties : & additionalPropsType {
360
+ Type : "array" ,
361
+ Items : & additionalPropsType {Ref : "#/components/schemas/MapData" },
362
+ },
363
+ }}},
364
+ },
365
+ },
366
+ }
367
+
368
+ for _ , tc := range testCases {
369
+ t .Run (tc .Name , func (t * testing.T ) {
370
+ // Build the OpenAPI spec
371
+ root := codegen .RunDSL (t , tc .DSL )
372
+ bodies , types := buildBodyTypes (root .API , root .Types , root .ResultTypes )
373
+
374
+ // Find the service and method
375
+ svcBodies , ok := bodies [svcName ]
376
+ if ! ok {
377
+ t .Fatalf ("Could not find service %s in bodies" , svcName )
378
+ }
379
+
380
+ methodBody , ok := svcBodies [tc .Name ]
381
+ if ! ok {
382
+ t .Fatalf ("Could not find method %s in service bodies" , tc .Name )
383
+ }
384
+
385
+ // Get the request body schema
386
+ requestBodyRef := methodBody .RequestBody .Ref
387
+ if requestBodyRef == "" {
388
+ t .Fatal ("Expected request body reference" )
389
+ }
390
+
391
+ requestBodyTypeName := nameFromRef (requestBodyRef )
392
+ requestBodySchema , ok := types [requestBodyTypeName ]
393
+ if ! ok {
394
+ t .Fatalf ("Could not find request body type %s" , requestBodyTypeName )
395
+ }
396
+
397
+ // Validate the schema
398
+ matchesSchema (t , tc .Name , requestBodySchema , types , tc .Expected )
399
+ })
400
+ }
401
+ }
402
+
403
+ func validateAdditionalProperties (t * testing.T , ctx string , addProps interface {}, types map [string ]* openapi.Schema , expected * additionalPropsType , prefix string ) {
404
+ if addProps == nil {
405
+ t .Errorf ("%s: %sexpected additionalProperties to be set" , ctx , prefix )
406
+ return
407
+ }
408
+
409
+ // Check if additionalProperties is a schema
410
+ schema , ok := addProps .(* openapi.Schema )
411
+ if ! ok {
412
+ t .Errorf ("%s: %sexpected additionalProperties to be schema, got %T" , ctx , prefix , addProps )
413
+ return
414
+ }
415
+
416
+ validateAdditionalPropsSchema (t , ctx , schema , types , expected , prefix + "additionalProperties: " )
417
+ }
418
+
419
+ func validateAdditionalPropsSchema (t * testing.T , ctx string , schema * openapi.Schema , types map [string ]* openapi.Schema , expected * additionalPropsType , prefix string ) {
420
+ // Handle reference case
421
+ if expected .Ref != "" {
422
+ if schema .Ref == "" {
423
+ t .Errorf ("%s: %sexpected reference to %s, but got inline schema" , ctx , prefix , expected .Ref )
424
+ return
425
+ }
426
+ if schema .Ref != expected .Ref {
427
+ t .Errorf ("%s: %sexpected reference %s, got %s" , ctx , prefix , expected .Ref , schema .Ref )
428
+ }
429
+ return
430
+ }
431
+
432
+ // Resolve reference if present
433
+ if schema .Ref != "" {
434
+ typeName := nameFromRef (schema .Ref )
435
+ resolvedSchema , ok := types [typeName ]
436
+ if ! ok {
437
+ t .Errorf ("%s: %scould not resolve reference %s" , ctx , prefix , schema .Ref )
438
+ return
439
+ }
440
+ schema = resolvedSchema
441
+ }
442
+
443
+ // Check type
444
+ if string (schema .Type ) != expected .Type {
445
+ t .Errorf ("%s: %sexpected type %s, got %s" , ctx , prefix , expected .Type , schema .Type )
446
+ }
447
+
448
+ // Check array items if expected
449
+ if expected .Items != nil {
450
+ if schema .Items == nil {
451
+ t .Errorf ("%s: %sexpected array items to be set" , ctx , prefix )
452
+ } else {
453
+ validateAdditionalPropsSchema (t , ctx , schema .Items , types , expected .Items , prefix + "items: " )
454
+ }
256
455
}
257
456
}
258
457
0 commit comments