Beispiel #1
0
func okResp(a *design.ActionDefinition) map[string]interface{} {
	var ok *design.ResponseDefinition
	for _, resp := range a.Responses {
		if resp.Status == 200 {
			ok = resp
			break
		}
	}
	if ok == nil {
		return nil
	}
	var mt *design.MediaTypeDefinition
	var ok2 bool
	if mt, ok2 = design.Design.MediaTypes[design.CanonicalIdentifier(ok.MediaType)]; !ok2 {
		return nil
	}
	name := codegen.GoTypeRef(mt, mt.AllRequired(), 1, false)
	var pointer string
	if strings.HasPrefix(name, "*") {
		name = name[1:]
		pointer = "*"
	}
	typeref := fmt.Sprintf("%s%s.%s", pointer, TargetPackage, name)
	if strings.HasPrefix(typeref, "*") {
		typeref = "&" + typeref[1:]
	}
	return map[string]interface{}{
		"Name":    ok.Name,
		"GoType":  codegen.GoNativeType(mt),
		"TypeRef": typeref,
	}
}
Beispiel #2
0
// DefaultMedia sets a resource default media type by identifier or by reference using a value
// returned by MediaType:
//
//	var _ = Resource("bottle", func() {
//		DefaultMedia(BottleMedia)
//		// ...
//	})
//
//	var _ = Resource("region", func() {
//		DefaultMedia("vnd.goa.region")
//		// ...
//	})
//
// The default media type is used to build OK response definitions when no specific media type is
// given in the Response function call. The default media type is also used to set the default
// properties of attributes listed in action payloads. So if a media type defines an attribute
// "name" with associated validations then simply calling Attribute("name") inside a request
// Payload defines the payload attribute with the same type and validations.
func DefaultMedia(val interface{}) {
	if r, ok := resourceDefinition(true); ok {
		if m, ok := val.(*design.MediaTypeDefinition); ok {
			if m.UserTypeDefinition == nil {
				ReportError("invalid media type specification, media type is not initialized")
			} else {
				r.MediaType = design.CanonicalIdentifier(m.Identifier)
				m.Resource = r
			}
		} else if identifier, ok := val.(string); ok {
			r.MediaType = design.CanonicalIdentifier(identifier)
		} else {
			ReportError("media type must be a string or a *design.MediaTypeDefinition, got %#v", val)
		}
	}
}
Beispiel #3
0
func (g *Generator) okResp(a *design.ActionDefinition) map[string]interface{} {
	var ok *design.ResponseDefinition
	for _, resp := range a.Responses {
		if resp.Status == 200 {
			ok = resp
			break
		}
	}
	if ok == nil {
		return nil
	}
	var mt *design.MediaTypeDefinition
	var ok2 bool
	if mt, ok2 = design.Design.MediaTypes[design.CanonicalIdentifier(ok.MediaType)]; !ok2 {
		return nil
	}
	view := ok.ViewName
	if view == "" {
		view = design.DefaultView
	}
	pmt, _, err := mt.Project(view)
	if err != nil {
		return nil
	}
	var typeref string
	if pmt.IsError() {
		typeref = `goa.ErrInternal("not implemented")`
	} else {
		name := codegen.GoTypeRef(pmt, pmt.AllRequired(), 1, false)
		var pointer string
		if strings.HasPrefix(name, "*") {
			name = name[1:]
			pointer = "*"
		}
		typeref = fmt.Sprintf("%s%s.%s", pointer, g.Target, name)
		if strings.HasPrefix(typeref, "*") {
			typeref = "&" + typeref[1:]
		}
		typeref += "{}"
	}
	var nameSuffix string
	if view != "default" {
		nameSuffix = codegen.Goify(view, true)
	}
	return map[string]interface{}{
		"Name":    ok.Name + nameSuffix,
		"GoType":  codegen.GoNativeType(pmt),
		"TypeRef": typeref,
	}
}
Beispiel #4
0
// MediaType implements the media type definition DSL. A media type definition describes the
// representation of a resource used in a response body.
//
// Media types are defined with a unique identifier as defined by RFC6838. The identifier also
// defines the default value for the Content-Type header of responses. The ContentType DSL allows
// overridding the default as shown in the example below.
//
// The media type definition includes a listing of all the potential 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")
//        TypeName("BottleMedia")         // Override default generated name
//        ContentType("application/json") // Override default Content-Type header value
//        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 link to Account media type
//                Link("origin", "tiny")  // Set view used to render link if not "link"
//            })
//            Required("id", "href")
//        })
//        View("default", func() {
//            Attribute("id")
//            Attribute("href")
//            Attribute("links")          // Renders links
//        })
//        View("extended", func() {
//            Attribute("id")
//            Attribute("href")
//            Attribute("account")        // Renders account inline
//            Attribute("origin")         // Renders origin inline
//            Attribute("links")          // Renders links
//        })
//     })
//
// This function returns the media type definition so it can be referred to throughout the apidsl.
func MediaType(identifier string, apidsl func()) *design.MediaTypeDefinition {
	if design.Design.MediaTypes == nil {
		design.Design.MediaTypes = make(map[string]*design.MediaTypeDefinition)
	}

	if !dslengine.IsTopLevelDefinition() {
		dslengine.IncompatibleDSL()
		return nil
	}

	// Validate Media Type
	identifier, params, err := mime.ParseMediaType(identifier)
	if err != nil {
		dslengine.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 = "text/plain"
	}
	canonicalID := design.CanonicalIdentifier(identifier)
	// Validate that media type identifier doesn't clash
	if _, ok := design.Design.MediaTypes[canonicalID]; ok {
		dslengine.ReportError("media type %#v with canonical identifier %#v is defined twice", identifier, canonicalID)
		return nil
	}
	identifier = mime.FormatMediaType(identifier, params)
	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, apidsl)
	design.Design.MediaTypes[canonicalID] = mt
	return mt
}
Beispiel #5
0
func okResp(a *design.ActionDefinition) map[string]interface{} {
	var ok *design.ResponseDefinition
	for _, resp := range a.Responses {
		if resp.Status == 200 {
			ok = resp
			break
		}
	}
	if ok == nil {
		return nil
	}
	var mt *design.MediaTypeDefinition
	var ok2 bool
	if mt, ok2 = design.Design.MediaTypes[design.CanonicalIdentifier(ok.MediaType)]; !ok2 {
		return nil
	}
	view := "default"
	if _, ok := mt.Views["default"]; !ok {
		for v := range mt.Views {
			view = v
			break
		}
	}
	pmt, _, err := mt.Project(view)
	if err != nil {
		return nil
	}
	name := codegen.GoTypeRef(pmt, pmt.AllRequired(), 1, false)
	var pointer string
	if strings.HasPrefix(name, "*") {
		name = name[1:]
		pointer = "*"
	}
	typeref := fmt.Sprintf("%s%s.%s", pointer, TargetPackage, name)
	if strings.HasPrefix(typeref, "*") {
		typeref = "&" + typeref[1:]
	}
	var nameSuffix string
	if view != "default" {
		nameSuffix = codegen.Goify(view, true)
	}
	return map[string]interface{}{
		"Name":    ok.Name + nameSuffix,
		"GoType":  codegen.GoNativeType(pmt),
		"TypeRef": typeref,
	}
}
Beispiel #6
0
func responseSpecFromDefinition(s *Swagger, api *design.APIDefinition, r *design.ResponseDefinition) (*Response, error) {
	var schema *genschema.JSONSchema
	if r.MediaType != "" {
		if mt, ok := api.MediaTypes[design.CanonicalIdentifier(r.MediaType)]; ok {
			schema = genschema.TypeSchema(api, mt)
		}
	}
	headers, err := headersFromDefinition(r.Headers)
	if err != nil {
		return nil, err
	}
	return &Response{
		Description: r.Description,
		Schema:      schema,
		Headers:     headers,
	}, nil
}
Beispiel #7
0
func responseSpecFromDefinition(s *Swagger, api *design.APIDefinition, r *design.ResponseDefinition) (*Response, error) {
	var schema *genschema.JSONSchema
	if r.MediaType != "" {
		if mt, ok := api.MediaTypes[design.CanonicalIdentifier(r.MediaType)]; ok {
			view := r.ViewName
			if view == "" {
				view = design.DefaultView
			}
			schema = genschema.NewJSONSchema()
			schema.Ref = genschema.MediaTypeRef(api, mt, view)
		}
	}
	headers, err := headersFromDefinition(r.Headers)
	if err != nil {
		return nil, err
	}
	return &Response{
		Description: r.Description,
		Schema:      schema,
		Headers:     headers,
		Extensions:  extensionsFromDefinition(r.Metadata),
	}, nil
}
Beispiel #8
0
package design_test

import (
	"github.com/goadesign/goa/design"
	"github.com/goadesign/goa/dslengine"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("CanonicalIdentifier", func() {
	var id string
	var canonical string

	JustBeforeEach(func() {
		canonical = design.CanonicalIdentifier(id)
	})

	Context("with a canonical identifier", func() {
		BeforeEach(func() {
			id = "application/json"
		})

		It("returns it", func() {
			Ω(canonical).Should(Equal(id))
		})
	})

	Context("with a non canonical identifier", func() {
		BeforeEach(func() {
			id = "application/json+xml; foo=bar"
		})
Beispiel #9
0
// MediaType implements the media type definition apidsl. 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 apidsl. 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")
//		TypeName("BottleMedia") 		// Optionally override the default generated name
//		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 apidsl.
func MediaType(identifier string, apidsl func()) *design.MediaTypeDefinition {
	if design.Design.MediaTypes == nil {
		design.Design.MediaTypes = make(map[string]*design.MediaTypeDefinition)
	}

	if !dslengine.IsTopLevelDefinition() {
		dslengine.IncompatibleDSL()
		return nil
	}

	// Validate Media Type
	identifier, params, err := mime.ParseMediaType(identifier)
	if err != nil {
		dslengine.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 = "text/plain"
	}
	canonicalID := design.CanonicalIdentifier(identifier)
	// Validate that media type identifier doesn't clash
	if _, ok := design.Design.MediaTypes[canonicalID]; ok {
		dslengine.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 apidsl?
	// pros: control the type name generated, cons: not needed in apidsl, 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, apidsl)
	design.Design.MediaTypes[canonicalID] = mt
	return mt

}
Beispiel #10
0
// 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{}, apidsl ...func()) *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 {
		dslengine.ReportError("invalid CollectionOf argument: not a media type and not a known media type identifier")
		// don't return nil to avoid panics, the error will get reported at the end
		return design.NewMediaTypeDefinition("InvalidCollection", "text/plain", nil)
	}
	id := m.Identifier
	mediatype, params, err := mime.ParseMediaType(id)
	if err != nil {
		dslengine.ReportError("invalid media type identifier %#v: %s", id, err)
		// don't return nil to avoid panics, the error will get reported at the end
		return design.NewMediaTypeDefinition("InvalidCollection", "text/plain", nil)
	}
	hasType := false
	for param := range params {
		if param == "type" {
			hasType = true
			break
		}
	}
	if !hasType {
		params["type"] = "collection"
	}
	id = mime.FormatMediaType(mediatype, params)
	canonical := design.CanonicalIdentifier(id)
	if mt, ok := design.GeneratedMediaTypes[canonical]; ok {
		// Already have a type for this collection, reuse it.
		return mt
	}
	mt := design.NewMediaTypeDefinition("", id, func() {
		if mt, ok := mediaTypeDefinition(); ok {
			// Cannot compute collection type name before element media type DSL has executed
			// since the DSL may modify element type name via the TypeName function.
			mt.TypeName = m.TypeName + "Collection"
			mt.AttributeDefinition = &design.AttributeDefinition{Type: ArrayOf(m)}
			if len(apidsl) > 0 {
				dslengine.Execute(apidsl[0], mt)
			}
			if mt.Views == nil {
				// If the apidsl didn't create any views (or there is no apidsl 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 apidsl right away, will be done last to make sure the element apidsl has run
	// first.
	design.GeneratedMediaTypes[canonical] = mt
	return mt
}
Beispiel #11
0
// 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{}, apidsl ...func()) *design.MediaTypeDefinition {
	if design.GeneratedMediaTypes == nil {
		design.GeneratedMediaTypes = make(design.MediaTypeRoot)
		dslengine.Roots = append(dslengine.Roots, design.GeneratedMediaTypes)
	}
	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 {
		dslengine.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 {
		dslengine.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 := design.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)}
			mt.APIVersions = m.APIVersions
			if len(apidsl) > 0 {
				dslengine.Execute(apidsl[0], mt)
			}
			if mt.Views == nil {
				// If the apidsl didn't create any views (or there is no apidsl 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 apidsl right away, will be done last to make sure the element apidsl has run
	// first.
	design.GeneratedMediaTypes[typeName] = mt
	return mt
}
Beispiel #12
0
							AttributeDefinition: &design.AttributeDefinition{
								Type: design.Object{"foo": {Type: design.String}},
							},
						},
						Identifier:  "application/vnd.goa.test",
						ContentType: contentType,
					}
					defView := &design.ViewDefinition{
						AttributeDefinition: mediaType.AttributeDefinition,
						Name:                "default",
						Parent:              mediaType,
					}
					mediaType.Views = map[string]*design.ViewDefinition{"default": defView}
					design.Design = new(design.APIDefinition)
					design.Design.MediaTypes = map[string]*design.MediaTypeDefinition{
						design.CanonicalIdentifier(mediaType.Identifier): mediaType,
					}
					design.ProjectedMediaTypes = make(map[string]*design.MediaTypeDefinition)
					responses = map[string]*design.ResponseDefinition{"OK": {
						Name:      "OK",
						Status:    200,
						MediaType: mediaType.Identifier,
					}}
				})

				It("the generated code sets the Content-Type header", func() {
					err := writer.Execute(data)
					Ω(err).ShouldNot(HaveOccurred())
					b, err := ioutil.ReadFile(filename)
					Ω(err).ShouldNot(HaveOccurred())
					written := string(b)