func (rp *responseParser) parseStructType(gofile *ast.File, response *spec.Response, tpe *ast.StructType, seenPreviously map[string]struct{}) error { if tpe.Fields != nil { seenProperties := seenPreviously for _, fld := range tpe.Fields.List { if len(fld.Names) == 0 { // 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 := rp.parseEmbeddedStruct(gofile, response, fld.Type, seenProperties); err != nil { return err } } } for _, fld := range tpe.Fields.List { var nm string if len(fld.Names) > 0 && fld.Names[0] != nil && fld.Names[0].IsExported() { nm = fld.Names[0].Name if fld.Tag != nil && len(strings.TrimSpace(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] } } } var in string // scan for param location first, this changes some behavior down the line if fld.Doc != nil { for _, cmt := range fld.Doc.List { for _, line := range strings.Split(cmt.Text, "\n") { matches := rxIn.FindStringSubmatch(line) if len(matches) > 0 && len(strings.TrimSpace(matches[1])) > 0 { in = strings.TrimSpace(matches[1]) } } } } ps := response.Headers[nm] if err := parseProperty(rp.scp, gofile, fld.Type, responseTypable{in, &ps, response}); err != nil { return err } sp := new(sectionedParser) sp.setDescription = func(lines []string) { ps.Description = joinDropLast(lines) } sp.taggers = []tagParser{ newSingleLineTagParser("maximum", &setMaximum{headerValidations{&ps}, rxf(rxMaximumFmt, "")}), newSingleLineTagParser("minimum", &setMinimum{headerValidations{&ps}, rxf(rxMinimumFmt, "")}), newSingleLineTagParser("multipleOf", &setMultipleOf{headerValidations{&ps}, rxf(rxMultipleOfFmt, "")}), newSingleLineTagParser("minLength", &setMinLength{headerValidations{&ps}, rxf(rxMinLengthFmt, "")}), newSingleLineTagParser("maxLength", &setMaxLength{headerValidations{&ps}, rxf(rxMaxLengthFmt, "")}), newSingleLineTagParser("pattern", &setPattern{headerValidations{&ps}, rxf(rxPatternFmt, "")}), newSingleLineTagParser("collectionFormat", &setCollectionFormat{headerValidations{&ps}, rxf(rxCollectionFormatFmt, "")}), newSingleLineTagParser("minItems", &setMinItems{headerValidations{&ps}, rxf(rxMinItemsFmt, "")}), newSingleLineTagParser("maxItems", &setMaxItems{headerValidations{&ps}, rxf(rxMaxItemsFmt, "")}), newSingleLineTagParser("unique", &setUnique{headerValidations{&ps}, rxf(rxUniqueFmt, "")}), } itemsTaggers := func() []tagParser { return []tagParser{ newSingleLineTagParser("itemsMaximum", &setMaximum{itemsValidations{ps.Items}, rxf(rxMaximumFmt, rxItemsPrefix)}), newSingleLineTagParser("itemsMinimum", &setMinimum{itemsValidations{ps.Items}, rxf(rxMinimumFmt, rxItemsPrefix)}), newSingleLineTagParser("itemsMultipleOf", &setMultipleOf{itemsValidations{ps.Items}, rxf(rxMultipleOfFmt, rxItemsPrefix)}), newSingleLineTagParser("itemsMinLength", &setMinLength{itemsValidations{ps.Items}, rxf(rxMinLengthFmt, rxItemsPrefix)}), newSingleLineTagParser("itemsMaxLength", &setMaxLength{itemsValidations{ps.Items}, rxf(rxMaxLengthFmt, rxItemsPrefix)}), newSingleLineTagParser("itemsPattern", &setPattern{itemsValidations{ps.Items}, rxf(rxPatternFmt, rxItemsPrefix)}), newSingleLineTagParser("itemsCollectionFormat", &setCollectionFormat{itemsValidations{ps.Items}, rxf(rxCollectionFormatFmt, rxItemsPrefix)}), newSingleLineTagParser("itemsMinItems", &setMinItems{itemsValidations{ps.Items}, rxf(rxMinItemsFmt, rxItemsPrefix)}), newSingleLineTagParser("itemsMaxItems", &setMaxItems{itemsValidations{ps.Items}, rxf(rxMaxItemsFmt, rxItemsPrefix)}), newSingleLineTagParser("itemsUnique", &setUnique{itemsValidations{ps.Items}, rxf(rxUniqueFmt, rxItemsPrefix)}), } } // check if this is a primitive, if so parse the validations from the // doc comments of the slice declaration. if ftpe, ok := fld.Type.(*ast.ArrayType); ok { if iftpe, ok := ftpe.Elt.(*ast.Ident); ok && iftpe.Obj == nil { if ps.Items != nil { // items matchers should go before the default matchers so they match first sp.taggers = append(itemsTaggers(), sp.taggers...) } } } if err := sp.Parse(fld.Doc); err != nil { return err } if in != "body" { seenProperties[nm] = struct{}{} if response.Headers == nil { response.Headers = make(map[string]spec.Header) } response.Headers[nm] = ps } } } for k := range response.Headers { if _, ok := seenProperties[k]; !ok { delete(response.Headers, k) } } } return nil }