* Use vendored go-swagger * vendor go-swagger * revert un wanteed change * remove un-needed GO111MODULE * Update Makefile Co-Authored-By: techknowlogick <matti@mdranta.net>
1507 lines
33 KiB
Go
1507 lines
33 KiB
Go
package codescan
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"go/ast"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/go-openapi/loads/fmts"
|
|
"github.com/go-openapi/spec"
|
|
"github.com/pkg/errors"
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
func shouldAcceptTag(tags []string, includeTags map[string]bool, excludeTags map[string]bool) bool {
|
|
for _, tag := range tags {
|
|
if len(includeTags) > 0 {
|
|
if includeTags[tag] {
|
|
return true
|
|
}
|
|
} else if len(excludeTags) > 0 {
|
|
if excludeTags[tag] {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return len(includeTags) == 0
|
|
}
|
|
|
|
func shouldAcceptPkg(path string, includePkgs, excludePkgs []string) bool {
|
|
if len(includePkgs) == 0 && len(excludePkgs) == 0 {
|
|
return true
|
|
}
|
|
for _, pkgName := range includePkgs {
|
|
matched, _ := regexp.MatchString(pkgName, path)
|
|
if matched {
|
|
return true
|
|
}
|
|
}
|
|
for _, pkgName := range excludePkgs {
|
|
matched, _ := regexp.MatchString(pkgName, path)
|
|
if matched {
|
|
return false
|
|
}
|
|
}
|
|
return len(includePkgs) == 0
|
|
}
|
|
|
|
// Many thanks go to https://github.com/yvasiyarov/swagger
|
|
// this is loosely based on that implementation but for swagger 2.0
|
|
|
|
func joinDropLast(lines []string) string {
|
|
l := len(lines)
|
|
lns := lines
|
|
if l > 0 && len(strings.TrimSpace(lines[l-1])) == 0 {
|
|
lns = lines[:l-1]
|
|
}
|
|
return strings.Join(lns, "\n")
|
|
}
|
|
|
|
func removeEmptyLines(lines []string) (notEmpty []string) {
|
|
for _, l := range lines {
|
|
if len(strings.TrimSpace(l)) > 0 {
|
|
notEmpty = append(notEmpty, l)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func rxf(rxp, ar string) *regexp.Regexp {
|
|
return regexp.MustCompile(fmt.Sprintf(rxp, ar))
|
|
}
|
|
|
|
func allOfMember(comments *ast.CommentGroup) bool {
|
|
if comments != nil {
|
|
for _, cmt := range comments.List {
|
|
for _, ln := range strings.Split(cmt.Text, "\n") {
|
|
if rxAllOf.MatchString(ln) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func fileParam(comments *ast.CommentGroup) bool {
|
|
if comments != nil {
|
|
for _, cmt := range comments.List {
|
|
for _, ln := range strings.Split(cmt.Text, "\n") {
|
|
if rxFileUpload.MatchString(ln) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func strfmtName(comments *ast.CommentGroup) (string, bool) {
|
|
if comments != nil {
|
|
for _, cmt := range comments.List {
|
|
for _, ln := range strings.Split(cmt.Text, "\n") {
|
|
matches := rxStrFmt.FindStringSubmatch(ln)
|
|
if len(matches) > 1 && len(strings.TrimSpace(matches[1])) > 0 {
|
|
return strings.TrimSpace(matches[1]), true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func ignored(comments *ast.CommentGroup) bool {
|
|
if comments != nil {
|
|
for _, cmt := range comments.List {
|
|
for _, ln := range strings.Split(cmt.Text, "\n") {
|
|
if rxIgnoreOverride.MatchString(ln) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func enumName(comments *ast.CommentGroup) (string, bool) {
|
|
if comments != nil {
|
|
for _, cmt := range comments.List {
|
|
for _, ln := range strings.Split(cmt.Text, "\n") {
|
|
matches := rxEnum.FindStringSubmatch(ln)
|
|
if len(matches) > 1 && len(strings.TrimSpace(matches[1])) > 0 {
|
|
return strings.TrimSpace(matches[1]), true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func aliasParam(comments *ast.CommentGroup) bool {
|
|
if comments != nil {
|
|
for _, cmt := range comments.List {
|
|
for _, ln := range strings.Split(cmt.Text, "\n") {
|
|
if rxAlias.MatchString(ln) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isAliasParam(prop swaggerTypable) bool {
|
|
var isParam bool
|
|
if param, ok := prop.(paramTypable); ok {
|
|
isParam = param.param.In == "query" ||
|
|
param.param.In == "path" ||
|
|
param.param.In == "formData"
|
|
}
|
|
return isParam
|
|
}
|
|
|
|
func defaultName(comments *ast.CommentGroup) (string, bool) {
|
|
if comments != nil {
|
|
for _, cmt := range comments.List {
|
|
for _, ln := range strings.Split(cmt.Text, "\n") {
|
|
matches := rxDefault.FindStringSubmatch(ln)
|
|
if len(matches) > 1 && len(strings.TrimSpace(matches[1])) > 0 {
|
|
return strings.TrimSpace(matches[1]), true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func typeName(comments *ast.CommentGroup) (string, bool) {
|
|
var typ string
|
|
if comments != nil {
|
|
for _, cmt := range comments.List {
|
|
for _, ln := range strings.Split(cmt.Text, "\n") {
|
|
matches := rxType.FindStringSubmatch(ln)
|
|
if len(matches) > 1 && len(strings.TrimSpace(matches[1])) > 0 {
|
|
typ = strings.TrimSpace(matches[1])
|
|
return typ, true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
type swaggerTypable interface {
|
|
Typed(string, string)
|
|
SetRef(spec.Ref)
|
|
Items() swaggerTypable
|
|
Schema() *spec.Schema
|
|
Level() int
|
|
AddExtension(key string, value interface{})
|
|
}
|
|
|
|
// Map all Go builtin types that have Json representation to Swagger/Json types.
|
|
// See https://golang.org/pkg/builtin/ and http://swagger.io/specification/
|
|
func swaggerSchemaForType(typeName string, prop swaggerTypable) error {
|
|
switch typeName {
|
|
case "bool":
|
|
prop.Typed("boolean", "")
|
|
case "byte":
|
|
prop.Typed("integer", "uint8")
|
|
case "complex128", "complex64":
|
|
return fmt.Errorf("unsupported builtin %q (no JSON marshaller)", typeName)
|
|
case "error":
|
|
// TODO: error is often marshalled into a string but not always (e.g. errors package creates
|
|
// errors that are marshalled into an empty object), this could be handled the same way
|
|
// custom JSON marshallers are handled (in future)
|
|
prop.Typed("string", "")
|
|
case "float32":
|
|
prop.Typed("number", "float")
|
|
case "float64":
|
|
prop.Typed("number", "double")
|
|
case "int":
|
|
prop.Typed("integer", "int64")
|
|
case "int16":
|
|
prop.Typed("integer", "int16")
|
|
case "int32":
|
|
prop.Typed("integer", "int32")
|
|
case "int64":
|
|
prop.Typed("integer", "int64")
|
|
case "int8":
|
|
prop.Typed("integer", "int8")
|
|
case "rune":
|
|
prop.Typed("integer", "int32")
|
|
case "string":
|
|
prop.Typed("string", "")
|
|
case "uint":
|
|
prop.Typed("integer", "uint64")
|
|
case "uint16":
|
|
prop.Typed("integer", "uint16")
|
|
case "uint32":
|
|
prop.Typed("integer", "uint32")
|
|
case "uint64":
|
|
prop.Typed("integer", "uint64")
|
|
case "uint8":
|
|
prop.Typed("integer", "uint8")
|
|
case "uintptr":
|
|
prop.Typed("integer", "uint64")
|
|
default:
|
|
return fmt.Errorf("unsupported type %q", typeName)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func newMultiLineTagParser(name string, parser valueParser, skipCleanUp bool) tagParser {
|
|
return tagParser{
|
|
Name: name,
|
|
MultiLine: true,
|
|
SkipCleanUp: skipCleanUp,
|
|
Parser: parser,
|
|
}
|
|
}
|
|
|
|
func newSingleLineTagParser(name string, parser valueParser) tagParser {
|
|
return tagParser{
|
|
Name: name,
|
|
MultiLine: false,
|
|
SkipCleanUp: false,
|
|
Parser: parser,
|
|
}
|
|
}
|
|
|
|
type tagParser struct {
|
|
Name string
|
|
MultiLine bool
|
|
SkipCleanUp bool
|
|
Lines []string
|
|
Parser valueParser
|
|
}
|
|
|
|
func (st *tagParser) Matches(line string) bool {
|
|
return st.Parser.Matches(line)
|
|
}
|
|
|
|
func (st *tagParser) Parse(lines []string) error {
|
|
return st.Parser.Parse(lines)
|
|
}
|
|
|
|
func newYamlParser(rx *regexp.Regexp, setter func(json.RawMessage) error) valueParser {
|
|
return &yamlParser{
|
|
set: setter,
|
|
rx: rx,
|
|
}
|
|
}
|
|
|
|
type yamlParser struct {
|
|
set func(json.RawMessage) error
|
|
rx *regexp.Regexp
|
|
}
|
|
|
|
func (y *yamlParser) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
|
|
var uncommented []string
|
|
uncommented = append(uncommented, removeYamlIndent(lines)...)
|
|
|
|
yamlContent := strings.Join(uncommented, "\n")
|
|
var yamlValue interface{}
|
|
err := yaml.Unmarshal([]byte(yamlContent), &yamlValue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var jsonValue json.RawMessage
|
|
jsonValue, err = fmts.YAMLToJSON(yamlValue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return y.set(jsonValue)
|
|
}
|
|
|
|
func (y *yamlParser) Matches(line string) bool {
|
|
return y.rx.MatchString(line)
|
|
}
|
|
|
|
// aggregates lines in header until it sees `---`,
|
|
// the beginning of a YAML spec
|
|
type yamlSpecScanner struct {
|
|
header []string
|
|
yamlSpec []string
|
|
setTitle func([]string)
|
|
setDescription func([]string)
|
|
workedOutTitle bool
|
|
title []string
|
|
skipHeader bool
|
|
}
|
|
|
|
func cleanupScannerLines(lines []string, ur *regexp.Regexp, yamlBlock *regexp.Regexp) []string {
|
|
// bail early when there is nothing to parse
|
|
if len(lines) == 0 {
|
|
return lines
|
|
}
|
|
seenLine := -1
|
|
var lastContent int
|
|
var uncommented []string
|
|
var startBlock bool
|
|
var yamlLines []string
|
|
for i, v := range lines {
|
|
if yamlBlock != nil && yamlBlock.MatchString(v) && !startBlock {
|
|
startBlock = true
|
|
if seenLine < 0 {
|
|
seenLine = i
|
|
}
|
|
continue
|
|
}
|
|
if startBlock {
|
|
if yamlBlock != nil && yamlBlock.MatchString(v) {
|
|
startBlock = false
|
|
uncommented = append(uncommented, removeIndent(yamlLines)...)
|
|
continue
|
|
}
|
|
yamlLines = append(yamlLines, v)
|
|
if v != "" {
|
|
if seenLine < 0 {
|
|
seenLine = i
|
|
}
|
|
lastContent = i
|
|
}
|
|
continue
|
|
}
|
|
str := ur.ReplaceAllString(v, "")
|
|
uncommented = append(uncommented, str)
|
|
if str != "" {
|
|
if seenLine < 0 {
|
|
seenLine = i
|
|
}
|
|
lastContent = i
|
|
}
|
|
}
|
|
|
|
// fixes issue #50
|
|
if seenLine == -1 {
|
|
return nil
|
|
}
|
|
return uncommented[seenLine : lastContent+1]
|
|
}
|
|
|
|
// a shared function that can be used to split given headers
|
|
// into a title and description
|
|
func collectScannerTitleDescription(headers []string) (title, desc []string) {
|
|
hdrs := cleanupScannerLines(headers, rxUncommentHeaders, nil)
|
|
|
|
idx := -1
|
|
for i, line := range hdrs {
|
|
if strings.TrimSpace(line) == "" {
|
|
idx = i
|
|
break
|
|
}
|
|
}
|
|
|
|
if idx > -1 {
|
|
title = hdrs[:idx]
|
|
if len(hdrs) > idx+1 {
|
|
desc = hdrs[idx+1:]
|
|
} else {
|
|
desc = nil
|
|
}
|
|
return
|
|
}
|
|
|
|
if len(hdrs) > 0 {
|
|
line := hdrs[0]
|
|
if rxPunctuationEnd.MatchString(line) {
|
|
title = []string{line}
|
|
desc = hdrs[1:]
|
|
} else {
|
|
desc = hdrs
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (sp *yamlSpecScanner) collectTitleDescription() {
|
|
if sp.workedOutTitle {
|
|
return
|
|
}
|
|
if sp.setTitle == nil {
|
|
sp.header = cleanupScannerLines(sp.header, rxUncommentHeaders, nil)
|
|
return
|
|
}
|
|
|
|
sp.workedOutTitle = true
|
|
sp.title, sp.header = collectScannerTitleDescription(sp.header)
|
|
}
|
|
|
|
func (sp *yamlSpecScanner) Title() []string {
|
|
sp.collectTitleDescription()
|
|
return sp.title
|
|
}
|
|
|
|
func (sp *yamlSpecScanner) Description() []string {
|
|
sp.collectTitleDescription()
|
|
return sp.header
|
|
}
|
|
|
|
func (sp *yamlSpecScanner) Parse(doc *ast.CommentGroup) error {
|
|
if doc == nil {
|
|
return nil
|
|
}
|
|
var startedYAMLSpec bool
|
|
COMMENTS:
|
|
for _, c := range doc.List {
|
|
for _, line := range strings.Split(c.Text, "\n") {
|
|
if rxSwaggerAnnotation.MatchString(line) {
|
|
break COMMENTS // a new swagger: annotation terminates this parser
|
|
}
|
|
|
|
if !startedYAMLSpec {
|
|
if rxBeginYAMLSpec.MatchString(line) {
|
|
startedYAMLSpec = true
|
|
sp.yamlSpec = append(sp.yamlSpec, line)
|
|
continue
|
|
}
|
|
|
|
if !sp.skipHeader {
|
|
sp.header = append(sp.header, line)
|
|
}
|
|
|
|
// no YAML spec yet, moving on
|
|
continue
|
|
}
|
|
|
|
sp.yamlSpec = append(sp.yamlSpec, line)
|
|
}
|
|
}
|
|
if sp.setTitle != nil {
|
|
sp.setTitle(sp.Title())
|
|
}
|
|
if sp.setDescription != nil {
|
|
sp.setDescription(sp.Description())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (sp *yamlSpecScanner) UnmarshalSpec(u func([]byte) error) (err error) {
|
|
specYaml := cleanupScannerLines(sp.yamlSpec, rxUncommentYAML, nil)
|
|
if len(specYaml) == 0 {
|
|
return errors.New("no spec available to unmarshal")
|
|
}
|
|
|
|
if !strings.Contains(specYaml[0], "---") {
|
|
return errors.New("yaml spec has to start with `---`")
|
|
}
|
|
|
|
// remove indentation
|
|
specYaml = removeIndent(specYaml)
|
|
|
|
// 1. parse yaml lines
|
|
yamlValue := make(map[interface{}]interface{})
|
|
|
|
yamlContent := strings.Join(specYaml, "\n")
|
|
err = yaml.Unmarshal([]byte(yamlContent), &yamlValue)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// 2. convert to json
|
|
var jsonValue json.RawMessage
|
|
jsonValue, err = fmts.YAMLToJSON(yamlValue)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// 3. unmarshal the json into an interface
|
|
var data []byte
|
|
data, err = jsonValue.MarshalJSON()
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = u(data)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// all parsed, returning...
|
|
sp.yamlSpec = nil // spec is now consumed, so let's erase the parsed lines
|
|
return
|
|
}
|
|
|
|
// removes indent base on the first line
|
|
func removeIndent(spec []string) []string {
|
|
loc := rxIndent.FindStringIndex(spec[0])
|
|
if loc[1] == 0 {
|
|
return spec
|
|
}
|
|
for i := range spec {
|
|
if len(spec[i]) >= loc[1] {
|
|
spec[i] = spec[i][loc[1]-1:]
|
|
}
|
|
}
|
|
return spec
|
|
}
|
|
|
|
// removes indent base on the first line
|
|
func removeYamlIndent(spec []string) []string {
|
|
loc := rxIndent.FindStringIndex(spec[0])
|
|
if loc[1] == 0 {
|
|
return nil
|
|
}
|
|
var s []string
|
|
for i := range spec {
|
|
if len(spec[i]) >= loc[1] {
|
|
s = append(s, spec[i][loc[1]-1:])
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
// aggregates lines in header until it sees a tag.
|
|
type sectionedParser struct {
|
|
header []string
|
|
matched map[string]tagParser
|
|
annotation valueParser
|
|
|
|
seenTag bool
|
|
skipHeader bool
|
|
setTitle func([]string)
|
|
setDescription func([]string)
|
|
workedOutTitle bool
|
|
taggers []tagParser
|
|
currentTagger *tagParser
|
|
title []string
|
|
ignored bool
|
|
}
|
|
|
|
func (st *sectionedParser) collectTitleDescription() {
|
|
if st.workedOutTitle {
|
|
return
|
|
}
|
|
if st.setTitle == nil {
|
|
st.header = cleanupScannerLines(st.header, rxUncommentHeaders, nil)
|
|
return
|
|
}
|
|
|
|
st.workedOutTitle = true
|
|
st.title, st.header = collectScannerTitleDescription(st.header)
|
|
}
|
|
|
|
func (st *sectionedParser) Title() []string {
|
|
st.collectTitleDescription()
|
|
return st.title
|
|
}
|
|
|
|
func (st *sectionedParser) Description() []string {
|
|
st.collectTitleDescription()
|
|
return st.header
|
|
}
|
|
|
|
func (st *sectionedParser) Parse(doc *ast.CommentGroup) error {
|
|
if doc == nil {
|
|
return nil
|
|
}
|
|
COMMENTS:
|
|
for _, c := range doc.List {
|
|
for _, line := range strings.Split(c.Text, "\n") {
|
|
if rxSwaggerAnnotation.MatchString(line) {
|
|
if rxIgnoreOverride.MatchString(line) {
|
|
st.ignored = true
|
|
break COMMENTS // an explicit ignore terminates this parser
|
|
}
|
|
if st.annotation == nil || !st.annotation.Matches(line) {
|
|
break COMMENTS // a new swagger: annotation terminates this parser
|
|
}
|
|
|
|
_ = st.annotation.Parse([]string{line})
|
|
if len(st.header) > 0 {
|
|
st.seenTag = true
|
|
}
|
|
continue
|
|
}
|
|
|
|
var matched bool
|
|
for _, tagger := range st.taggers {
|
|
if tagger.Matches(line) {
|
|
st.seenTag = true
|
|
st.currentTagger = &tagger
|
|
matched = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if st.currentTagger == nil {
|
|
if !st.skipHeader && !st.seenTag {
|
|
st.header = append(st.header, line)
|
|
}
|
|
// didn't match a tag, moving on
|
|
continue
|
|
}
|
|
|
|
if st.currentTagger.MultiLine && matched {
|
|
// the first line of a multiline tagger doesn't count
|
|
continue
|
|
}
|
|
|
|
ts, ok := st.matched[st.currentTagger.Name]
|
|
if !ok {
|
|
ts = *st.currentTagger
|
|
}
|
|
ts.Lines = append(ts.Lines, line)
|
|
if st.matched == nil {
|
|
st.matched = make(map[string]tagParser)
|
|
}
|
|
st.matched[st.currentTagger.Name] = ts
|
|
|
|
if !st.currentTagger.MultiLine {
|
|
st.currentTagger = nil
|
|
}
|
|
}
|
|
}
|
|
if st.setTitle != nil {
|
|
st.setTitle(st.Title())
|
|
}
|
|
if st.setDescription != nil {
|
|
st.setDescription(st.Description())
|
|
}
|
|
for _, mt := range st.matched {
|
|
if !mt.SkipCleanUp {
|
|
mt.Lines = cleanupScannerLines(mt.Lines, rxUncommentHeaders, nil)
|
|
}
|
|
if err := mt.Parse(mt.Lines); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type validationBuilder interface {
|
|
SetMaximum(float64, bool)
|
|
SetMinimum(float64, bool)
|
|
SetMultipleOf(float64)
|
|
|
|
SetMinItems(int64)
|
|
SetMaxItems(int64)
|
|
|
|
SetMinLength(int64)
|
|
SetMaxLength(int64)
|
|
SetPattern(string)
|
|
|
|
SetUnique(bool)
|
|
SetEnum(string)
|
|
SetDefault(interface{})
|
|
SetExample(interface{})
|
|
}
|
|
|
|
type valueParser interface {
|
|
Parse([]string) error
|
|
Matches(string) bool
|
|
}
|
|
|
|
type operationValidationBuilder interface {
|
|
validationBuilder
|
|
SetCollectionFormat(string)
|
|
}
|
|
|
|
type setMaximum struct {
|
|
builder validationBuilder
|
|
rx *regexp.Regexp
|
|
}
|
|
|
|
func (sm *setMaximum) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
matches := sm.rx.FindStringSubmatch(lines[0])
|
|
if len(matches) > 2 && len(matches[2]) > 0 {
|
|
max, err := strconv.ParseFloat(matches[2], 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sm.builder.SetMaximum(max, matches[1] == "<")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (sm *setMaximum) Matches(line string) bool {
|
|
return sm.rx.MatchString(line)
|
|
}
|
|
|
|
type setMinimum struct {
|
|
builder validationBuilder
|
|
rx *regexp.Regexp
|
|
}
|
|
|
|
func (sm *setMinimum) Matches(line string) bool {
|
|
return sm.rx.MatchString(line)
|
|
}
|
|
|
|
func (sm *setMinimum) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
matches := sm.rx.FindStringSubmatch(lines[0])
|
|
if len(matches) > 2 && len(matches[2]) > 0 {
|
|
min, err := strconv.ParseFloat(matches[2], 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sm.builder.SetMinimum(min, matches[1] == ">")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type setMultipleOf struct {
|
|
builder validationBuilder
|
|
rx *regexp.Regexp
|
|
}
|
|
|
|
func (sm *setMultipleOf) Matches(line string) bool {
|
|
return sm.rx.MatchString(line)
|
|
}
|
|
|
|
func (sm *setMultipleOf) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
matches := sm.rx.FindStringSubmatch(lines[0])
|
|
if len(matches) > 2 && len(matches[1]) > 0 {
|
|
multipleOf, err := strconv.ParseFloat(matches[1], 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sm.builder.SetMultipleOf(multipleOf)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type setMaxItems struct {
|
|
builder validationBuilder
|
|
rx *regexp.Regexp
|
|
}
|
|
|
|
func (sm *setMaxItems) Matches(line string) bool {
|
|
return sm.rx.MatchString(line)
|
|
}
|
|
|
|
func (sm *setMaxItems) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
matches := sm.rx.FindStringSubmatch(lines[0])
|
|
if len(matches) > 1 && len(matches[1]) > 0 {
|
|
maxItems, err := strconv.ParseInt(matches[1], 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sm.builder.SetMaxItems(maxItems)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type setMinItems struct {
|
|
builder validationBuilder
|
|
rx *regexp.Regexp
|
|
}
|
|
|
|
func (sm *setMinItems) Matches(line string) bool {
|
|
return sm.rx.MatchString(line)
|
|
}
|
|
|
|
func (sm *setMinItems) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
matches := sm.rx.FindStringSubmatch(lines[0])
|
|
if len(matches) > 1 && len(matches[1]) > 0 {
|
|
minItems, err := strconv.ParseInt(matches[1], 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sm.builder.SetMinItems(minItems)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type setMaxLength struct {
|
|
builder validationBuilder
|
|
rx *regexp.Regexp
|
|
}
|
|
|
|
func (sm *setMaxLength) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
matches := sm.rx.FindStringSubmatch(lines[0])
|
|
if len(matches) > 1 && len(matches[1]) > 0 {
|
|
maxLength, err := strconv.ParseInt(matches[1], 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sm.builder.SetMaxLength(maxLength)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (sm *setMaxLength) Matches(line string) bool {
|
|
return sm.rx.MatchString(line)
|
|
}
|
|
|
|
type setMinLength struct {
|
|
builder validationBuilder
|
|
rx *regexp.Regexp
|
|
}
|
|
|
|
func (sm *setMinLength) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
matches := sm.rx.FindStringSubmatch(lines[0])
|
|
if len(matches) > 1 && len(matches[1]) > 0 {
|
|
minLength, err := strconv.ParseInt(matches[1], 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sm.builder.SetMinLength(minLength)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (sm *setMinLength) Matches(line string) bool {
|
|
return sm.rx.MatchString(line)
|
|
}
|
|
|
|
type setPattern struct {
|
|
builder validationBuilder
|
|
rx *regexp.Regexp
|
|
}
|
|
|
|
func (sm *setPattern) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
matches := sm.rx.FindStringSubmatch(lines[0])
|
|
if len(matches) > 1 && len(matches[1]) > 0 {
|
|
sm.builder.SetPattern(matches[1])
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (sm *setPattern) Matches(line string) bool {
|
|
return sm.rx.MatchString(line)
|
|
}
|
|
|
|
type setCollectionFormat struct {
|
|
builder operationValidationBuilder
|
|
rx *regexp.Regexp
|
|
}
|
|
|
|
func (sm *setCollectionFormat) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
matches := sm.rx.FindStringSubmatch(lines[0])
|
|
if len(matches) > 1 && len(matches[1]) > 0 {
|
|
sm.builder.SetCollectionFormat(matches[1])
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (sm *setCollectionFormat) Matches(line string) bool {
|
|
return sm.rx.MatchString(line)
|
|
}
|
|
|
|
type setUnique struct {
|
|
builder validationBuilder
|
|
rx *regexp.Regexp
|
|
}
|
|
|
|
func (su *setUnique) Matches(line string) bool {
|
|
return su.rx.MatchString(line)
|
|
}
|
|
|
|
func (su *setUnique) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
matches := su.rx.FindStringSubmatch(lines[0])
|
|
if len(matches) > 1 && len(matches[1]) > 0 {
|
|
req, err := strconv.ParseBool(matches[1])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
su.builder.SetUnique(req)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type setEnum struct {
|
|
builder validationBuilder
|
|
rx *regexp.Regexp
|
|
}
|
|
|
|
func (se *setEnum) Matches(line string) bool {
|
|
return se.rx.MatchString(line)
|
|
}
|
|
|
|
func (se *setEnum) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
matches := se.rx.FindStringSubmatch(lines[0])
|
|
if len(matches) > 1 && len(matches[1]) > 0 {
|
|
se.builder.SetEnum(matches[1])
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseValueFromSchema(s string, schema *spec.SimpleSchema) (interface{}, error) {
|
|
if schema != nil {
|
|
switch strings.Trim(schema.TypeName(), "\"") {
|
|
case "integer", "int", "int64", "int32", "int16":
|
|
return strconv.Atoi(s)
|
|
case "bool", "boolean":
|
|
return strconv.ParseBool(s)
|
|
case "number", "float64", "float32":
|
|
return strconv.ParseFloat(s, 64)
|
|
case "object":
|
|
var obj map[string]interface{}
|
|
if err := json.Unmarshal([]byte(s), &obj); err != nil {
|
|
// If we can't parse it, just return the string.
|
|
return s, nil
|
|
}
|
|
return obj, nil
|
|
case "array":
|
|
var slice []interface{}
|
|
if err := json.Unmarshal([]byte(s), &slice); err != nil {
|
|
// If we can't parse it, just return the string.
|
|
return s, nil
|
|
}
|
|
return slice, nil
|
|
default:
|
|
return s, nil
|
|
}
|
|
} else {
|
|
return s, nil
|
|
}
|
|
}
|
|
|
|
type setDefault struct {
|
|
scheme *spec.SimpleSchema
|
|
builder validationBuilder
|
|
rx *regexp.Regexp
|
|
}
|
|
|
|
func (sd *setDefault) Matches(line string) bool {
|
|
return sd.rx.MatchString(line)
|
|
}
|
|
|
|
func (sd *setDefault) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
matches := sd.rx.FindStringSubmatch(lines[0])
|
|
if len(matches) > 1 && len(matches[1]) > 0 {
|
|
d, err := parseValueFromSchema(matches[1], sd.scheme)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sd.builder.SetDefault(d)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type setExample struct {
|
|
scheme *spec.SimpleSchema
|
|
builder validationBuilder
|
|
rx *regexp.Regexp
|
|
}
|
|
|
|
func (se *setExample) Matches(line string) bool {
|
|
return se.rx.MatchString(line)
|
|
}
|
|
|
|
func (se *setExample) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
matches := se.rx.FindStringSubmatch(lines[0])
|
|
if len(matches) > 1 && len(matches[1]) > 0 {
|
|
d, err := parseValueFromSchema(matches[1], se.scheme)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
se.builder.SetExample(d)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type matchOnlyParam struct {
|
|
tgt *spec.Parameter
|
|
rx *regexp.Regexp
|
|
}
|
|
|
|
func (mo *matchOnlyParam) Matches(line string) bool {
|
|
return mo.rx.MatchString(line)
|
|
}
|
|
|
|
func (mo *matchOnlyParam) Parse(lines []string) error {
|
|
return nil
|
|
}
|
|
|
|
type setRequiredParam struct {
|
|
tgt *spec.Parameter
|
|
}
|
|
|
|
func (su *setRequiredParam) Matches(line string) bool {
|
|
return rxRequired.MatchString(line)
|
|
}
|
|
|
|
func (su *setRequiredParam) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
matches := rxRequired.FindStringSubmatch(lines[0])
|
|
if len(matches) > 1 && len(matches[1]) > 0 {
|
|
req, err := strconv.ParseBool(matches[1])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
su.tgt.Required = req
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type setReadOnlySchema struct {
|
|
tgt *spec.Schema
|
|
}
|
|
|
|
func (su *setReadOnlySchema) Matches(line string) bool {
|
|
return rxReadOnly.MatchString(line)
|
|
}
|
|
|
|
func (su *setReadOnlySchema) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
matches := rxReadOnly.FindStringSubmatch(lines[0])
|
|
if len(matches) > 1 && len(matches[1]) > 0 {
|
|
req, err := strconv.ParseBool(matches[1])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
su.tgt.ReadOnly = req
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type setDeprecatedOp struct {
|
|
tgt *spec.Operation
|
|
}
|
|
|
|
func (su *setDeprecatedOp) Matches(line string) bool {
|
|
return rxDeprecated.MatchString(line)
|
|
}
|
|
|
|
func (su *setDeprecatedOp) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
matches := rxDeprecated.FindStringSubmatch(lines[0])
|
|
if len(matches) > 1 && len(matches[1]) > 0 {
|
|
req, err := strconv.ParseBool(matches[1])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
su.tgt.Deprecated = req
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type setDiscriminator struct {
|
|
schema *spec.Schema
|
|
field string
|
|
}
|
|
|
|
func (su *setDiscriminator) Matches(line string) bool {
|
|
return rxDiscriminator.MatchString(line)
|
|
}
|
|
|
|
func (su *setDiscriminator) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
matches := rxDiscriminator.FindStringSubmatch(lines[0])
|
|
if len(matches) > 1 && len(matches[1]) > 0 {
|
|
req, err := strconv.ParseBool(matches[1])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if req {
|
|
su.schema.Discriminator = su.field
|
|
} else if su.schema.Discriminator == su.field {
|
|
su.schema.Discriminator = ""
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type setRequiredSchema struct {
|
|
schema *spec.Schema
|
|
field string
|
|
}
|
|
|
|
func (su *setRequiredSchema) Matches(line string) bool {
|
|
return rxRequired.MatchString(line)
|
|
}
|
|
|
|
func (su *setRequiredSchema) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
matches := rxRequired.FindStringSubmatch(lines[0])
|
|
if len(matches) > 1 && len(matches[1]) > 0 {
|
|
req, err := strconv.ParseBool(matches[1])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
midx := -1
|
|
for i, nm := range su.schema.Required {
|
|
if nm == su.field {
|
|
midx = i
|
|
break
|
|
}
|
|
}
|
|
if req {
|
|
if midx < 0 {
|
|
su.schema.Required = append(su.schema.Required, su.field)
|
|
}
|
|
} else if midx >= 0 {
|
|
su.schema.Required = append(su.schema.Required[:midx], su.schema.Required[midx+1:]...)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func newMultilineDropEmptyParser(rx *regexp.Regexp, set func([]string)) *multiLineDropEmptyParser {
|
|
return &multiLineDropEmptyParser{
|
|
rx: rx,
|
|
set: set,
|
|
}
|
|
}
|
|
|
|
type multiLineDropEmptyParser struct {
|
|
set func([]string)
|
|
rx *regexp.Regexp
|
|
}
|
|
|
|
func (m *multiLineDropEmptyParser) Matches(line string) bool {
|
|
return m.rx.MatchString(line)
|
|
}
|
|
|
|
func (m *multiLineDropEmptyParser) Parse(lines []string) error {
|
|
m.set(removeEmptyLines(lines))
|
|
return nil
|
|
}
|
|
|
|
func newSetSchemes(set func([]string)) *setSchemes {
|
|
return &setSchemes{
|
|
set: set,
|
|
rx: rxSchemes,
|
|
}
|
|
}
|
|
|
|
type setSchemes struct {
|
|
set func([]string)
|
|
rx *regexp.Regexp
|
|
}
|
|
|
|
func (ss *setSchemes) Matches(line string) bool {
|
|
return ss.rx.MatchString(line)
|
|
}
|
|
|
|
func (ss *setSchemes) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
matches := ss.rx.FindStringSubmatch(lines[0])
|
|
if len(matches) > 1 && len(matches[1]) > 0 {
|
|
sch := strings.Split(matches[1], ", ")
|
|
|
|
schemes := []string{}
|
|
for _, s := range sch {
|
|
ts := strings.TrimSpace(s)
|
|
if ts != "" {
|
|
schemes = append(schemes, ts)
|
|
}
|
|
}
|
|
ss.set(schemes)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func newSetSecurity(rx *regexp.Regexp, setter func([]map[string][]string)) *setSecurity {
|
|
return &setSecurity{
|
|
set: setter,
|
|
rx: rx,
|
|
}
|
|
}
|
|
|
|
type setSecurity struct {
|
|
set func([]map[string][]string)
|
|
rx *regexp.Regexp
|
|
}
|
|
|
|
func (ss *setSecurity) Matches(line string) bool {
|
|
return ss.rx.MatchString(line)
|
|
}
|
|
|
|
func (ss *setSecurity) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
|
|
var result []map[string][]string
|
|
for _, line := range lines {
|
|
kv := strings.SplitN(line, ":", 2)
|
|
scopes := []string{}
|
|
var key string
|
|
|
|
if len(kv) > 1 {
|
|
scs := strings.Split(kv[1], ",")
|
|
for _, scope := range scs {
|
|
tr := strings.TrimSpace(scope)
|
|
if tr != "" {
|
|
tr = strings.SplitAfter(tr, " ")[0]
|
|
scopes = append(scopes, strings.TrimSpace(tr))
|
|
}
|
|
}
|
|
|
|
key = strings.TrimSpace(kv[0])
|
|
|
|
result = append(result, map[string][]string{key: scopes})
|
|
}
|
|
}
|
|
ss.set(result)
|
|
return nil
|
|
}
|
|
|
|
func newSetResponses(definitions map[string]spec.Schema, responses map[string]spec.Response, setter func(*spec.Response, map[int]spec.Response)) *setOpResponses {
|
|
return &setOpResponses{
|
|
set: setter,
|
|
rx: rxResponses,
|
|
definitions: definitions,
|
|
responses: responses,
|
|
}
|
|
}
|
|
|
|
type setOpResponses struct {
|
|
set func(*spec.Response, map[int]spec.Response)
|
|
rx *regexp.Regexp
|
|
definitions map[string]spec.Schema
|
|
responses map[string]spec.Response
|
|
}
|
|
|
|
func (ss *setOpResponses) Matches(line string) bool {
|
|
return ss.rx.MatchString(line)
|
|
}
|
|
|
|
//ResponseTag used when specifying a response to point to a defined swagger:response
|
|
const ResponseTag = "response"
|
|
|
|
//BodyTag used when specifying a response to point to a model/schema
|
|
const BodyTag = "body"
|
|
|
|
//DescriptionTag used when specifying a response that gives a description of the response
|
|
const DescriptionTag = "description"
|
|
|
|
func parseTags(line string) (modelOrResponse string, arrays int, isDefinitionRef bool, description string, err error) {
|
|
tags := strings.Split(line, " ")
|
|
parsedModelOrResponse := false
|
|
|
|
for i, tagAndValue := range tags {
|
|
tagValList := strings.SplitN(tagAndValue, ":", 2)
|
|
var tag, value string
|
|
if len(tagValList) > 1 {
|
|
tag = tagValList[0]
|
|
value = tagValList[1]
|
|
} else {
|
|
//TODO: Print a warning, and in the long term, do not support not tagged values
|
|
//Add a default tag if none is supplied
|
|
if i == 0 {
|
|
tag = ResponseTag
|
|
} else {
|
|
tag = DescriptionTag
|
|
}
|
|
value = tagValList[0]
|
|
}
|
|
|
|
foundModelOrResponse := false
|
|
if !parsedModelOrResponse {
|
|
if tag == BodyTag {
|
|
foundModelOrResponse = true
|
|
isDefinitionRef = true
|
|
}
|
|
if tag == ResponseTag {
|
|
foundModelOrResponse = true
|
|
isDefinitionRef = false
|
|
}
|
|
}
|
|
if foundModelOrResponse {
|
|
//Read the model or response tag
|
|
parsedModelOrResponse = true
|
|
//Check for nested arrays
|
|
arrays = 0
|
|
for strings.HasPrefix(value, "[]") {
|
|
arrays++
|
|
value = value[2:]
|
|
}
|
|
//What's left over is the model name
|
|
modelOrResponse = value
|
|
} else {
|
|
foundDescription := false
|
|
if tag == DescriptionTag {
|
|
foundDescription = true
|
|
}
|
|
if foundDescription {
|
|
//Descriptions are special, they make they read the rest of the line
|
|
descriptionWords := []string{value}
|
|
if i < len(tags)-1 {
|
|
descriptionWords = append(descriptionWords, tags[i+1:]...)
|
|
}
|
|
description = strings.Join(descriptionWords, " ")
|
|
break
|
|
} else {
|
|
if tag == ResponseTag || tag == BodyTag || tag == DescriptionTag {
|
|
err = fmt.Errorf("valid tag %s, but not in a valid position", tag)
|
|
} else {
|
|
err = fmt.Errorf("invalid tag: %s", tag)
|
|
}
|
|
//return error
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
//TODO: Maybe do, if !parsedModelOrResponse {return some error}
|
|
return
|
|
}
|
|
|
|
func (ss *setOpResponses) Parse(lines []string) error {
|
|
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
|
|
return nil
|
|
}
|
|
|
|
var def *spec.Response
|
|
var scr map[int]spec.Response
|
|
|
|
for _, line := range lines {
|
|
kv := strings.SplitN(line, ":", 2)
|
|
var key, value string
|
|
|
|
if len(kv) > 1 {
|
|
key = strings.TrimSpace(kv[0])
|
|
if key == "" {
|
|
// this must be some weird empty line
|
|
continue
|
|
}
|
|
value = strings.TrimSpace(kv[1])
|
|
if value == "" {
|
|
var resp spec.Response
|
|
if strings.EqualFold("default", key) {
|
|
if def == nil {
|
|
def = &resp
|
|
}
|
|
} else {
|
|
if sc, err := strconv.Atoi(key); err == nil {
|
|
if scr == nil {
|
|
scr = make(map[int]spec.Response)
|
|
}
|
|
scr[sc] = resp
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
refTarget, arrays, isDefinitionRef, description, err := parseTags(value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
//A possible exception for having a definition
|
|
if _, ok := ss.responses[refTarget]; !ok {
|
|
if _, ok := ss.definitions[refTarget]; ok {
|
|
isDefinitionRef = true
|
|
}
|
|
}
|
|
|
|
var ref spec.Ref
|
|
if isDefinitionRef {
|
|
if description == "" {
|
|
description = refTarget
|
|
}
|
|
ref, err = spec.NewRef("#/definitions/" + refTarget)
|
|
} else {
|
|
ref, err = spec.NewRef("#/responses/" + refTarget)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// description should used on anyway.
|
|
resp := spec.Response{ResponseProps: spec.ResponseProps{Description: description}}
|
|
|
|
if isDefinitionRef {
|
|
resp.Schema = new(spec.Schema)
|
|
resp.Description = description
|
|
if arrays == 0 {
|
|
resp.Schema.Ref = ref
|
|
} else {
|
|
cs := resp.Schema
|
|
for i := 0; i < arrays; i++ {
|
|
cs.Typed("array", "")
|
|
cs.Items = new(spec.SchemaOrArray)
|
|
cs.Items.Schema = new(spec.Schema)
|
|
cs = cs.Items.Schema
|
|
}
|
|
cs.Ref = ref
|
|
}
|
|
// ref. could be empty while use description tag
|
|
} else if len(refTarget) > 0 {
|
|
resp.Ref = ref
|
|
}
|
|
|
|
if strings.EqualFold("default", key) {
|
|
if def == nil {
|
|
def = &resp
|
|
}
|
|
} else {
|
|
if sc, err := strconv.Atoi(key); err == nil {
|
|
if scr == nil {
|
|
scr = make(map[int]spec.Response)
|
|
}
|
|
scr[sc] = resp
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ss.set(def, scr)
|
|
return nil
|
|
}
|
|
|
|
func parseEnum(val string, s *spec.SimpleSchema) []interface{} {
|
|
list := strings.Split(val, ",")
|
|
interfaceSlice := make([]interface{}, len(list))
|
|
for i, d := range list {
|
|
v, err := parseValueFromSchema(d, s)
|
|
if err != nil {
|
|
interfaceSlice[i] = d
|
|
continue
|
|
}
|
|
|
|
interfaceSlice[i] = v
|
|
}
|
|
return interfaceSlice
|
|
}
|