// Validate tests whether the resource definition is consistent: action names are valid and each action is // valid. func (r *ResourceDefinition) Validate(version *APIVersionDefinition) *dslengine.ValidationErrors { verr := new(dslengine.ValidationErrors) if r.Name == "" { verr.Add(r, "Resource name cannot be empty") } r.validateActions(version, verr) if r.BaseParams != nil { r.validateBaseParams(verr) } if r.ParentName != "" { r.validateParent(verr) } for _, resp := range r.Responses { verr.Merge(resp.Validate()) } if r.Params != nil { verr.Merge(r.Params.Validate("resource parameters", r)) } if !r.SupportsNoVersion() { if err := dslengine.CanUse(r, Design); err != nil { verr.Add(r, "Invalid API version in list") } } return verr.AsError() }
// Validate checks that the link definition is consistent: it has a media type or the name of an // attribute part of the parent media type. func (l *LinkDefinition) Validate() *dslengine.ValidationErrors { verr := new(dslengine.ValidationErrors) if l.Name == "" { verr.Add(l, "Links must have a name") } if l.Parent == nil { verr.Add(l, "Link must have a parent media type") } if l.Parent.ToObject() == nil { verr.Add(l, "Link parent media type must be an Object") } att, ok := l.Parent.ToObject()[l.Name] if !ok { verr.Add(l, "Link name must match one of the parent media type attribute names") } else { mediaType, ok := att.Type.(*MediaTypeDefinition) if !ok { verr.Add(l, "attribute type must be a media type") } else { viewFound := false view := l.View for v := range mediaType.Views { if v == view { viewFound = true break } } if !viewFound { verr.Add(l, "view %#v does not exist on target media type %#v", view, mediaType.Identifier) } } } return verr.AsError() }
// Validate tests whether the action definition is consistent: parameters have unique names and it has at least // one response. func (a *ActionDefinition) Validate(version *APIVersionDefinition) *dslengine.ValidationErrors { verr := new(dslengine.ValidationErrors) if a.Name == "" { verr.Add(a, "Action name cannot be empty") } if len(a.Routes) == 0 { verr.Add(a, "No route defined for action") } for i, r := range a.Responses { for j, r2 := range a.Responses { if i != j && r.Status == r2.Status { verr.Add(r, "Multiple response definitions with status code %d", r.Status) } } verr.Merge(r.Validate()) } verr.Merge(a.ValidateParams(version)) if a.Payload != nil { verr.Merge(a.Payload.Validate("action payload", a)) } if a.Parent == nil { verr.Add(a, "missing parent resource") } return verr.AsError() }
// Validate checks that the user type definition is consistent: it has a name and all user and media // types used in fields support the API versions that use the type. func (u *UserTypeDefinition) Validate(ctx string, parent dslengine.Definition) *dslengine.ValidationErrors { verr := new(dslengine.ValidationErrors) if u.TypeName == "" { verr.Add(parent, "%s - %s", ctx, "User type must have a name") } if u.Type != nil { // u.Type can be nil when types refer to each other. if o := u.ToObject(); o != nil { o.IterateAttributes(func(name string, at *AttributeDefinition) error { var ut *UserTypeDefinition if mt, ok := at.Type.(*MediaTypeDefinition); ok { ut = mt.UserTypeDefinition } else if ut2, ok := at.Type.(*UserTypeDefinition); ok { ut = ut2 } if ut != nil { if err := dslengine.CanUse(u, ut); err != nil { verr.Add(u, err.Error()) } } return nil }) } } verr.Merge(u.AttributeDefinition.Validate(ctx, parent)) return verr.AsError() }
// Validate checks that the route definition is consistent: it has a parent. func (r *RouteDefinition) Validate() *dslengine.ValidationErrors { verr := new(dslengine.ValidationErrors) if r.Parent == nil { verr.Add(r, "missing route parent action") } return verr.AsError() }
// Validate checks that the user type definition is consistent: it has a name and the attribute // backing the type is valid. func (u *UserTypeDefinition) Validate(ctx string, parent dslengine.Definition) *dslengine.ValidationErrors { verr := new(dslengine.ValidationErrors) if u.TypeName == "" { verr.Add(parent, "%s - %s", ctx, "User type must have a name") } verr.Merge(u.AttributeDefinition.Validate(ctx, u)) return verr.AsError() }
// Validate checks that the view definition is consistent: it has a parent media type and the // underlying definition type is consistent. func (v *ViewDefinition) Validate() *dslengine.ValidationErrors { verr := new(dslengine.ValidationErrors) if v.Parent == nil { verr.Add(v, "View must have a parent media type") } verr.Merge(v.AttributeDefinition.Validate("", v)) return verr.AsError() }
// Validate checks that the response definition is consistent: its status is set and the media // type definition if any is valid. func (r *ResponseDefinition) Validate() *dslengine.ValidationErrors { verr := new(dslengine.ValidationErrors) if r.Headers != nil { verr.Merge(r.Headers.Validate("response headers", r)) } if r.Status == 0 { verr.Add(r, "response status not defined") } return verr.AsError() }
// Validate tests whether the attribute definition is consistent: required fields exist. // Since attributes are unaware of their context, additional context information can be provided // to be used in error messages. // The parent definition context is automatically added to error messages. func (a *AttributeDefinition) Validate(ctx string, parent dslengine.Definition) *dslengine.ValidationErrors { if validated[a] { return nil } validated[a] = true verr := new(dslengine.ValidationErrors) if a.Type == nil { verr.Add(parent, "attribute type is nil") return verr } if ctx != "" { ctx += " - " } // If both Default and Enum are given, make sure the Default value is one of Enum values. // TODO: We only do the default value and enum check just for primitive types. // Issue 388 (https://github.com/goadesign/goa/issues/388) will address this for other types. if a.Type.IsPrimitive() && a.DefaultValue != nil && a.Validation != nil && a.Validation.Values != nil { var found bool for _, e := range a.Validation.Values { if e == a.DefaultValue { found = true break } } if !found { verr.Add(parent, "%sdefault value %#v is not one of the accepted values: %#v", ctx, a.DefaultValue, a.Validation.Values) } } o := a.Type.ToObject() if o != nil { for _, n := range a.AllRequired() { found := false for an := range o { if n == an { found = true break } } if !found { verr.Add(parent, `%srequired field "%s" does not exist`, ctx, n) } } for n, att := range o { ctx = fmt.Sprintf("field %s", n) verr.Merge(att.Validate(ctx, parent)) } } else { if a.Type.IsArray() { elemType := a.Type.ToArray().ElemType verr.Merge(elemType.Validate(ctx, a)) } } return verr.AsError() }
// Validate checks that the media type definition is consistent: its identifier is a valid media // type identifier. func (m *MediaTypeDefinition) Validate() *dslengine.ValidationErrors { verr := new(dslengine.ValidationErrors) verr.Merge(m.UserTypeDefinition.Validate("", m)) if m.Type == nil { // TBD move this to somewhere else than validation code m.Type = String } var obj Object if a := m.Type.ToArray(); a != nil { if a.ElemType == nil { verr.Add(m, "array element type is nil") } else { if err := a.ElemType.Validate("array element", m); err != nil { verr.Merge(err) } else { if _, ok := a.ElemType.Type.(*MediaTypeDefinition); !ok { verr.Add(m, "collection media type array element type must be a media type, got %s", a.ElemType.Type.Name()) } else { obj = a.ElemType.Type.ToObject() } } } } else { obj = m.Type.ToObject() } if obj != nil { for n, att := range obj { verr.Merge(att.Validate("attribute "+n, m)) if att.View != "" { cmt, ok := att.Type.(*MediaTypeDefinition) if !ok { verr.Add(m, "attribute %s of media type defines a view for rendering but its type is not MediaTypeDefinition", n) } if _, ok := cmt.Views[att.View]; !ok { verr.Add(m, "attribute %s of media type uses unknown view %#v", n, att.View) } } } } if !m.Type.IsArray() { hasDefaultView := false for n, v := range m.Views { if n == "default" { hasDefaultView = true } verr.Merge(v.Validate()) } if !hasDefaultView { verr.Add(m, `media type does not define the default view, use View("default", ...) to define it.`) } } for _, l := range m.Links { verr.Merge(l.Validate()) } return verr.AsError() }
// Validate tests whether the RelationalField definition is consistent. func (field *RelationalFieldDefinition) Validate() *dslengine.ValidationErrors { fmt.Println("Validing Field") verr := new(dslengine.ValidationErrors) if field.Parent == nil { verr.Add(field, "missing relational model parent") } if field.Name == "" { verr.Add(field, "field name not defined") } return verr.AsError() }
// Validate tests whether the StorageGroup definition is consistent. func (a *StorageGroupDefinition) Validate() *dslengine.ValidationErrors { fmt.Println("Validating Group") verr := new(dslengine.ValidationErrors) if a.Name == "" { verr.Add(a, "storage group name not defined") } a.IterateStores(func(store *RelationalStoreDefinition) error { verr.Merge(store.Validate()) return nil }) return verr.AsError() }
// Validate tests whether the RelationalStore definition is consistent. func (a *RelationalStoreDefinition) Validate() *dslengine.ValidationErrors { fmt.Println("Validating Store") verr := new(dslengine.ValidationErrors) if a.Name == "" { verr.Add(a, "store name not defined") } if a.Parent == nil { verr.Add(a, "missing storage group parent") } a.IterateModels(func(model *RelationalModelDefinition) error { verr.Merge(model.Validate()) return nil }) return verr.AsError() }
// Validate tests whether the RelationalModel definition is consistent. func (a *RelationalModelDefinition) Validate() *dslengine.ValidationErrors { fmt.Println("Validating Model") verr := new(dslengine.ValidationErrors) if a.Name == "" { verr.Add(a, "model name not defined") } if a.Parent == nil { verr.Add(a, "missing relational store parent") } a.IterateFields(func(field *RelationalFieldDefinition) error { verr.Merge(field.Validate()) return nil }) return verr.AsError() }
// ValidateParams checks the action parameters (make sure they have names, members and types). func (a *ActionDefinition) ValidateParams() *dslengine.ValidationErrors { verr := new(dslengine.ValidationErrors) if a.Params == nil { return nil } params, ok := a.Params.Type.(Object) if !ok { verr.Add(a, `"Params" field of action is not an object`) } var wcs []string for _, r := range a.Routes { rwcs := ExtractWildcards(r.FullPath()) for _, rwc := range rwcs { found := false for _, wc := range wcs { if rwc == wc { found = true break } } if !found { wcs = append(wcs, rwc) } } } for n, p := range params { if n == "" { verr.Add(a, "action has parameter with no name") } else if p == nil { verr.Add(a, "definition of parameter %s cannot be nil", n) } else if p.Type == nil { verr.Add(a, "type of parameter %s cannot be nil", n) } if p.Type.Kind() == ObjectKind { verr.Add(a, `parameter %s cannot be an object, only action payloads may be of type object`, n) } else if p.Type.Kind() == HashKind { verr.Add(a, `parameter %s cannot be a hash, only action payloads may be of type hash`, n) } ctx := fmt.Sprintf("parameter %s", n) verr.Merge(p.Validate(ctx, a)) } for _, resp := range a.Responses { verr.Merge(resp.Validate()) } return verr.AsError() }
// Validate tests whether the attribute definition is consistent: required fields exist. // Since attributes are unaware of their context, additional context information can be provided // to be used in error messages. // The parent definition context is automatically added to error messages. func (a *AttributeDefinition) Validate(ctx string, parent dslengine.Definition) *dslengine.ValidationErrors { verr := new(dslengine.ValidationErrors) if a.Type == nil { verr.Add(parent, "attribute type is nil") return verr } if ctx != "" { ctx += " - " } o, isObject := a.Type.(Object) for _, v := range a.Validations { if r, ok := v.(*dslengine.RequiredValidationDefinition); ok { if !isObject { verr.Add(parent, `%sonly objects may define a "Required" validation`, ctx) } for _, n := range r.Names { var found bool for an := range o { if n == an { found = true break } } if !found { verr.Add(parent, `%srequired field "%s" does not exist`, ctx, n) } } } } if isObject { for n, att := range o { ctx = fmt.Sprintf("field %s", n) verr.Merge(att.Validate(ctx, a)) } } else { if a.Type.IsArray() { elemType := a.Type.ToArray().ElemType verr.Merge(elemType.Validate(ctx, a)) } } return verr.AsError() }
// Validate tests whether the resource definition is consistent: action names are valid and each action is // valid. func (r *ResourceDefinition) Validate() *dslengine.ValidationErrors { verr := new(dslengine.ValidationErrors) if r.Name == "" { verr.Add(r, "Resource name cannot be empty") } r.validateActions(verr) if r.ParentName != "" { r.validateParent(verr) } for _, resp := range r.Responses { verr.Merge(resp.Validate()) } if r.Params != nil { verr.Merge(r.Params.Validate("resource parameters", r)) } for _, origin := range r.Origins { verr.Merge(origin.Validate()) } return verr.AsError() }
// Validate tests whether the attribute definition is consistent: required fields exist. // Since attributes are unaware of their context, additional context information can be provided // to be used in error messages. // The parent definition context is automatically added to error messages. func (a *AttributeDefinition) Validate(ctx string, parent dslengine.Definition) *dslengine.ValidationErrors { if validated[a] { return nil } validated[a] = true verr := new(dslengine.ValidationErrors) if a.Type == nil { verr.Add(parent, "attribute type is nil") return verr } if ctx != "" { ctx += " - " } o := a.Type.ToObject() if o != nil { for _, n := range a.AllRequired() { found := false for an := range o { if n == an { found = true break } } if !found { verr.Add(parent, `%srequired field "%s" does not exist`, ctx, n) } } for n, att := range o { ctx = fmt.Sprintf("field %s", n) verr.Merge(att.Validate(ctx, a)) } } else { if a.Type.IsArray() { elemType := a.Type.ToArray().ElemType verr.Merge(elemType.Validate(ctx, a)) } } return verr.AsError() }
// Validate tests whether the action definition is consistent: parameters have unique names and it has at least // one response. func (a *ActionDefinition) Validate() *dslengine.ValidationErrors { verr := new(dslengine.ValidationErrors) if a.Name == "" { verr.Add(a, "Action name cannot be empty") } if len(a.Routes) == 0 { verr.Add(a, "No route defined for action") } for i, r := range a.Responses { for j, r2 := range a.Responses { if i != j && r.Status == r2.Status { verr.Add(r, "Multiple response definitions with status code %d", r.Status) } } verr.Merge(r.Validate()) } verr.Merge(a.ValidateParams()) if a.Payload != nil { verr.Merge(a.Payload.Validate("action payload", a)) } if a.Parent == nil { verr.Add(a, "missing parent resource") } if a.Params != nil { for n, p := range a.Params.Type.ToObject() { if p.Type.IsPrimitive() { continue } if p.Type.IsArray() { if p.Type.ToArray().ElemType.Type.IsPrimitive() { continue } } verr.Add(a, "Param %s has an invalid type, action params must be primitives or arrays of primitives", n) } } return verr.AsError() }
// Validate checks the file server is properly initialized. func (f *FileServerDefinition) Validate() *dslengine.ValidationErrors { verr := new(dslengine.ValidationErrors) if f.FilePath == "" { verr.Add(f, "File server must have a non empty file path") } if f.RequestPath == "" { verr.Add(f, "File server must have a non empty route path") } if f.Parent == nil { verr.Add(f, "missing parent resource") } matches := WildcardRegex.FindAllString(f.RequestPath, -1) if len(matches) == 1 { if !strings.HasSuffix(f.RequestPath, matches[0]) { verr.Add(f, "invalid request path %s, must end with a wildcard starting with *", f.RequestPath) } } if len(matches) > 2 { verr.Add(f, "invalid request path, may only contain one wildcard") } return verr.AsError() }
// Validate tests whether the API definition is consistent: all resource parent names resolve to // an actual resource. func (a *APIDefinition) Validate() *dslengine.ValidationErrors { verr := new(dslengine.ValidationErrors) if a.BaseParams != nil { verr.Merge(a.BaseParams.Validate("base parameters", a)) } a.validateContact(verr) a.validateLicense(verr) a.validateDocs(verr) a.IterateVersions(func(ver *APIVersionDefinition) error { var allRoutes []*routeInfo a.IterateResources(func(r *ResourceDefinition) error { verr.Merge(r.Validate(ver)) r.IterateActions(func(ac *ActionDefinition) error { if ac.Docs != nil && ac.Docs.URL != "" { if _, err := url.ParseRequestURI(ac.Docs.URL); err != nil { verr.Add(ac, "invalid action docs URL value: %s", err) } } for _, ro := range ac.Routes { info := newRouteInfo(ver, r, ac, ro) allRoutes = append(allRoutes, info) rwcs := ExtractWildcards(ac.Parent.FullPath(ver)) wcs := ExtractWildcards(ro.Path) for _, rwc := range rwcs { for _, wc := range wcs { if rwc == wc { verr.Add(ac, `duplicate wildcard "%s" in resource base path "%s" and action route "%s"`, wc, ac.Parent.FullPath(ver), ro.Path) } } } } return nil }) return nil }) for _, route := range allRoutes { for _, other := range allRoutes { if route == other { continue } if strings.HasPrefix(route.Key, other.Key) { diffs := route.DifferentWildcards(other) if len(diffs) > 0 { var msg string conflicts := make([]string, len(diffs)) for i, d := range diffs { conflicts[i] = fmt.Sprintf(`"%s" from %s and "%s" from %s`, d[0].Name, d[0].Orig.Context(), d[1].Name, d[1].Orig.Context()) } msg = fmt.Sprintf("%s", strings.Join(conflicts, ", ")) verr.Add(route.Action, `route "%s" conflicts with route "%s" of %s action %s. Make sure wildcards at the same positions have the same name. Conflicting wildcards are %s.`, route.Route.FullPath(ver), other.Route.FullPath(ver), other.Resource.Name, other.Action.Name, msg, ) } } } } ver.IterateMediaTypes(func(mt *MediaTypeDefinition) error { verr.Merge(mt.Validate()) return nil }) ver.IterateUserTypes(func(t *UserTypeDefinition) error { verr.Merge(t.Validate("", a)) return nil }) ver.IterateResponses(func(r *ResponseDefinition) error { verr.Merge(r.Validate()) return nil }) for _, dec := range ver.Consumes { verr.Merge(dec.Validate()) } for _, enc := range ver.Produces { verr.Merge(enc.Validate()) } return nil }) return verr.AsError() }
// Validate tests whether the API definition is consistent: all resource parent names resolve to // an actual resource. func (a *APIDefinition) Validate() error { // This is a little bit hacky but we need the generated media types DSLs to run first so // that their views are defined otherwise we risk running into validation errors where an // attribute defined on a non generated media type uses a generated mediatype (i.e. // CollectionOf(Foo)) with a specific view that hasn't been set yet. // TBD: Maybe GeneratedMediaTypes should not be a separate DSL root. for _, mt := range GeneratedMediaTypes { dslengine.Execute(mt.DSLFunc, mt) mt.DSLFunc = nil // So that it doesn't run again when the generated media types DSL root is executed } verr := new(dslengine.ValidationErrors) if a.Params != nil { verr.Merge(a.Params.Validate("base parameters", a)) } a.validateContact(verr) a.validateLicense(verr) a.validateDocs(verr) a.validateOrigins(verr) var allRoutes []*routeInfo a.IterateResources(func(r *ResourceDefinition) error { verr.Merge(r.Validate()) r.IterateActions(func(ac *ActionDefinition) error { if ac.Docs != nil && ac.Docs.URL != "" { if _, err := url.ParseRequestURI(ac.Docs.URL); err != nil { verr.Add(ac, "invalid action docs URL value: %s", err) } } for _, ro := range ac.Routes { if ro.IsAbsolute() { continue } info := newRouteInfo(r, ac, ro) allRoutes = append(allRoutes, info) rwcs := ExtractWildcards(ac.Parent.FullPath()) wcs := ExtractWildcards(ro.Path) for _, rwc := range rwcs { for _, wc := range wcs { if rwc == wc { verr.Add(ac, `duplicate wildcard "%s" in resource base path "%s" and action route "%s"`, wc, ac.Parent.FullPath(), ro.Path) } } } } return nil }) return nil }) for _, route := range allRoutes { for _, other := range allRoutes { if route == other { continue } if strings.HasPrefix(route.Key, other.Key) { diffs := route.DifferentWildcards(other) if len(diffs) > 0 { var msg string conflicts := make([]string, len(diffs)) for i, d := range diffs { conflicts[i] = fmt.Sprintf(`"%s" from %s and "%s" from %s`, d[0].Name, d[0].Orig.Context(), d[1].Name, d[1].Orig.Context()) } msg = fmt.Sprintf("%s", strings.Join(conflicts, ", ")) verr.Add(route.Action, `route "%s" conflicts with route "%s" of %s action %s. Make sure wildcards at the same positions have the same name. Conflicting wildcards are %s.`, route.Route.FullPath(), other.Route.FullPath(), other.Resource.Name, other.Action.Name, msg, ) } } } } a.IterateMediaTypes(func(mt *MediaTypeDefinition) error { verr.Merge(mt.Validate()) return nil }) a.IterateUserTypes(func(t *UserTypeDefinition) error { verr.Merge(t.Validate("", a)) return nil }) a.IterateResponses(func(r *ResponseDefinition) error { verr.Merge(r.Validate()) return nil }) for _, dec := range a.Consumes { verr.Merge(dec.Validate()) } for _, enc := range a.Produces { verr.Merge(enc.Validate()) } err := verr.AsError() if err == nil { // *ValidationErrors(nil) != error(nil) return nil } return err }