@@ -70,7 +70,7 @@ func structToSchema(prog *Program, name, tagName string, ref Reference) (*Schema
7070 name = p .Name
7171 }
7272
73- prop , err := fieldToSchema (prog , name , tagName , ref , p .KindField )
73+ prop , err := fieldToSchema (prog , name , tagName , ref , p .KindField , nil )
7474 if err != nil {
7575 return nil , fmt .Errorf ("cannot parse %v: %v" , ref .Lookup , err )
7676 }
@@ -215,7 +215,13 @@ func setTags(name, fName string, p *Schema, tags []string) error {
215215}
216216
217217// Convert a struct field to JSON schema.
218- func fieldToSchema (prog * Program , fName , tagName string , ref Reference , f * ast.Field ) (* Schema , error ) {
218+ func fieldToSchema (
219+ prog * Program ,
220+ fName , tagName string ,
221+ ref Reference ,
222+ f * ast.Field ,
223+ generics map [string ]string ,
224+ ) (* Schema , error ) {
219225 var p Schema
220226
221227 if f .Doc != nil {
@@ -283,6 +289,10 @@ start:
283289 if mappedType != "" {
284290 p .Type = JSONSchemaType (mappedType )
285291 }
292+ if generics != nil && generics [typ .Name ] != "" {
293+ mappedType = "generics"
294+ p .Type = JSONSchemaType (generics [typ .Name ])
295+ }
286296 if p .Type == "enum" && len (p .Enum ) == 0 {
287297 if variations , err := getEnumVariations (ref .File , pkg , typ .Name ); len (variations ) > 0 {
288298 p .Enum = variations
@@ -320,7 +330,7 @@ start:
320330 p .Properties = map [string ]* Schema {}
321331 for _ , f := range typ .Fields .List {
322332 propName := goutil .TagName (f , tagName )
323- prop , err := fieldToSchema (prog , propName , tagName , ref , f )
333+ prop , err := fieldToSchema (prog , propName , tagName , ref , f , generics )
324334 if err != nil {
325335 return nil , fmt .Errorf ("anon struct: %v" , err )
326336 }
@@ -373,7 +383,7 @@ start:
373383 case * ast.ArrayType :
374384 isEnum := p .Type == "enum"
375385 p .Type = "array"
376- err := resolveArray (prog , ref , pkg , & p , resolvType .Elt , isEnum )
386+ err := resolveArray (prog , ref , pkg , & p , resolvType .Elt , isEnum , generics )
377387 if err != nil {
378388 return nil , err
379389 }
@@ -393,6 +403,9 @@ start:
393403 dbg ("ERR FOUND MapType: %s" , err .Error ())
394404 return & p , nil
395405 }
406+ if generics != nil && generics [vtyp .Name ] != "" {
407+ vtyp .Name = generics [vtyp .Name ]
408+ }
396409 if isPrimitive (vtyp .Name ) {
397410 // we are done, no need for a lookup of a custom type
398411 p .AdditionalProperties = & Schema {Type : JSONSchemaType (vtyp .Name )}
@@ -418,13 +431,34 @@ start:
418431 isEnum := p .Type == "enum"
419432 p .Type = "array"
420433
421- err := resolveArray (prog , ref , pkg , & p , typ .Elt , isEnum )
434+ err := resolveArray (prog , ref , pkg , & p , typ .Elt , isEnum , generics )
422435 if err != nil {
423436 return nil , err
424437 }
425438
426439 return & p , nil
427440
441+ // Generic types
442+ case * ast.IndexExpr :
443+ genericsIdent , ok := typ .X .(* ast.Ident )
444+ if ! ok {
445+ return nil , fmt .Errorf ("unknown generic type: %T" , typ .X )
446+ }
447+ if err := fillGenericsSchema (prog , & p , tagName , ref , genericsIdent , generics , typ .Index ); err != nil {
448+ return nil , fmt .Errorf ("generic fieldToSchema: %v" , err )
449+ }
450+ return & p , nil
451+
452+ case * ast.IndexListExpr :
453+ genericsIdent , ok := typ .X .(* ast.Ident )
454+ if ! ok {
455+ return nil , fmt .Errorf ("unknown generic type: %T" , typ .X )
456+ }
457+ if err := fillGenericsSchema (prog , & p , tagName , ref , genericsIdent , generics , typ .Indices ... ); err != nil {
458+ return nil , fmt .Errorf ("generic fieldToSchema: %v" , err )
459+ }
460+ return & p , nil
461+
428462 default :
429463 return nil , fmt .Errorf ("fieldToSchema: unknown type: %T" , typ )
430464 }
@@ -456,6 +490,69 @@ start:
456490 return & p , nil
457491}
458492
493+ // fillGenericsSchema fills the schema with the generic type information. As the
494+ // types can be different for every generics declaration they will need to be a
495+ // anonymos object in the schema output instead of a reusable reference.
496+ func fillGenericsSchema (
497+ prog * Program ,
498+ p * Schema ,
499+ tagName string ,
500+ ref Reference ,
501+ genericsIdent * ast.Ident ,
502+ generics map [string ]string ,
503+ indices ... ast.Expr ,
504+ ) error {
505+ genericsType , _ , _ , err := findType (ref .File , ref .Package , genericsIdent .Name )
506+ if err != nil {
507+ return fmt .Errorf ("cannot find generic type: %v" , err )
508+ }
509+
510+ var genericsTemplateIDs []string
511+ for _ , item := range genericsType .TypeParams .List {
512+ for _ , name := range item .Names {
513+ genericsTemplateIDs = append (genericsTemplateIDs , name .Name )
514+ }
515+ }
516+
517+ if generics == nil {
518+ generics = make (map [string ]string )
519+ }
520+ if len (genericsTemplateIDs ) > 0 {
521+ if len (indices ) != len (genericsTemplateIDs ) {
522+ return fmt .Errorf ("generic type has %d template IDs, but %d arguments were provided" ,
523+ len (genericsTemplateIDs ), len (indices ))
524+ }
525+ for i := 0 ; i < len (indices ); i ++ {
526+ arg , _ , err := findTypeIdent (indices [i ], ref .Package )
527+ if err != nil {
528+ return fmt .Errorf ("cannot find generic type argument: %v" , err )
529+ }
530+ generics [genericsTemplateIDs [i ]] = arg .Name
531+ }
532+ }
533+
534+ genericsStruct , ok := genericsType .Type .(* ast.StructType )
535+ if ! ok {
536+ return fmt .Errorf ("generic type is not a struct: %T" , genericsType .Type )
537+ }
538+
539+ p .Type = "object"
540+ if p .Properties == nil {
541+ p .Properties = make (map [string ]* Schema )
542+ }
543+
544+ for _ , field := range genericsStruct .Fields .List {
545+ fieldName := goutil .TagName (field , tagName )
546+ schema , err := fieldToSchema (prog , fieldName , tagName , ref , field , generics )
547+ if err != nil {
548+ return fmt .Errorf ("generic fieldToSchema: %v" , err )
549+ }
550+ p .Properties [fieldName ] = schema
551+ }
552+
553+ return nil
554+ }
555+
459556// Helper function to extract enum variations from a file.
460557func getEnumVariations (currentFile , pkgPath , typeName string ) ([]string , error ) {
461558 resolvedPath , pkg , err := resolvePackage (currentFile , pkgPath )
@@ -532,7 +629,15 @@ func lookupTypeAndRef(file, pkg, name string) (string, string, error) {
532629 return t , sRef , nil
533630}
534631
535- func resolveArray (prog * Program , ref Reference , pkg string , p * Schema , typ ast.Expr , isEnum bool ) error {
632+ func resolveArray (
633+ prog * Program ,
634+ ref Reference ,
635+ pkg string ,
636+ p * Schema ,
637+ typ ast.Expr ,
638+ isEnum bool ,
639+ generics map [string ]string ,
640+ ) error {
536641 asw := typ
537642
538643 var name * ast.Ident
@@ -550,7 +655,11 @@ arrayStart:
550655
551656 dbg ("resolveArray: ident: %#v in %#v" , typ .Name , pkg )
552657
553- p .Items = & Schema {Type : JSONSchemaType (typ .Name )}
658+ if generics != nil && generics [typ .Name ] != "" {
659+ p .Items = & Schema {Type : JSONSchemaType (generics [typ .Name ])}
660+ } else {
661+ p .Items = & Schema {Type : JSONSchemaType (typ .Name )}
662+ }
554663
555664 // Generally an item is an enum rather than the array itself
556665 if len (p .Enum ) > 0 {
@@ -671,7 +780,7 @@ func JSONSchemaType(t string) string {
671780 return t
672781}
673782
674- func getTypeInfo (prog * Program , lookup , filePath string ) (string , error ) {
783+ func getTypeInfo (_ * Program , lookup , filePath string ) (string , error ) {
675784 // TODO: REMOVE THE prog PARAM, as this function is not
676785 // using it anymore.
677786 dbg ("getTypeInfo: %#v in %#v" , lookup , filePath )
0 commit comments