// CollectionOf creates a collection media type from its element media type. A collection media // type represents the content of responses that return a collection of resources such as "list" // actions. This function can be called from any place where a media type can be used. // The resulting media type identifier is built from the element media type by appending the media // type parameter "type" with value "collection". func CollectionOf(v interface{}, dsl ...func()) *design.MediaTypeDefinition { if generatedMediaTypes == nil { generatedMediaTypes = make(map[string]*design.MediaTypeDefinition) } var m *design.MediaTypeDefinition var ok bool m, ok = v.(*design.MediaTypeDefinition) if !ok { if id, ok := v.(string); ok { m = design.Design.MediaTypes[design.CanonicalIdentifier(id)] } } if m == nil { ReportError("invalid CollectionOf argument: not a media type and not a known media type identifier") return nil } id := m.Identifier mediatype, params, err := mime.ParseMediaType(id) if err != nil { ReportError("invalid media type identifier %#v: %s", id, err) return nil } hasType := false for param := range params { if param == "type" { hasType = true break } } if !hasType { params["type"] = "collection" } id = mime.FormatMediaType(mediatype, params) typeName := m.TypeName + "Collection" if mt, ok := generatedMediaTypes[typeName]; ok { // Already have a type for this collection, reuse it. return mt } mt := design.NewMediaTypeDefinition(typeName, id, func() { if mt, ok := mediaTypeDefinition(true); ok { mt.TypeName = typeName mt.AttributeDefinition = &design.AttributeDefinition{Type: ArrayOf(m)} if len(dsl) > 0 { executeDSL(dsl[0], mt) } if mt.Views == nil { // If the DSL didn't create any views (or there is no DSL at all) // then inherit the views from the collection element. mt.Views = make(map[string]*design.ViewDefinition) for n, v := range m.Views { mt.Views[n] = v } } } }) // Do not execute the DSL right away, will be done last to make sure the element DSL has run // first. generatedMediaTypes[mt.TypeName] = mt return mt }
// CollectionOf creates a collection media type from its element media type. A collection media // type represents the content of responses that return a collection of resources such as "list" // actions. This function can be called from any place where a media type can be used. // The resulting media type identifier is built from the element media type by appending the media // type parameter "type" with value "collection". func CollectionOf(m *design.MediaTypeDefinition, dsl ...func()) *design.MediaTypeDefinition { id := m.Identifier mediatype, params, err := mime.ParseMediaType(id) if err != nil { ReportError("invalid media type identifier %#v: %s", id, err) return nil } slash := strings.Index(mediatype, "/") if slash == -1 { mediatype += "/json" } hasType := false for param := range params { if param == "type" { hasType = true break } } if !hasType { params["type"] = "collection" } id = mime.FormatMediaType(mediatype, params) typeName := m.TypeName + "Collection" mt := design.NewMediaTypeDefinition(typeName, id, func() { if mt, ok := mediaTypeDefinition(true); ok { mt.TypeName = typeName mt.AttributeDefinition = &design.AttributeDefinition{Type: ArrayOf(m)} if len(dsl) > 0 { executeDSL(dsl[0], mt) } } }) if executeDSL(mt.DSL, mt) { design.Design.MediaTypes[id] = mt } return mt }
// MediaType implements the media type definition DSL. A media type definition describes the // representation of a resource used in a response body. This includes listing all the *potential* // resource attributes that can appear in the body. Views specify which of the attributes are // actually rendered so that the same media type definition may represent multiple rendering of a // given resource representation. // // All media types must define a view named "default". This view is used to render the media type in // response bodies when no other view is specified. // // A media type definition may also define links to other media types. This is done by first // defining an attribute for the linked-to media type and then referring to that attribute in the // Links DSL. Views may then elect to render one or the other or both. Links are rendered using the // special "link" view. Media types that are linked to must define that view. Here is an example // showing all the possible media type sub-definitions: // // MediaType("application/vnd.goa.example.bottle", func() { // Description("A bottle of wine") // Attributes(func() { // Attribute("id", Integer, "ID of bottle") // Attribute("href", String, "API href of bottle") // Attribute("account", Account, "Owner account") // Attribute("origin", Origin, "Details on wine origin") // Links(func() { // Link("account") // Defines a link to the Account media type // Link("origin", "tiny") // Overrides the default view used to render links // }) // Required("id", "href") // }) // View("default", func() { // Attribute("id") // Attribute("href") // Attribute("links") // Default view renders links // }) // View("extended", func() { // Attribute("id") // Attribute("href") // Attribute("account") // Extended view renders account inline // Attribute("origin") // Extended view renders origin inline // Attribute("links") // Extended view also renders links // }) // }) // // This function returns the media type definition so it can be referred to throughout the DSL. func MediaType(identifier string, dsl func()) *design.MediaTypeDefinition { if design.Design == nil { InitDesign() } if design.Design.MediaTypes == nil { design.Design.MediaTypes = make(map[string]*design.MediaTypeDefinition) } if topLevelDefinition(true) { identifier, params, err := mime.ParseMediaType(identifier) if err != nil { ReportError("invalid media type identifier %#v: %s", identifier, err) } slash := strings.Index(identifier, "/") if slash == -1 { identifier += "/json" } identifier = mime.FormatMediaType(identifier, params) elems := strings.Split(identifier, ".") elems = strings.Split(elems[len(elems)-1], "/") elems = strings.Split(elems[0], "+") typeName := inflect.Camelize(elems[0]) if typeName == "" { mediaTypeCount++ typeName = fmt.Sprintf("MediaType%d", mediaTypeCount) } if _, ok := design.Design.MediaTypes[identifier]; ok { ReportError("media type %#v is defined twice", identifier) return nil } mt := design.NewMediaTypeDefinition(typeName, identifier, dsl) design.Design.MediaTypes[identifier] = mt return mt } return nil }
// MediaType implements the media type definition DSL. A media type definition describes the // representation of a resource used in a response body. This includes listing all the *potential* // resource attributes that can appear in the body. Views specify which of the attributes are // actually rendered so that the same media type definition may represent multiple rendering of a // given resource representation. // // All media types must define a view named "default". This view is used to render the media type in // response bodies when no other view is specified. // // A media type definition may also define links to other media types. This is done by first // defining an attribute for the linked-to media type and then referring to that attribute in the // Links DSL. Views may then elect to render one or the other or both. Links are rendered using the // special "link" view. Media types that are linked to must define that view. Here is an example // showing all the possible media type sub-definitions: // // MediaType("application/vnd.goa.example.bottle", func() { // Description("A bottle of wine") // APIVersion("1.0") // Attributes(func() { // Attribute("id", Integer, "ID of bottle") // Attribute("href", String, "API href of bottle") // Attribute("account", Account, "Owner account") // Attribute("origin", Origin, "Details on wine origin") // Links(func() { // Link("account") // Defines a link to the Account media type // Link("origin", "tiny") // Overrides the default view used to render links // }) // Required("id", "href") // }) // View("default", func() { // Attribute("id") // Attribute("href") // Attribute("links") // Default view renders links // }) // View("extended", func() { // Attribute("id") // Attribute("href") // Attribute("account") // Extended view renders account inline // Attribute("origin") // Extended view renders origin inline // Attribute("links") // Extended view also renders links // }) // }) // // This function returns the media type definition so it can be referred to throughout the DSL. func MediaType(identifier string, dsl func()) *design.MediaTypeDefinition { if design.Design == nil { InitDesign() } if design.Design.MediaTypes == nil { design.Design.MediaTypes = make(map[string]*design.MediaTypeDefinition) } if topLevelDefinition(true) { // Validate Media Type identifier, params, err := mime.ParseMediaType(identifier) if err != nil { ReportError("invalid media type identifier %#v: %s", identifier, err) // We don't return so that other errors may be // captured in this one run. identifier = "plain/text" } canonicalID := design.CanonicalIdentifier(identifier) // Validate that media type identifier doesn't clash if _, ok := design.Design.MediaTypes[canonicalID]; ok { ReportError("media type %#v is defined twice", identifier) return nil } parts := strings.Split(identifier, "+") // Make sure it has the `+json` suffix (TBD update when goa supports other encodings) if len(parts) > 1 { parts = parts[1:] found := false for _, part := range parts { if part == "json" { found = true break } } if !found { identifier += "+json" } } identifier = mime.FormatMediaType(identifier, params) // Concoct a Go type name from the identifier, should it be possible to set it in the DSL? // pros: control the type name generated, cons: not needed in DSL, adds one more thing to worry about lastPart := identifier lastPartIndex := strings.LastIndex(identifier, "/") if lastPartIndex > -1 { lastPart = identifier[lastPartIndex+1:] } plusIndex := strings.Index(lastPart, "+") if plusIndex > 0 { lastPart = lastPart[:plusIndex] } lastPart = strings.TrimPrefix(lastPart, "vnd.") elems := strings.Split(lastPart, ".") for i, e := range elems { elems[i] = strings.Title(e) } typeName := strings.Join(elems, "") if typeName == "" { mediaTypeCount++ typeName = fmt.Sprintf("MediaType%d", mediaTypeCount) } // Now save the type in the API media types map mt := design.NewMediaTypeDefinition(typeName, identifier, dsl) design.Design.MediaTypes[canonicalID] = mt return mt } return nil }