// Spec validates a spec document // It validates the spec json against the json schema for swagger // and then validates a number of extra rules that can't be expressed in json schema: // // - definition can't declare a property that's already defined by one of its ancestors // - definition's ancestor can't be a descendant of the same model // - each api path should be non-verbatim (account for path param names) unique per method // - each security reference should contain only unique scopes // - each security scope in a security definition should be unique // - each path parameter should correspond to a parameter placeholder and vice versa // - each referencable defintion must have references // - each definition property listed in the required array must be defined in the properties of the model // - each parameter should have a unique `name` and `type` combination // - each operation should have only 1 parameter of type body // - each reference must point to a valid object // - every default value that is specified must validate against the schema for that property // - items property is required for all schemas/definitions of type `array` func Spec(doc *spec.Document, formats strfmt.Registry) error { errs, _ /*warns*/ := validate.NewSpecValidator(doc.Schema(), formats).Validate(doc) if errs.HasErrors() { return errors.CompositeValidationError(errs.Errors...) } return nil }
func gatherModels(specDoc *spec.Document, modelNames []string) (map[string]spec.Schema, error) { models, mnc := make(map[string]spec.Schema), len(modelNames) defs := specDoc.Spec().Definitions if mnc > 0 { var unknownModels []string for _, m := range modelNames { _, ok := defs[m] if !ok { unknownModels = append(unknownModels, m) } } if len(unknownModels) != 0 { return nil, fmt.Errorf("unknown models: %s", strings.Join(unknownModels, ", ")) } } for k, v := range defs { if mnc == 0 { models[k] = v } for _, nm := range modelNames { if k == nm { models[k] = v } } } return models, nil }
func makeCodegenModel(name, pkg string, schema spec.Schema, specDoc *spec.Document) *genModel { receiver := "m" props := make(map[string]genModelProperty) for pn, p := range schema.Properties { var required bool for _, v := range schema.Required { if v == pn { required = true break } } props[swag.ToJSONName(pn)] = makeGenModelProperty( "\""+pn+"\"", swag.ToJSONName(pn), swag.ToGoName(pn), receiver, "i", receiver+"."+swag.ToGoName(pn), p, required) } for _, p := range schema.AllOf { if p.Ref.GetURL() != nil { tn := filepath.Base(p.Ref.GetURL().Fragment) p = specDoc.Spec().Definitions[tn] } mod := makeCodegenModel(name, pkg, p, specDoc) if mod != nil { for _, prop := range mod.Properties { props[prop.ParamName] = prop } } } var properties []genModelProperty var hasValidations bool for _, v := range props { if v.HasValidations { hasValidations = v.HasValidations } properties = append(properties, v) } sort.Sort(genModelPropertySlice(properties)) return &genModel{ Package: filepath.Base(pkg), ClassName: swag.ToGoName(name), Name: swag.ToJSONName(name), ReceiverName: receiver, Properties: properties, Description: schema.Description, DocString: modelDocString(swag.ToGoName(name), schema.Description), HumanClassName: swag.ToHumanNameLower(swag.ToGoName(name)), DefaultImports: []string{"github.com/go-swagger/go-swagger/strfmt"}, HasValidations: hasValidations, } }
func petAPIRouterBuilder(spec *spec.Document, api *untyped.API) *defaultRouteBuilder { builder := newDefaultRouteBuilder(spec, newRoutableUntypedAPI(spec, api, new(Context))) builder.AddRoute("GET", "/pets", spec.AllPaths()["/pets"].Get) builder.AddRoute("POST", "/pets", spec.AllPaths()["/pets"].Post) builder.AddRoute("DELETE", "/pets/{id}", spec.AllPaths()["/pets/{id}"].Delete) builder.AddRoute("GET", "/pets/{id}", spec.AllPaths()["/pets/{id}"].Get) return builder }
func newRoutableUntypedAPI(spec *spec.Document, api *untyped.API, context *Context) *routableUntypedAPI { var handlers map[string]map[string]http.Handler if spec == nil || api == nil { return nil } for method, hls := range spec.Operations() { um := strings.ToUpper(method) for path, op := range hls { schemes := spec.SecurityDefinitionsFor(op) if oh, ok := api.OperationHandlerFor(method, path); ok { if handlers == nil { handlers = make(map[string]map[string]http.Handler) } if b, ok := handlers[um]; !ok || b == nil { handlers[um] = make(map[string]http.Handler) } handlers[um][path] = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // lookup route info in the context route, _ := context.RouteInfo(r) // bind and validate the request using reflection bound, validation := context.BindAndValidate(r, route) if validation != nil { context.Respond(w, r, route.Produces, route, validation) return } // actually handle the request result, err := oh.Handle(bound) if err != nil { // respond with failure context.Respond(w, r, route.Produces, route, err) return } // respond with success context.Respond(w, r, route.Produces, route, result) }) if len(schemes) > 0 { handlers[um][path] = newSecureAPI(context, handlers[um][path]) } } } } return &routableUntypedAPI{ api: api, handlers: handlers, defaultProduces: api.DefaultProduces, defaultConsumes: api.DefaultConsumes, } }
func gatherModels(specDoc *spec.Document, modelNames []string) map[string]spec.Schema { models, mnc := make(map[string]spec.Schema), len(modelNames) for k, v := range specDoc.Spec().Definitions { for _, nm := range modelNames { if mnc == 0 || k == nm { models[k] = v } } } return models }
func newTypeResolver(pkg string, doc *spec.Document) *typeResolver { resolver := typeResolver{ModelsPackage: pkg, Doc: doc} resolver.KnownDefs = make(map[string]struct{}) for k, sch := range doc.Pristine().Spec().Definitions { resolver.KnownDefs[k] = struct{}{} if nm, ok := sch.Extensions["x-go-name"]; ok { resolver.KnownDefs[nm.(string)] = struct{}{} } } return &resolver }
func AddSwaggerRoutes(doc *spec.Document) { for path, pathItem := range doc.AllPaths() { path = doc.BasePath() + deCurly(path) // TODO may be something usable here? // if extension, ok := pathItem.Extensions.GetString("x-revel-controller"); ok { } if pathItem.Head != nil { if action, ok := pathItem.Head.Extensions.GetString(X_REVEL_CONTROLLER_ACTION); ok { _fail(addRoute(action, "HEAD", path)) } } if pathItem.Get != nil { if action, ok := pathItem.Get.Extensions.GetString(X_REVEL_CONTROLLER_ACTION); ok { _fail(addRoute(action, "GET", path)) } } if pathItem.Post != nil { if action, ok := pathItem.Post.Extensions.GetString(X_REVEL_CONTROLLER_ACTION); ok { _fail(addRoute(action, "POST", path)) } } if pathItem.Put != nil { if action, ok := pathItem.Put.Extensions.GetString(X_REVEL_CONTROLLER_ACTION); ok { _fail(addRoute(action, "PUT", path)) } } if pathItem.Delete != nil { if action, ok := pathItem.Delete.Extensions.GetString(X_REVEL_CONTROLLER_ACTION); ok { _fail(addRoute(action, "DELETE", path)) } } if pathItem.Patch != nil { if action, ok := pathItem.Patch.Extensions.GetString(X_REVEL_CONTROLLER_ACTION); ok { _fail(addRoute(action, "PATCH", path)) } } if pathItem.Options != nil { if action, ok := pathItem.Options.Extensions.GetString("x-revel-controller-action"); ok { _fail(addRoute(action, "OPTIONS", path)) } } } }
func gatherOperations(specDoc *spec.Document, operationIDs []string) map[string]spec.Operation { operations := make(map[string]spec.Operation) for _, pathItem := range specDoc.Operations() { for _, operation := range pathItem { if len(operationIDs) == 0 || containsString(operationIDs, operation.ID) { nm := ensureUniqueName(operation.ID, operations) operations[nm] = *operation } } } return operations }
func discriminatorInfo(doc *spec.Document) *discInfo { baseTypes := make(map[string]discor) for _, sch := range doc.AllDefinitions() { if sch.Schema.Discriminator != "" { tpe, _ := sch.Schema.Extensions.GetString("x-go-name") if tpe == "" { tpe = swag.ToGoName(sch.Name) } baseTypes[sch.Ref.String()] = discor{ FieldName: sch.Schema.Discriminator, GoType: tpe, JSONName: sch.Name, } } } subTypes := make(map[string]discee) for _, sch := range doc.SchemasWithAllOf() { for _, ao := range sch.Schema.AllOf { if ao.Ref.String() != "" { if bt, ok := baseTypes[ao.Ref.String()]; ok { name, _ := sch.Schema.Extensions.GetString("x-class") if name == "" { name, _ = sch.Schema.Extensions.GetString("x-go-name") } if name == "" { name = swag.ToGoName(sch.Name) } tpe, _ := sch.Schema.Extensions.GetString("x-go-name") if tpe == "" { tpe = swag.ToGoName(sch.Name) } dce := discee{ FieldName: bt.FieldName, FieldValue: name, Ref: sch.Ref, ParentRef: ao.Ref, JSONName: sch.Name, GoType: tpe, } subTypes[sch.Ref.String()] = dce bt.Children = append(bt.Children, dce) baseTypes[ao.Ref.String()] = bt } } } } return &discInfo{Discriminators: baseTypes, Discriminated: subTypes} }
// Validate validates the swagger spec func (s *SpecValidator) Validate(data interface{}) (errs *Result, warnings *Result) { var sd *spec.Document switch v := data.(type) { case *spec.Document: sd = v } if sd == nil { errs = sErr(errors.New(500, "spec validator can only validate spec.Document objects")) return } s.spec = sd errs = new(Result) warnings = new(Result) schv := NewSchemaValidator(s.schema, nil, "", s.KnownFormats) var obj interface{} if err := json.Unmarshal(sd.Raw(), &obj); err != nil { errs.AddErrors(err) return } errs.Merge(schv.Validate(obj)) // error - if errs.HasErrors() { return // no point in continuing } errs.Merge(s.validateReferencesValid()) // error - if errs.HasErrors() { return // no point in continuing } errs.Merge(s.validateDuplicateOperationIDs()) errs.Merge(s.validateDuplicatePropertyNames()) // error - errs.Merge(s.validateParameters()) // error - errs.Merge(s.validateItems()) // error - errs.Merge(s.validateRequiredDefinitions()) // error - errs.Merge(s.validateDefaultValueValidAgainstSchema()) // error - errs.Merge(s.validateExamplesValidAgainstSchema()) // error - errs.Merge(s.validateNonEmptyPathParamNames()) warnings.Merge(s.validateUniqueSecurityScopes()) // warning warnings.Merge(s.validateUniqueScopesSecurityDefinitions()) // warning warnings.Merge(s.validateReferenced()) // warning return }
func gatherOperations(specDoc *spec.Document, operationIDs []string) map[string]opRef { operations := make(map[string]opRef) for method, pathItem := range specDoc.Operations() { for path, operation := range pathItem { if len(operationIDs) == 0 || containsString(operationIDs, operation.ID) { nm := ensureUniqueName(operation.ID, method, path, operations) vv := *operation vv.ID = nm operations[nm] = opRef{ Method: method, Path: path, Op: vv, } } } } return operations }
// DefaultRouter creates a default implemenation of the router func DefaultRouter(spec *spec.Document, api RoutableAPI) Router { builder := newDefaultRouteBuilder(spec, api) if spec != nil { for method, paths := range spec.Operations() { for path, operation := range paths { builder.AddRoute(method, path, operation) } } } return builder.Build() }
// New creates a new default runtime for a swagger api client. func New(swaggerSpec *spec.Document) *Runtime { var rt Runtime rt.DefaultMediaType = httpkit.JSONMime // TODO: actually infer this stuff from the spec rt.Consumers = map[string]httpkit.Consumer{ httpkit.JSONMime: httpkit.JSONConsumer(), } rt.Producers = map[string]httpkit.Producer{ httpkit.JSONMime: httpkit.JSONProducer(), } rt.Spec = swaggerSpec rt.Transport = http.DefaultTransport rt.client = http.DefaultClient rt.client.Transport = rt.Transport rt.Host = swaggerSpec.Host() rt.BasePath = swaggerSpec.BasePath() schemes := swaggerSpec.Spec().Schemes if len(schemes) == 0 { schemes = append(schemes, "http") } rt.methodsAndPaths = make(map[string]methodAndPath) for mth, pathItem := range rt.Spec.Operations() { for pth, op := range pathItem { if len(op.Schemes) > 0 { rt.methodsAndPaths[op.ID] = methodAndPath{mth, pth, op.Schemes} } else { rt.methodsAndPaths[op.ID] = methodAndPath{mth, pth, schemes} } } } return &rt }
// New creates a new default runtime for a swagger api client. func New(swaggerSpec *spec.Document) *Runtime { var rt Runtime rt.DefaultMediaType = httpkit.JSONMime rt.Consumers = map[string]httpkit.Consumer{ httpkit.JSONMime: httpkit.JSONConsumer(), } rt.Producers = map[string]httpkit.Producer{ httpkit.JSONMime: httpkit.JSONProducer(), } rt.Spec = swaggerSpec rt.Transport = http.DefaultTransport rt.client = http.DefaultClient rt.Host = swaggerSpec.Host() rt.BasePath = swaggerSpec.BasePath() rt.methodsAndPaths = make(map[string]methodAndPath) for mth, pathItem := range rt.Spec.Operations() { for pth, op := range pathItem { rt.methodsAndPaths[op.ID] = methodAndPath{mth, pth} } } return &rt }
func gatherOperations(specDoc *spec.Document, operationIDs []string) map[string]opRef { var oprefs opRefs for method, pathItem := range specDoc.Operations() { for path, operation := range pathItem { // nm := ensureUniqueName(operation.ID, method, path, operations) vv := *operation oprefs = append(oprefs, opRef{ Key: swag.ToGoName(strings.ToLower(method) + " " + path), Method: method, Path: path, ID: vv.ID, Op: &vv, }) } } sort.Sort(oprefs) operations := make(map[string]opRef) for _, opr := range oprefs { nm := opr.ID if nm == "" { nm = opr.Key } _, found := operations[nm] if found { nm = opr.Key } if len(operationIDs) == 0 || containsString(operationIDs, opr.ID) || containsString(operationIDs, nm) { opr.ID = nm opr.Op.ID = nm operations[nm] = opr } } return operations }
func gatherModels(specDoc *spec.Document, modelNames []string) (map[string]spec.Schema, error) { models, mnc := make(map[string]spec.Schema), len(modelNames) defs := specDoc.Spec().Definitions if mnc > 0 { for _, m := range modelNames { _, ok := defs[m] if !ok { return nil, errors.New("unknown model") } } } for k, v := range defs { if mnc == 0 { models[k] = v } for _, nm := range modelNames { if k == nm { models[k] = v } } } return models, nil }
// Validate validates the swagger spec func (s *SpecValidator) Validate(data interface{}) (errs *Result, warnings *Result) { var sd *spec.Document switch v := data.(type) { case *spec.Document: sd = v } if sd == nil { errs = sErr(errors.New(500, "spec validator can only validate spec.Document objects")) return } s.spec = sd errs = new(Result) warnings = new(Result) schv := NewSchemaValidator(s.schema, nil, "", s.KnownFormats) errs.Merge(schv.Validate(sd.Spec())) // error - if errs.HasErrors() { return // no point in continuing } errs.Merge(s.validateReferencesValid()) // error if errs.HasErrors() { return // no point in continuing } errs.Merge(s.validateParameters()) // error - errs.Merge(s.validateItems()) // error - errs.Merge(s.validateRequiredDefinitions()) // error - errs.Merge(s.validateDefaultValueValidAgainstSchema()) // error warnings.Merge(s.validateUniqueSecurityScopes()) // warning warnings.Merge(s.validateUniqueScopesSecurityDefinitions()) // warning warnings.Merge(s.validateReferenced()) // warning return }
func appNameOrDefault(specDoc *spec.Document, name, defaultName string) string { if name == "" { if specDoc.Spec().Info != nil && specDoc.Spec().Info.Title != "" { name = specDoc.Spec().Info.Title } else { name = defaultName } } return strings.TrimSuffix(swag.ToGoName(name), "API") }
func gatherOperations(specDoc *spec.Document, operationIDs []string) map[string]spec.Operation { operations := make(map[string]spec.Operation) if len(operationIDs) == 0 { for _, k := range specDoc.OperationIDs() { if _, _, op, ok := specDoc.OperationForName(k); ok { operations[k] = *op } } } else { for _, k := range specDoc.OperationIDs() { for _, nm := range operationIDs { if k == nm { if _, _, op, ok := specDoc.OperationForName(k); ok { operations[k] = *op } } } } } return operations }
// New creates a new default runtime for a swagger api client. func New(swaggerSpec *spec.Document) *Runtime { var rt Runtime rt.DefaultMediaType = httpkit.JSONMime // TODO: actually infer this stuff from the spec rt.Consumers = map[string]httpkit.Consumer{ httpkit.JSONMime: httpkit.JSONConsumer(), } rt.Producers = map[string]httpkit.Producer{ httpkit.JSONMime: httpkit.JSONProducer(), } rt.Spec = swaggerSpec rt.Transport = http.DefaultTransport rt.client = http.DefaultClient rt.client.Transport = rt.Transport rt.Host = swaggerSpec.Host() rt.BasePath = swaggerSpec.BasePath() if !strings.HasPrefix(rt.BasePath, "/") { rt.BasePath = "/" + rt.BasePath } rt.Debug = os.Getenv("DEBUG") == "1" schemes := swaggerSpec.Spec().Schemes if len(schemes) == 0 { schemes = append(schemes, "http") } rt.methodsAndPaths = make(map[string]methodAndPath) for mth, pathItem := range rt.Spec.Operations() { for pth, op := range pathItem { nm := ensureUniqueName(op.ID, mth, pth, rt.methodsAndPaths) op.ID = nm if len(op.Schemes) > 0 { rt.methodsAndPaths[nm] = methodAndPath{mth, pth, op.Schemes} } else { rt.methodsAndPaths[nm] = methodAndPath{mth, pth, schemes} } } } return &rt }
func makeGenDefinition(name, pkg string, schema spec.Schema, specDoc *spec.Document) (*GenDefinition, error) { receiver := "m" resolver := &typeResolver{ ModelsPackage: "", ModelName: name, Doc: specDoc, } di := discriminatorInfo(specDoc) pg := schemaGenContext{ Path: "", Name: name, Receiver: receiver, IndexVar: "i", ValueExpr: receiver, Schema: schema, Required: false, TypeResolver: resolver, Named: true, ExtraSchemas: make(map[string]GenSchema), Discrimination: di, } if err := pg.makeGenSchema(); err != nil { return nil, err } dsi, ok := di.Discriminators["#/definitions/"+name] if ok { // when these 2 are true then the schema will render as an interface pg.GenSchema.IsBaseType = true pg.GenSchema.IsExported = true pg.GenSchema.DiscriminatorField = dsi.FieldName // clone schema and turn into IsExported false //tpeImpl := newDiscriminatorImpl(pg.GenSchema) //pg.ExtraSchemas[tpeImpl.Name] = tpeImpl for _, v := range dsi.Children { if pg.GenSchema.Discriminates == nil { pg.GenSchema.Discriminates = make(map[string]string) } pg.GenSchema.Discriminates[v.FieldValue] = v.GoType } } dse, ok := di.Discriminated["#/definitions/"+name] if ok { pg.GenSchema.DiscriminatorField = dse.FieldName pg.GenSchema.DiscriminatorValue = dse.FieldValue pg.GenSchema.IsSubType = true // find the referenced definitions // check if it has a discriminator defined // when it has a discriminator get the schema and run makeGenSchema for it. // replace the ref with this new genschema swsp := specDoc.Spec() for i, ss := range schema.AllOf { ref := ss.Ref for ref.String() != "" { rsch, err := spec.ResolveRef(swsp, &ref) if err != nil { return nil, err } ref = rsch.Ref if rsch != nil && rsch.Ref.String() != "" { ref = rsch.Ref continue } ref = spec.Ref{} if rsch != nil && rsch.Discriminator != "" { gs, err := makeGenDefinition(strings.TrimPrefix(ss.Ref.String(), "#/definitions/"), pkg, *rsch, specDoc) if err != nil { return nil, err } gs.GenSchema.IsBaseType = true gs.GenSchema.IsExported = true pg.GenSchema.AllOf[i] = gs.GenSchema schPtr := &(pg.GenSchema.AllOf[i]) if schPtr.AdditionalItems != nil { schPtr.AdditionalItems.IsBaseType = true } if schPtr.AdditionalProperties != nil { schPtr.AdditionalProperties.IsBaseType = true } for j := range schPtr.Properties { schPtr.Properties[j].IsBaseType = true schPtr.Properties[j].ValueExpression += "()" } } } } } var defaultImports []string if pg.GenSchema.HasValidations { defaultImports = []string{ "github.com/go-swagger/go-swagger/errors", "github.com/go-swagger/go-swagger/strfmt", "github.com/go-swagger/go-swagger/httpkit/validate", } } var extras []GenSchema var extraKeys []string for k := range pg.ExtraSchemas { extraKeys = append(extraKeys, k) } sort.Strings(extraKeys) for _, k := range extraKeys { extras = append(extras, pg.ExtraSchemas[k]) } return &GenDefinition{ Package: mangleName(filepath.Base(pkg), "definitions"), GenSchema: pg.GenSchema, DependsOn: pg.Dependencies, DefaultImports: defaultImports, ExtraSchemas: extras, }, nil }