// 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 definition 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 *loads.Document, formats strfmt.Registry) error { errs, _ /*warns*/ := NewSpecValidator(doc.Schema(), formats).Validate(doc) if errs.HasErrors() { return errors.CompositeValidationError(errs.Errors...) } return nil }
func gatherModels(specDoc *loads.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 newTypeResolver(pkg string, doc *loads.Document) *typeResolver { resolver := typeResolver{ModelsPackage: pkg, Doc: doc} resolver.KnownDefs = make(map[string]struct{}, 64) for k, sch := range doc.OrigSpec().Definitions { resolver.KnownDefs[k] = struct{}{} if nm, ok := sch.Extensions["x-go-name"]; ok { resolver.KnownDefs[nm.(string)] = struct{}{} } } return &resolver }
// Validate validates the swagger spec func (s *SpecValidator) Validate(data interface{}) (errs *Result, warnings *Result) { var sd *loads.Document switch v := data.(type) { case *loads.Document: sd = v } if sd == nil { errs = sErr(errors.New(500, "spec validator can only validate spec.Document objects")) return } s.spec = sd s.analyzer = analysis.New(sd.Spec()) 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.validateReferenced()) // warning return }
func newDefaultRouteBuilder(spec *loads.Document, api RoutableAPI) *defaultRouteBuilder { return &defaultRouteBuilder{ spec: spec, analyzer: analysis.New(spec.Spec()), api: api, records: make(map[string][]denco.Record), } }
// NewRoutableContext creates a new context for a routable API func NewRoutableContext(spec *loads.Document, routableAPI RoutableAPI, routes Router) *Context { var an *analysis.Spec if spec != nil { an = analysis.New(spec.Spec()) } ctx := &Context{spec: spec, api: routableAPI, analyzer: an} return ctx }
func newRoutableUntypedAPI(spec *loads.Document, api *untyped.API, context *Context) *routableUntypedAPI { var handlers map[string]map[string]http.Handler if spec == nil || api == nil { return nil } analyzer := analysis.New(spec.Spec()) for method, hls := range analyzer.Operations() { um := strings.ToUpper(method) for path, op := range hls { schemes := analyzer.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) } var handler http.Handler = 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 { handler = newSecureAPI(context, handler) } handlers[um][path] = handler } } } return &routableUntypedAPI{ api: api, hlock: new(sync.Mutex), handlers: handlers, defaultProduces: api.DefaultProduces, defaultConsumes: api.DefaultConsumes, } }
// NewContext creates a new context wrapper func NewContext(spec *loads.Document, api *untyped.API, routes Router) *Context { var an *analysis.Spec if spec != nil { an = analysis.New(spec.Spec()) } ctx := &Context{spec: spec, analyzer: an} ctx.api = newRoutableUntypedAPI(spec, api, ctx) return ctx }
// NewAPI creates the default untyped API func NewAPI(spec *loads.Document) *API { var an *analysis.Spec if spec != nil && spec.Spec() != nil { an = analysis.New(spec.Spec()) } api := &API{ spec: spec, analyzer: an, consumers: make(map[string]runtime.Consumer, 10), producers: make(map[string]runtime.Producer, 10), authenticators: make(map[string]runtime.Authenticator), operations: make(map[string]map[string]runtime.OperationHandler), ServeError: errors.ServeError, Models: make(map[string]func() interface{}), formats: strfmt.NewFormats(), } return api.WithJSONDefaults() }
// NewAPI creates the default untyped API func NewAPI(spec *loads.Document) *API { var an *analysis.Spec if spec != nil && spec.Spec() != nil { an = analysis.New(spec.Spec()) } return &API{ spec: spec, analyzer: an, DefaultProduces: runtime.JSONMime, DefaultConsumes: runtime.JSONMime, consumers: map[string]runtime.Consumer{ runtime.JSONMime: runtime.JSONConsumer(), }, producers: map[string]runtime.Producer{ runtime.JSONMime: runtime.JSONProducer(), }, authenticators: make(map[string]runtime.Authenticator), operations: make(map[string]map[string]runtime.OperationHandler), ServeError: errors.ServeError, Models: make(map[string]func() interface{}), formats: strfmt.NewFormats(), } }
func appNameOrDefault(specDoc *loads.Document, name, defaultName string) string { if strings.TrimSpace(name) == "" { if specDoc.Spec().Info != nil && strings.TrimSpace(specDoc.Spec().Info.Title) != "" { name = specDoc.Spec().Info.Title } else { name = defaultName } } return strings.TrimSuffix(swag.ToGoName(name), "API") }
func makeGenDefinitionHierarchy(name, pkg, container string, schema spec.Schema, specDoc *loads.Document, includeValidator, includeModel bool) (*GenDefinition, error) { receiver := "m" resolver := newTypeResolver("", specDoc) resolver.ModelName = name analyzed := analysis.New(specDoc.Spec()) di := discriminatorInfo(analyzed) 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, Container: container, IncludeValidator: includeValidator, IncludeModel: includeModel, } 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 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 := makeGenDefinitionHierarchy(strings.TrimPrefix(ss.Ref.String(), "#/definitions/"), pkg, pg.GenSchema.Name, *rsch, specDoc, pg.IncludeValidator, pg.IncludeModel) 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-openapi/errors", "github.com/go-openapi/runtime", "github.com/go-openapi/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 }