func (scp *schemaParser) parseInterfaceType(gofile *ast.File, bschema *spec.Schema, tpe *ast.InterfaceType, seenPreviously map[string]struct{}) error { if tpe.Methods == nil { return nil } // first check if this has embedded interfaces, if so make sure to refer to those by ref // when they are decorated with an allOf annotation // go over the method list again and this time collect the nullary methods and parse the comments // as if they are properties on a struct var schema *spec.Schema seenProperties := seenPreviously hasAllOf := false for _, fld := range tpe.Methods.List { if len(fld.Names) == 0 { // if this created an allOf property then we have to rejig the schema var // because all the fields collected that aren't from embedded structs should go in // their own proper schema // first process embedded structs in order of embedding if allOfMember(fld.Doc) { hasAllOf = true if schema == nil { schema = new(spec.Schema) } var newSch spec.Schema // when the embedded struct is annotated with swagger:allOf it will be used as allOf property // otherwise the fields will just be included as normal properties if err := scp.parseAllOfMember(gofile, &newSch, fld.Type, seenProperties); err != nil { return err } if fld.Doc != nil { for _, cmt := range fld.Doc.List { for _, ln := range strings.Split(cmt.Text, "\n") { matches := rxAllOf.FindStringSubmatch(ln) ml := len(matches) if ml > 1 { mv := matches[ml-1] if mv != "" { bschema.AddExtension("x-class", mv) } } } } } bschema.AllOf = append(bschema.AllOf, newSch) continue } var newSch spec.Schema // when the embedded struct is annotated with swagger:allOf it will be used as allOf property // otherwise the fields will just be included as normal properties if err := scp.parseEmbeddedType(gofile, &newSch, fld.Type, seenProperties); err != nil { return err } bschema.AllOf = append(bschema.AllOf, newSch) hasAllOf = true } } if schema == nil { schema = bschema } // then add and possibly override values if schema.Properties == nil { schema.Properties = make(map[string]spec.Schema) } schema.Typed("object", "") for _, fld := range tpe.Methods.List { if mtpe, ok := fld.Type.(*ast.FuncType); ok && mtpe.Params.NumFields() == 0 && mtpe.Results.NumFields() == 1 { gnm := fld.Names[0].Name nm := gnm if fld.Doc != nil { for _, cmt := range fld.Doc.List { for _, ln := range strings.Split(cmt.Text, "\n") { matches := rxName.FindStringSubmatch(ln) ml := len(matches) if ml > 1 { nm = matches[ml-1] } } } } ps := schema.Properties[nm] if err := parseProperty(scp, gofile, mtpe.Results.List[0].Type, schemaTypable{&ps, 0}); err != nil { return err } if err := scp.createParser(nm, schema, &ps, fld).Parse(fld.Doc); err != nil { return err } if nm != gnm { ps.AddExtension("x-go-name", gnm) } seenProperties[nm] = struct{}{} schema.Properties[nm] = ps } } if schema != nil && hasAllOf { bschema.AllOf = append(bschema.AllOf, *schema) } for k := range schema.Properties { if _, ok := seenProperties[k]; !ok { delete(schema.Properties, k) } } return nil }
func (scp *schemaParser) parseStructType(gofile *ast.File, bschema *spec.Schema, tpe *ast.StructType, seenPreviously map[string]struct{}) error { if tpe.Fields == nil { return nil } var schema *spec.Schema seenProperties := seenPreviously hasAllOf := false for _, fld := range tpe.Fields.List { if len(fld.Names) == 0 { // if this created an allOf property then we have to rejig the schema var // because all the fields collected that aren't from embedded structs should go in // their own proper schema // first process embedded structs in order of embedding if allOfMember(fld.Doc) { hasAllOf = true if schema == nil { schema = new(spec.Schema) } var newSch spec.Schema // when the embedded struct is annotated with swagger:allOf it will be used as allOf property // otherwise the fields will just be included as normal properties if err := scp.parseAllOfMember(gofile, &newSch, fld.Type, seenProperties); err != nil { return err } if fld.Doc != nil { for _, cmt := range fld.Doc.List { for _, ln := range strings.Split(cmt.Text, "\n") { matches := rxAllOf.FindStringSubmatch(ln) ml := len(matches) if ml > 1 { mv := matches[ml-1] if mv != "" { bschema.AddExtension("x-class", mv) } } } } } bschema.AllOf = append(bschema.AllOf, newSch) continue } if schema == nil { schema = bschema } // when the embedded struct is annotated with swagger:allOf it will be used as allOf property // otherwise the fields will just be included as normal properties if err := scp.parseEmbeddedType(gofile, schema, fld.Type, seenProperties); err != nil { return err } } } if schema == nil { schema = bschema } // then add and possibly override values if schema.Properties == nil { schema.Properties = make(map[string]spec.Schema) } schema.Typed("object", "") for _, fld := range tpe.Fields.List { var tag string if fld.Tag != nil { val, err := strconv.Unquote(fld.Tag.Value) if err == nil { tag = reflect.StructTag(val).Get("json") } } if len(fld.Names) > 0 && fld.Names[0] != nil && fld.Names[0].IsExported() && (tag == "" || tag[0] != '-') { var nm, gnm string nm = fld.Names[0].Name gnm = nm if fld.Tag != nil && len(strings.TrimSpace(fld.Tag.Value)) > 0 /*&& fld.Tag.Value[0] != '-'*/ { tv, err := strconv.Unquote(fld.Tag.Value) if err != nil { return err } if strings.TrimSpace(tv) != "" { st := reflect.StructTag(tv) if st.Get("json") != "" { nm = strings.Split(st.Get("json"), ",")[0] } } } ps := schema.Properties[nm] if err := parseProperty(scp, gofile, fld.Type, schemaTypable{&ps, 0}); err != nil { return err } if err := scp.createParser(nm, schema, &ps, fld).Parse(fld.Doc); err != nil { return err } if nm != gnm { ps.AddExtension("x-go-name", gnm) } seenProperties[nm] = struct{}{} schema.Properties[nm] = ps } } if schema != nil && hasAllOf { bschema.AllOf = append(bschema.AllOf, *schema) } for k := range schema.Properties { if _, ok := seenProperties[k]; !ok { delete(schema.Properties, k) } } return nil }
func TestTypeResolver_BasicTypes(t *testing.T) { _, resolver, err := basicTaskListResolver(t) if assert.NoError(t, err) { // primitives and string formats for _, val := range schTypeVals { sch := new(spec.Schema) sch.Typed(val.Type, val.Format) rt, err := resolver.ResolveSchema(sch, true, false) if assert.NoError(t, err) { assert.False(t, rt.IsNullable, "expected %s with format %q to not be nullable", val.Type, val.Format) assertPrimitiveResolve(t, val.Type, val.Format, val.Expected, rt) } } // arrays of primitives and string formats for _, val := range schTypeVals { var sch spec.Schema sch.Typed(val.Type, val.Format) rt, err := resolver.ResolveSchema(new(spec.Schema).CollectionOf(sch), true, true) if assert.NoError(t, err) { assert.True(t, rt.IsArray) } } // primitives and string formats for _, val := range schTypeVals { sch := new(spec.Schema) sch.Typed(val.Type, val.Format) sch.Extensions = make(spec.Extensions) sch.Extensions[xIsNullable] = true rt, err := resolver.ResolveSchema(sch, true, false) if assert.NoError(t, err) { assert.True(t, rt.IsNullable, "expected %q (%q) to be nullable", val.Type, val.Format) assertPrimitiveResolve(t, val.Type, val.Format, val.Expected, rt) } // Test x-nullable overrides x-isnullable sch.Extensions[xIsNullable] = false sch.Extensions[xNullable] = true rt, err = resolver.ResolveSchema(sch, true, true) if assert.NoError(t, err) { assert.True(t, rt.IsNullable, "expected %q (%q) to be nullable", val.Type, val.Format) assertPrimitiveResolve(t, val.Type, val.Format, val.Expected, rt) } // Test x-nullable without x-isnullable delete(sch.Extensions, xIsNullable) sch.Extensions[xNullable] = true rt, err = resolver.ResolveSchema(sch, true, true) if assert.NoError(t, err) { assert.True(t, rt.IsNullable, "expected %q (%q) to be nullable", val.Type, val.Format) assertPrimitiveResolve(t, val.Type, val.Format, val.Expected, rt) } } // arrays of primitives and string formats for _, val := range schTypeVals { var sch spec.Schema sch.Typed(val.Type, val.Format) sch.AddExtension(xIsNullable, true) rt, err := resolver.ResolveSchema(new(spec.Schema).CollectionOf(sch), true, true) if assert.NoError(t, err) { assert.True(t, rt.IsArray) } } } }