github.com/zak-blake/goa@v1.4.1/design/validation.go (about)

     1  package design
     2  
     3  import (
     4  	"fmt"
     5  	"go/build"
     6  	"mime"
     7  	"net/url"
     8  	"os"
     9  	"path/filepath"
    10  	"regexp"
    11  	"sort"
    12  	"strings"
    13  
    14  	"github.com/goadesign/goa/dslengine"
    15  )
    16  
    17  type routeInfo struct {
    18  	Key       string
    19  	Resource  *ResourceDefinition
    20  	Action    *ActionDefinition
    21  	Route     *RouteDefinition
    22  	Wildcards []*wildCardInfo
    23  }
    24  
    25  type wildCardInfo struct {
    26  	Name string
    27  	Orig dslengine.Definition
    28  }
    29  
    30  func newRouteInfo(resource *ResourceDefinition, action *ActionDefinition, route *RouteDefinition) *routeInfo {
    31  	vars := route.Params()
    32  	wi := make([]*wildCardInfo, len(vars))
    33  	for i, v := range vars {
    34  		var orig dslengine.Definition
    35  		if strings.Contains(route.Path, v) {
    36  			orig = route
    37  		} else if strings.Contains(resource.BasePath, v) {
    38  			orig = resource
    39  		} else {
    40  			orig = Design
    41  		}
    42  		wi[i] = &wildCardInfo{Name: v, Orig: orig}
    43  	}
    44  	key := WildcardRegex.ReplaceAllLiteralString(route.FullPath(), "*")
    45  	return &routeInfo{
    46  		Key:       key,
    47  		Resource:  resource,
    48  		Action:    action,
    49  		Route:     route,
    50  		Wildcards: wi,
    51  	}
    52  }
    53  
    54  // DifferentWildcards returns the list of wildcards in other that have a different name from the
    55  // wildcard in target at the same position.
    56  func (r *routeInfo) DifferentWildcards(other *routeInfo) (res [][2]*wildCardInfo) {
    57  	for i, wc := range other.Wildcards {
    58  		if r.Wildcards[i].Name != wc.Name {
    59  			res = append(res, [2]*wildCardInfo{r.Wildcards[i], wc})
    60  		}
    61  	}
    62  	return
    63  }
    64  
    65  // Validate tests whether the API definition is consistent: all resource parent names resolve to
    66  // an actual resource.
    67  func (a *APIDefinition) Validate() error {
    68  
    69  	// This is a little bit hacky but we need the generated media types DSLs to run first so
    70  	// that their views are defined otherwise we risk running into validation errors where an
    71  	// attribute defined on a non generated media type uses a generated mediatype (i.e.
    72  	// CollectionOf(Foo)) with a specific view that hasn't been set yet.
    73  	// TBD: Maybe GeneratedMediaTypes should not be a separate DSL root.
    74  	for _, mt := range GeneratedMediaTypes {
    75  		dslengine.Execute(mt.DSLFunc, mt)
    76  		mt.DSLFunc = nil // So that it doesn't run again when the generated media types DSL root is executed
    77  	}
    78  
    79  	verr := new(dslengine.ValidationErrors)
    80  	if a.Params != nil {
    81  		verr.Merge(a.Params.Validate("base parameters", a))
    82  	}
    83  
    84  	a.validateContact(verr)
    85  	a.validateLicense(verr)
    86  	a.validateDocs(verr)
    87  	a.validateOrigins(verr)
    88  
    89  	var allRoutes []*routeInfo
    90  	a.IterateResources(func(r *ResourceDefinition) error {
    91  		verr.Merge(r.Validate())
    92  		r.IterateActions(func(ac *ActionDefinition) error {
    93  			if ac.Docs != nil && ac.Docs.URL != "" {
    94  				if _, err := url.ParseRequestURI(ac.Docs.URL); err != nil {
    95  					verr.Add(ac, "invalid action docs URL value: %s", err)
    96  				}
    97  			}
    98  			for _, ro := range ac.Routes {
    99  				if ro.IsAbsolute() {
   100  					continue
   101  				}
   102  				info := newRouteInfo(r, ac, ro)
   103  				allRoutes = append(allRoutes, info)
   104  				rwcs := ExtractWildcards(ac.Parent.FullPath())
   105  				wcs := ExtractWildcards(ro.Path)
   106  				for _, rwc := range rwcs {
   107  					for _, wc := range wcs {
   108  						if rwc == wc {
   109  							verr.Add(ac, `duplicate wildcard "%s" in resource base path "%s" and action route "%s"`,
   110  								wc, ac.Parent.FullPath(), ro.Path)
   111  						}
   112  					}
   113  				}
   114  			}
   115  			return nil
   116  		})
   117  		return nil
   118  	})
   119  
   120  	a.validateRoutes(verr, allRoutes)
   121  
   122  	a.IterateMediaTypes(func(mt *MediaTypeDefinition) error {
   123  		verr.Merge(mt.Validate())
   124  		return nil
   125  	})
   126  	a.IterateUserTypes(func(t *UserTypeDefinition) error {
   127  		verr.Merge(t.Validate("", a))
   128  		return nil
   129  	})
   130  	a.IterateResponses(func(r *ResponseDefinition) error {
   131  		verr.Merge(r.Validate())
   132  		return nil
   133  	})
   134  	for _, dec := range a.Consumes {
   135  		verr.Merge(dec.Validate())
   136  	}
   137  	for _, enc := range a.Produces {
   138  		verr.Merge(enc.Validate())
   139  	}
   140  
   141  	err := verr.AsError()
   142  	if err == nil {
   143  		// *ValidationErrors(nil) != error(nil)
   144  		return nil
   145  	}
   146  	return err
   147  }
   148  
   149  func (a *APIDefinition) validateRoutes(verr *dslengine.ValidationErrors, routes []*routeInfo) {
   150  	for _, route := range routes {
   151  		for _, other := range routes {
   152  			if route == other {
   153  				continue
   154  			}
   155  			if route.Route.Verb != other.Route.Verb {
   156  				continue
   157  			}
   158  			if strings.HasPrefix(route.Key, other.Key) {
   159  				diffs := route.DifferentWildcards(other)
   160  				if len(diffs) > 0 {
   161  					var msg string
   162  					conflicts := make([]string, len(diffs))
   163  					for i, d := range diffs {
   164  						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())
   165  					}
   166  					msg = fmt.Sprintf("%s", strings.Join(conflicts, ", "))
   167  					verr.Add(route.Action,
   168  						`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.`,
   169  						route.Route.FullPath(),
   170  						other.Route.FullPath(),
   171  						other.Resource.Name,
   172  						other.Action.Name,
   173  						msg,
   174  					)
   175  				}
   176  			}
   177  		}
   178  	}
   179  }
   180  
   181  func (a *APIDefinition) validateContact(verr *dslengine.ValidationErrors) {
   182  	if a.Contact != nil && a.Contact.URL != "" {
   183  		if _, err := url.ParseRequestURI(a.Contact.URL); err != nil {
   184  			verr.Add(a, "invalid contact URL value: %s", err)
   185  		}
   186  	}
   187  }
   188  
   189  func (a *APIDefinition) validateLicense(verr *dslengine.ValidationErrors) {
   190  	if a.License != nil && a.License.URL != "" {
   191  		if _, err := url.ParseRequestURI(a.License.URL); err != nil {
   192  			verr.Add(a, "invalid license URL value: %s", err)
   193  		}
   194  	}
   195  }
   196  
   197  func (a *APIDefinition) validateDocs(verr *dslengine.ValidationErrors) {
   198  	if a.Docs != nil && a.Docs.URL != "" {
   199  		if _, err := url.ParseRequestURI(a.Docs.URL); err != nil {
   200  			verr.Add(a, "invalid docs URL value: %s", err)
   201  		}
   202  	}
   203  }
   204  
   205  func (a *APIDefinition) validateOrigins(verr *dslengine.ValidationErrors) {
   206  	for _, origin := range a.Origins {
   207  		verr.Merge(origin.Validate())
   208  	}
   209  }
   210  
   211  // Validate tests whether the resource definition is consistent: action names are valid and each action is
   212  // valid.
   213  func (r *ResourceDefinition) Validate() *dslengine.ValidationErrors {
   214  	verr := new(dslengine.ValidationErrors)
   215  	if r.Name == "" {
   216  		verr.Add(r, "Resource name cannot be empty")
   217  	}
   218  	r.validateActions(verr)
   219  	if r.ParentName != "" {
   220  		r.validateParent(verr)
   221  	}
   222  	for _, resp := range r.Responses {
   223  		verr.Merge(resp.Validate())
   224  	}
   225  	if r.Params != nil {
   226  		verr.Merge(r.Params.Validate("resource parameters", r))
   227  	}
   228  	for _, origin := range r.Origins {
   229  		verr.Merge(origin.Validate())
   230  	}
   231  	return verr.AsError()
   232  }
   233  
   234  func (r *ResourceDefinition) validateActions(verr *dslengine.ValidationErrors) {
   235  	found := false
   236  	for _, a := range r.Actions {
   237  		if a.Name == r.CanonicalActionName {
   238  			found = true
   239  		}
   240  		verr.Merge(a.Validate())
   241  	}
   242  	for _, f := range r.FileServers {
   243  		verr.Merge(f.Validate())
   244  	}
   245  	if r.CanonicalActionName != "" && !found {
   246  		verr.Add(r, `unknown canonical action "%s"`, r.CanonicalActionName)
   247  	}
   248  }
   249  
   250  func (r *ResourceDefinition) validateParent(verr *dslengine.ValidationErrors) {
   251  	p, ok := Design.Resources[r.ParentName]
   252  	if !ok {
   253  		verr.Add(r, "Parent resource named %#v not found", r.ParentName)
   254  	} else {
   255  		if p.CanonicalAction() == nil {
   256  			verr.Add(r, "Parent resource %#v has no canonical action", r.ParentName)
   257  		}
   258  	}
   259  }
   260  
   261  // Validate makes sure the CORS definition origin is valid.
   262  func (cors *CORSDefinition) Validate() *dslengine.ValidationErrors {
   263  	verr := new(dslengine.ValidationErrors)
   264  	if !cors.Regexp && strings.Count(cors.Origin, "*") > 1 {
   265  		verr.Add(cors, "invalid origin, can only contain one wildcard character")
   266  	}
   267  	if cors.Regexp {
   268  		_, err := regexp.Compile(cors.Origin)
   269  		if err != nil {
   270  			verr.Add(cors, "invalid origin, should be a valid regular expression")
   271  		}
   272  	}
   273  	return verr
   274  }
   275  
   276  // Validate validates the encoding MIME type and Go package path if set.
   277  func (enc *EncodingDefinition) Validate() *dslengine.ValidationErrors {
   278  	verr := new(dslengine.ValidationErrors)
   279  	if len(enc.MIMETypes) == 0 {
   280  		verr.Add(enc, "missing MIME type")
   281  		return verr
   282  	}
   283  	for _, m := range enc.MIMETypes {
   284  		_, _, err := mime.ParseMediaType(m)
   285  		if err != nil {
   286  			verr.Add(enc, "invalid MIME type %#v: %s", m, err)
   287  		}
   288  	}
   289  	if len(enc.PackagePath) > 0 {
   290  		rel := filepath.FromSlash(enc.PackagePath)
   291  		dir, err := os.Getwd()
   292  		if err != nil {
   293  			verr.Add(enc, "couldn't retrieve working directory %s", err)
   294  			return verr
   295  		}
   296  		_, err = build.Default.Import(rel, dir, build.FindOnly)
   297  		if err != nil {
   298  			verr.Add(enc, "invalid Go package path %#v: %s", enc.PackagePath, err)
   299  			return verr
   300  		}
   301  	} else {
   302  		for _, m := range enc.MIMETypes {
   303  			if _, ok := KnownEncoders[m]; !ok {
   304  				knownMIMETypes := make([]string, len(KnownEncoders))
   305  				i := 0
   306  				for k := range KnownEncoders {
   307  					knownMIMETypes[i] = k
   308  					i++
   309  				}
   310  				sort.Strings(knownMIMETypes)
   311  				verr.Add(enc, "Encoders not known for all MIME types, use Package to specify encoder Go package. MIME types with known encoders are %s",
   312  					strings.Join(knownMIMETypes, ", "))
   313  			}
   314  		}
   315  	}
   316  	if enc.Function != "" && enc.PackagePath == "" {
   317  		verr.Add(enc, "Must specify encoder package page with PackagePath")
   318  	}
   319  	return verr
   320  }
   321  
   322  // Validate tests whether the action definition is consistent: parameters have unique names and it has at least
   323  // one response.
   324  func (a *ActionDefinition) Validate() *dslengine.ValidationErrors {
   325  	verr := new(dslengine.ValidationErrors)
   326  	if a.Name == "" {
   327  		verr.Add(a, "Action name cannot be empty")
   328  	}
   329  	if len(a.Routes) == 0 {
   330  		verr.Add(a, "No route defined for action")
   331  	}
   332  	for i, r := range a.Responses {
   333  		for j, r2 := range a.Responses {
   334  			if i != j && r.Status == r2.Status {
   335  				verr.Add(r, "Multiple response definitions with status code %d", r.Status)
   336  			}
   337  		}
   338  		verr.Merge(r.Validate())
   339  		if HasFile(r.Type) {
   340  			verr.Add(a, "Response %s contains an invalid type, action responses cannot contain a file", i)
   341  		}
   342  	}
   343  	verr.Merge(a.ValidateParams())
   344  	if a.Payload != nil {
   345  		verr.Merge(a.Payload.Validate("action payload", a))
   346  		if HasFile(a.Payload.Type) && a.PayloadMultipart != true {
   347  			verr.Add(a, "Payload %s contains an invalid type, action payloads cannot contain a file", a.Payload.TypeName)
   348  		}
   349  	}
   350  	if a.Parent == nil {
   351  		verr.Add(a, "missing parent resource")
   352  	}
   353  	if a.Params != nil {
   354  		for n, p := range a.Params.Type.ToObject() {
   355  			if p.Type.IsPrimitive() {
   356  				if HasFile(p.Type) {
   357  					verr.Add(a, "Param %s has an invalid type, action params cannot be a file", n)
   358  				}
   359  				continue
   360  			}
   361  			if p.Type.IsArray() {
   362  				if p.Type.ToArray().ElemType.Type.IsPrimitive() {
   363  					if HasFile(p.Type.ToArray().ElemType.Type) {
   364  						verr.Add(a, "Param %s has an invalid type, action params cannot be a file array", n)
   365  					}
   366  					continue
   367  				}
   368  			}
   369  			verr.Add(a, "Param %s has an invalid type, action params must be primitives or arrays of primitives", n)
   370  		}
   371  	}
   372  
   373  	return verr.AsError()
   374  }
   375  
   376  // Validate checks the file server is properly initialized.
   377  func (f *FileServerDefinition) Validate() *dslengine.ValidationErrors {
   378  	verr := new(dslengine.ValidationErrors)
   379  	if f.FilePath == "" {
   380  		verr.Add(f, "File server must have a non empty file path")
   381  	}
   382  	if f.RequestPath == "" {
   383  		verr.Add(f, "File server must have a non empty route path")
   384  	}
   385  	if f.Parent == nil {
   386  		verr.Add(f, "missing parent resource")
   387  	}
   388  	matches := WildcardRegex.FindAllString(f.RequestPath, -1)
   389  	if len(matches) == 1 {
   390  		if !strings.HasSuffix(f.RequestPath, matches[0]) {
   391  			verr.Add(f, "invalid request path %s, must end with a wildcard starting with *", f.RequestPath)
   392  		}
   393  	}
   394  	if len(matches) > 2 {
   395  		verr.Add(f, "invalid request path, may only contain one wildcard")
   396  	}
   397  
   398  	return verr.AsError()
   399  }
   400  
   401  // ValidateParams checks the action parameters (make sure they have names, members and types).
   402  func (a *ActionDefinition) ValidateParams() *dslengine.ValidationErrors {
   403  	verr := new(dslengine.ValidationErrors)
   404  	if a.Params == nil {
   405  		return nil
   406  	}
   407  	params, ok := a.Params.Type.(Object)
   408  	if !ok {
   409  		verr.Add(a, `"Params" field of action is not an object`)
   410  	}
   411  	var wcs []string
   412  	for _, r := range a.Routes {
   413  		rwcs := ExtractWildcards(r.FullPath())
   414  		for _, rwc := range rwcs {
   415  			found := false
   416  			for _, wc := range wcs {
   417  				if rwc == wc {
   418  					found = true
   419  					break
   420  				}
   421  			}
   422  			if !found {
   423  				wcs = append(wcs, rwc)
   424  			}
   425  		}
   426  	}
   427  	for n, p := range params {
   428  		if n == "" {
   429  			verr.Add(a, "action has parameter with no name")
   430  		} else if p == nil {
   431  			verr.Add(a, "definition of parameter %s cannot be nil", n)
   432  		} else if p.Type == nil {
   433  			verr.Add(a, "type of parameter %s cannot be nil", n)
   434  		}
   435  		if p.Type.Kind() == ObjectKind {
   436  			verr.Add(a, `parameter %s cannot be an object, only action payloads may be of type object`, n)
   437  		} else if p.Type.Kind() == HashKind {
   438  			verr.Add(a, `parameter %s cannot be a hash, only action payloads may be of type hash`, n)
   439  		}
   440  		ctx := fmt.Sprintf("parameter %s", n)
   441  		verr.Merge(p.Validate(ctx, a))
   442  	}
   443  	for _, resp := range a.Responses {
   444  		verr.Merge(resp.Validate())
   445  	}
   446  	return verr.AsError()
   447  }
   448  
   449  // validated keeps track of validated attributes to handle cyclical definitions.
   450  var validated = make(map[*AttributeDefinition]bool)
   451  
   452  // Validate tests whether the attribute definition is consistent: required fields exist.
   453  // Since attributes are unaware of their context, additional context information can be provided
   454  // to be used in error messages.
   455  // The parent definition context is automatically added to error messages.
   456  func (a *AttributeDefinition) Validate(ctx string, parent dslengine.Definition) *dslengine.ValidationErrors {
   457  	if validated[a] {
   458  		return nil
   459  	}
   460  	validated[a] = true
   461  	verr := new(dslengine.ValidationErrors)
   462  	if a.Type == nil {
   463  		verr.Add(parent, "attribute type is nil")
   464  		return verr
   465  	}
   466  	if ctx != "" {
   467  		ctx += " - "
   468  	}
   469  	// If both Default and Enum are given, make sure the Default value is one of Enum values.
   470  	// TODO: We only do the default value and enum check just for primitive types.
   471  	// Issue 388 (https://github.com/goadesign/goa/issues/388) will address this for other types.
   472  	if a.Type.IsPrimitive() && a.DefaultValue != nil && a.Validation != nil && a.Validation.Values != nil {
   473  		var found bool
   474  		for _, e := range a.Validation.Values {
   475  			if e == a.DefaultValue {
   476  				found = true
   477  				break
   478  			}
   479  		}
   480  		if !found {
   481  			verr.Add(parent, "%sdefault value %#v is not one of the accepted values: %#v", ctx, a.DefaultValue, a.Validation.Values)
   482  		}
   483  	}
   484  	o := a.Type.ToObject()
   485  	if o != nil {
   486  		for _, n := range a.AllRequired() {
   487  			found := false
   488  			for an := range o {
   489  				if n == an {
   490  					found = true
   491  					break
   492  				}
   493  			}
   494  			if !found {
   495  				verr.Add(parent, `%srequired field "%s" does not exist`, ctx, n)
   496  			}
   497  		}
   498  		for n, att := range o {
   499  			ctx = fmt.Sprintf("field %s", n)
   500  			verr.Merge(att.Validate(ctx, parent))
   501  		}
   502  	} else {
   503  		if a.Type.IsArray() {
   504  			elemType := a.Type.ToArray().ElemType
   505  			verr.Merge(elemType.Validate(ctx, a))
   506  		}
   507  	}
   508  
   509  	return verr.AsError()
   510  }
   511  
   512  // Validate checks that the response definition is consistent: its status is set and the media
   513  // type definition if any is valid.
   514  func (r *ResponseDefinition) Validate() *dslengine.ValidationErrors {
   515  	verr := new(dslengine.ValidationErrors)
   516  	if r.Headers != nil {
   517  		verr.Merge(r.Headers.Validate("response headers", r))
   518  	}
   519  	if r.Status == 0 {
   520  		verr.Add(r, "response status not defined")
   521  	}
   522  	return verr.AsError()
   523  }
   524  
   525  // Validate checks that the route definition is consistent: it has a parent.
   526  func (r *RouteDefinition) Validate() *dslengine.ValidationErrors {
   527  	verr := new(dslengine.ValidationErrors)
   528  	if r.Parent == nil {
   529  		verr.Add(r, "missing route parent action")
   530  	}
   531  	return verr.AsError()
   532  }
   533  
   534  // Validate checks that the user type definition is consistent: it has a name and the attribute
   535  // backing the type is valid.
   536  func (u *UserTypeDefinition) Validate(ctx string, parent dslengine.Definition) *dslengine.ValidationErrors {
   537  	verr := new(dslengine.ValidationErrors)
   538  	if u.TypeName == "" {
   539  		verr.Add(parent, "%s - %s", ctx, "User type must have a name")
   540  	}
   541  	verr.Merge(u.AttributeDefinition.Validate(ctx, u))
   542  	return verr.AsError()
   543  }
   544  
   545  // Validate checks that the media type definition is consistent: its identifier is a valid media
   546  // type identifier.
   547  func (m *MediaTypeDefinition) Validate() *dslengine.ValidationErrors {
   548  	verr := new(dslengine.ValidationErrors)
   549  	verr.Merge(m.UserTypeDefinition.Validate("", m))
   550  	if m.Type == nil { // TBD move this to somewhere else than validation code
   551  		m.Type = String
   552  	}
   553  	var obj Object
   554  	if a := m.Type.ToArray(); a != nil {
   555  		if a.ElemType == nil {
   556  			verr.Add(m, "array element type is nil")
   557  		} else {
   558  			if err := a.ElemType.Validate("array element", m); err != nil {
   559  				verr.Merge(err)
   560  			} else {
   561  				if _, ok := a.ElemType.Type.(*MediaTypeDefinition); !ok {
   562  					verr.Add(m, "collection media type array element type must be a media type, got %s", a.ElemType.Type.Name())
   563  				} else {
   564  					obj = a.ElemType.Type.ToObject()
   565  				}
   566  			}
   567  		}
   568  	} else {
   569  		obj = m.Type.ToObject()
   570  	}
   571  	if obj != nil {
   572  		for n, att := range obj {
   573  			verr.Merge(att.Validate("attribute "+n, m))
   574  			if att.View != "" {
   575  				cmt, ok := att.Type.(*MediaTypeDefinition)
   576  				if !ok {
   577  					verr.Add(m, "attribute %s of media type defines a view for rendering but its type is not MediaTypeDefinition", n)
   578  				}
   579  				if _, ok := cmt.Views[att.View]; !ok {
   580  					verr.Add(m, "attribute %s of media type uses unknown view %#v", n, att.View)
   581  				}
   582  			}
   583  		}
   584  	}
   585  	hasDefaultView := false
   586  	for n, v := range m.Views {
   587  		if n == "default" {
   588  			hasDefaultView = true
   589  		}
   590  		verr.Merge(v.Validate())
   591  	}
   592  	if !hasDefaultView {
   593  		verr.Add(m, `media type does not define the default view, use View("default", ...) to define it.`)
   594  	}
   595  
   596  	for _, l := range m.Links {
   597  		verr.Merge(l.Validate())
   598  	}
   599  	return verr.AsError()
   600  }
   601  
   602  // Validate checks that the link definition is consistent: it has a media type or the name of an
   603  // attribute part of the parent media type.
   604  func (l *LinkDefinition) Validate() *dslengine.ValidationErrors {
   605  	verr := new(dslengine.ValidationErrors)
   606  	if l.Name == "" {
   607  		verr.Add(l, "Links must have a name")
   608  	}
   609  	if l.Parent == nil {
   610  		verr.Add(l, "Link must have a parent media type")
   611  	}
   612  	if l.Parent.ToObject() == nil {
   613  		verr.Add(l, "Link parent media type must be an Object")
   614  	}
   615  	att, ok := l.Parent.ToObject()[l.Name]
   616  	if !ok {
   617  		verr.Add(l, "Link name must match one of the parent media type attribute names")
   618  	} else {
   619  		mediaType, ok := att.Type.(*MediaTypeDefinition)
   620  		if !ok {
   621  			verr.Add(l, "attribute type must be a media type")
   622  		} else {
   623  			viewFound := false
   624  			view := l.View
   625  			for v := range mediaType.Views {
   626  				if v == view {
   627  					viewFound = true
   628  					break
   629  				}
   630  			}
   631  			if !viewFound {
   632  				verr.Add(l, "view %#v does not exist on target media type %#v", view, mediaType.Identifier)
   633  			}
   634  		}
   635  	}
   636  	return verr.AsError()
   637  }
   638  
   639  // Validate checks that the view definition is consistent: it has a  parent media type and the
   640  // underlying definition type is consistent.
   641  func (v *ViewDefinition) Validate() *dslengine.ValidationErrors {
   642  	verr := new(dslengine.ValidationErrors)
   643  	if v.Parent == nil {
   644  		verr.Add(v, "View must have a parent media type")
   645  	}
   646  	verr.Merge(v.AttributeDefinition.Validate("", v))
   647  	return verr.AsError()
   648  }