Esempio n. 1
0
// Headers implements the DSL for describing HTTP headers. The DSL syntax is identical to the one
// of Attribute. Here is an example defining a couple of headers with validations:
//
//	Headers(func() {
//		Header("Authorization")
//		Header("X-Account", Integer, func() {
//			Minimum(1)
//		})
//		Required("Authorization")
//	})
//
// Headers can be used inside Action to define the action request headers, Response to define the
// response headers or Resource to define common request headers to all the resource actions.
func Headers(dsl func()) {
	if a, ok := actionDefinition(false); ok {
		headers := newAttribute(a.Parent.MediaType)
		if dslengine.Execute(dsl, headers) {
			a.Headers = headers
		}
	} else if r, ok := resourceDefinition(false); ok {
		headers := newAttribute(r.MediaType)
		if dslengine.Execute(dsl, headers) {
			r.Headers = headers
		}
	} else if r, ok := responseDefinition(true); ok {
		if r.Headers != nil {
			dslengine.ReportError("headers already defined")
			return
		}
		var h *design.AttributeDefinition
		switch actual := r.Parent.(type) {
		case *design.ResourceDefinition:
			h = newAttribute(actual.MediaType)
		case *design.ActionDefinition:
			h = newAttribute(actual.Parent.MediaType)
		case nil: // API ResponseTemplate
			h = &design.AttributeDefinition{}
		default:
			dslengine.ReportError("invalid use of Response or ResponseTemplate")
		}
		if dslengine.Execute(dsl, h) {
			r.Headers = h
		}
	}
}
Esempio n. 2
0
// Headers implements the DSL for describing HTTP headers. The DSL syntax is identical to the one
// of Attribute. Here is an example defining a couple of headers with validations:
//
//	Headers(func() {
//		Header("Authorization")
//		Header("X-Account", Integer, func() {
//			Minimum(1)
//		})
//		Required("Authorization")
//	})
//
// Headers can be used inside Action to define the action request headers, Response to define the
// response headers or Resource to define common request headers to all the resource actions.
func Headers(params ...interface{}) {
	if len(params) == 0 {
		dslengine.ReportError("missing parameter")
		return
	}
	dsl, ok := params[0].(func())
	if ok {
		switch def := dslengine.CurrentDefinition().(type) {
		case *design.ActionDefinition:
			headers := newAttribute(def.Parent.MediaType)
			if dslengine.Execute(dsl, headers) {
				def.Headers = headers
			}

		case *design.ResourceDefinition:
			headers := newAttribute(def.MediaType)
			if dslengine.Execute(dsl, headers) {
				def.Headers = headers
			}

		case *design.ResponseDefinition:
			if def.Headers != nil {
				dslengine.ReportError("headers already defined")
				return
			}
			var h *design.AttributeDefinition
			switch actual := def.Parent.(type) {
			case *design.ResourceDefinition:
				h = newAttribute(actual.MediaType)
			case *design.ActionDefinition:
				h = newAttribute(actual.Parent.MediaType)
			case nil: // API ResponseTemplate
				h = &design.AttributeDefinition{}
			default:
				dslengine.ReportError("invalid use of Response or ResponseTemplate")
			}
			if dslengine.Execute(dsl, h) {
				def.Headers = h
			}

		default:
			dslengine.IncompatibleDSL()
		}
	} else if cors, ok := corsDefinition(); ok {
		vals := make([]string, len(params))
		for i, p := range params {
			if v, ok := p.(string); ok {
				vals[i] = v
			} else {
				dslengine.ReportError("invalid parameter at position %d: must be a string", i)
				return
			}
		}
		cors.Headers = vals
	} else {
		dslengine.IncompatibleDSL()
	}
}
Esempio n. 3
0
File: api.go Progetto: smessier/goa
func setupResponseTemplate(a *design.APIDefinition, name string, p interface{}) {
	if f, ok := p.(func()); ok {
		r := &design.ResponseDefinition{Name: name}
		if dslengine.Execute(f, r) {
			a.Responses[name] = r
		}
	} else if tmpl, ok := p.(func(...string)); ok {
		t := func(params ...string) *design.ResponseDefinition {
			r := &design.ResponseDefinition{Name: name}
			dslengine.Execute(func() { tmpl(params...) }, r)
			return r
		}
		a.ResponseTemplates[name] = &design.ResponseTemplateDefinition{
			Name:     name,
			Template: t,
		}
	} else {
		typ := reflect.TypeOf(p)
		if kind := typ.Kind(); kind != reflect.Func {
			dslengine.ReportError("dsl must be a function but got %s", kind)
			return
		}

		num := typ.NumIn()
		val := reflect.ValueOf(p)
		t := func(params ...string) *design.ResponseDefinition {
			if len(params) < num {
				args := "1 argument"
				if num > 0 {
					args = fmt.Sprintf("%d arguments", num)
				}
				dslengine.ReportError("expected at least %s when invoking response template %s", args, name)
				return nil
			}
			r := &design.ResponseDefinition{Name: name}

			in := make([]reflect.Value, num)
			for i := 0; i < num; i++ {
				// type checking
				if t := typ.In(i); t.Kind() != reflect.String {
					dslengine.ReportError("ResponseTemplate parameters must be strings but type of parameter at position %d is %s", i, t)
					return nil
				}
				// append input arguments
				in[i] = reflect.ValueOf(params[i])
			}
			dslengine.Execute(func() { val.Call(in) }, r)
			return r
		}
		a.ResponseTemplates[name] = &design.ResponseTemplateDefinition{
			Name:     name,
			Template: t,
		}
	}
}
Esempio n. 4
0
// Params describe the action parameters, either path parameters identified via wildcards or query
// string parameters. Each parameter is described via the `Param` function which uses the same DSL
// as the Attribute DSL. Here is an example:
//
//	Params(func() {
//		Param("id", Integer)		// A path parameter defined using e.g. GET("/:id")
//		Param("sort", String, func() {	// A query string parameter
//			Enum("asc", "desc")
//		})
//	})
//
// Params can be used inside Action to define the action parameters or Resource to define common
// parameters to all the resource actions.
func Params(dsl func()) {
	if a, ok := actionDefinition(false); ok {
		params := newAttribute(a.Parent.MediaType)
		if dslengine.Execute(dsl, params) {
			a.Params = params
		}
	} else if r, ok := resourceDefinition(true); ok {
		params := newAttribute(r.MediaType)
		if dslengine.Execute(dsl, params) {
			r.Params = params
		}
	}
}
Esempio n. 5
0
// View adds a new view to a media type. A view has a name and lists attributes that are
// rendered when the view is used to produce a response. The attribute names must appear in the
// media type definition. If an attribute is itself a media type then the view may specify which
// view to use when rendering the attribute using the View function in the View dsl. If not
// specified then the view named "default" is used. Examples:
//
//	View("default", func() {
//		Attribute("id")		// "id" and "name" must be media type attributes
//		Attribute("name")
//	})
//
//	View("extended", func() {
//		Attribute("id")
//		Attribute("name")
//		Attribute("origin", func() {
//			View("extended")	// Use view "extended" to render attribute "origin"
//		})
//	})
func View(name string, dsl ...func()) {
	if mt, ok := mediaTypeDefinition(false); ok {
		if !mt.Type.IsObject() && !mt.Type.IsArray() {
			dslengine.ReportError("cannot define view on non object and non collection media types")
			return
		}
		if mt.Views == nil {
			mt.Views = make(map[string]*design.ViewDefinition)
		} else {
			if _, ok = mt.Views[name]; ok {
				dslengine.ReportError("multiple definitions for view %#v in media type %#v", name, mt.TypeName)
				return
			}
		}
		at := &design.AttributeDefinition{}
		ok := false
		if len(dsl) > 0 {
			ok = dslengine.Execute(dsl[0], at)
		} else if mt.Type.IsArray() {
			// inherit view from collection element if present
			elem := mt.Type.ToArray().ElemType
			if elem != nil {
				if pa, ok2 := elem.Type.(*design.MediaTypeDefinition); ok2 {
					if v, ok2 := pa.Views[name]; ok2 {
						at = v.AttributeDefinition
						ok = true
					} else {
						dslengine.ReportError("unknown view %#v", name)
						return
					}
				}
			}
		}
		if ok {
			o := at.Type.ToObject()
			if o != nil {
				mto := mt.Type.ToObject()
				if mto == nil {
					mto = mt.Type.ToArray().ElemType.Type.ToObject()
				}
				for n, cat := range o {
					if existing, ok := mto[n]; ok {
						dup := existing.Dup()
						dup.View = cat.View
						o[n] = dup
					} else if n != "links" {
						dslengine.ReportError("unknown attribute %#v", n)
					}
				}
			}
			mt.Views[name] = &design.ViewDefinition{
				AttributeDefinition: at,
				Name:                name,
				Parent:              mt,
			}
		}
	} else if a, ok := attributeDefinition(true); ok {
		a.View = name
	}
}
Esempio n. 6
0
func executeResponseDSL(name string, paramsAndDSL ...interface{}) *design.ResponseDefinition {
	var params []string
	var dsl func()
	var ok bool
	var dt design.DataType
	if len(paramsAndDSL) > 0 {
		d := paramsAndDSL[len(paramsAndDSL)-1]
		if dsl, ok = d.(func()); ok {
			paramsAndDSL = paramsAndDSL[:len(paramsAndDSL)-1]
		}
		if len(paramsAndDSL) > 0 {
			t := paramsAndDSL[0]
			if dt, ok = t.(design.DataType); ok {
				paramsAndDSL = paramsAndDSL[1:]
			}
		}
		params = make([]string, len(paramsAndDSL))
		for i, p := range paramsAndDSL {
			params[i], ok = p.(string)
			if !ok {
				dslengine.ReportError("invalid response template parameter %#v, must be a string", p)
				return nil
			}
		}
	}
	var resp *design.ResponseDefinition
	if len(params) > 0 {
		if tmpl, ok := design.Design.ResponseTemplates[name]; ok {
			resp = tmpl.Template(params...)
		} else if tmpl, ok := design.Design.DefaultResponseTemplates[name]; ok {
			resp = tmpl.Template(params...)
		} else {
			dslengine.ReportError("no response template named %#v", name)
			return nil
		}
	} else {
		if ar, ok := design.Design.Responses[name]; ok {
			resp = ar.Dup()
		} else if ar, ok := design.Design.DefaultResponses[name]; ok {
			resp = ar.Dup()
			resp.Standard = true
		} else {
			resp = &design.ResponseDefinition{Name: name}
		}
	}
	if dsl != nil {
		if !dslengine.Execute(dsl, resp) {
			return nil
		}
		resp.Standard = false
	}
	if dt != nil {
		if mt, ok := dt.(*design.MediaTypeDefinition); ok {
			resp.MediaType = mt.Identifier
		}
		resp.Type = dt
		resp.Standard = false
	}
	return resp
}
Esempio n. 7
0
File: types.go Progetto: ajoulie/goa
func (m *MediaTypeDefinition) projectCollection(view string) (p *MediaTypeDefinition, links *UserTypeDefinition, err error) {
	e := m.ToArray().ElemType.Type.(*MediaTypeDefinition) // validation checked this cast would work
	pe, le, err2 := e.Project(view)
	if err2 != nil {
		return nil, nil, fmt.Errorf("collection element: %s", err2)
	}
	p = &MediaTypeDefinition{
		Identifier: m.Identifier,
		UserTypeDefinition: &UserTypeDefinition{
			AttributeDefinition: &AttributeDefinition{
				Type: &Array{ElemType: &AttributeDefinition{Type: pe}},
			},
			TypeName: pe.TypeName + "Collection",
		},
	}
	if !dslengine.Execute(p.DSL(), p) {
		return nil, nil, dslengine.Errors
	}
	if le != nil {
		lTypeName := le.TypeName + "Array"
		links = &UserTypeDefinition{
			AttributeDefinition: &AttributeDefinition{
				Type:        &Array{ElemType: &AttributeDefinition{Type: le}},
				Description: fmt.Sprintf("%s contains links to related resources of %s.", lTypeName, m.TypeName),
			},
			TypeName: lTypeName,
		}
	}
	return
}
Esempio n. 8
0
File: api.go Progetto: smessier/goa
// buildEncodingDefinition builds up an encoding definition.
func buildEncodingDefinition(encoding bool, args ...interface{}) *design.EncodingDefinition {
	var dsl func()
	var ok bool
	funcName := "Consumes"
	if encoding {
		funcName = "Produces"
	}
	if len(args) == 0 {
		dslengine.ReportError("missing argument in call to %s", funcName)
		return nil
	}
	if _, ok = args[0].(string); !ok {
		dslengine.ReportError("first argument to %s must be a string (MIME type)", funcName)
		return nil
	}
	last := len(args)
	if dsl, ok = args[len(args)-1].(func()); ok {
		last = len(args) - 1
	}
	mimeTypes := make([]string, last)
	for i := 0; i < last; i++ {
		var mimeType string
		if mimeType, ok = args[i].(string); !ok {
			dslengine.ReportError("argument #%d of %s must be a string (MIME type)", i, funcName)
			return nil
		}
		mimeTypes[i] = mimeType
	}
	d := &design.EncodingDefinition{MIMETypes: mimeTypes, Encoder: encoding}
	if dsl != nil {
		dslengine.Execute(dsl, d)
	}
	return d
}
Esempio n. 9
0
File: type.go Progetto: smessier/goa
// ArrayOf creates an array type from its element type. The result can be used anywhere a type can.
// Examples:
//
//	var Bottle = Type("bottle", func() {
//		Attribute("name")
//	})
//
//	var Bottles = ArrayOf(Bottle)
//
//	Action("update", func() {
//		Params(func() {
//			Param("ids", ArrayOf(Integer))
//		})
//		Payload(ArrayOf(Bottle))  // Equivalent to Payload(Bottles)
//	})
//
// ArrayOf accepts an optional DSL as second argument which allows providing validations for the
// elements of the array:
//
//      var Names = ArrayOf(String, func() {
//          Pattern("[a-zA-Z]+")
//      })
//
// If you are looking to return a collection of elements in a Response clause, refer to
// CollectionOf.  ArrayOf creates a type, where CollectionOf creates a media type.
func ArrayOf(v interface{}, dsl ...func()) *design.Array {
	var t design.DataType
	var ok bool
	t, ok = v.(design.DataType)
	if !ok {
		if name, ok := v.(string); ok {
			t = design.Design.Types[name]
		}
	}
	// never return nil to avoid panics, errors are reported after DSL execution
	res := &design.Array{ElemType: &design.AttributeDefinition{Type: design.String}}
	if t == nil {
		dslengine.ReportError("invalid ArrayOf argument: not a type and not a known user type name")
		return res
	}
	if len(dsl) > 1 {
		dslengine.ReportError("ArrayOf: too many arguments")
		return res
	}
	at := design.AttributeDefinition{Type: t}
	if len(dsl) == 1 {
		dslengine.Execute(dsl[0], &at)
	}
	return &design.Array{ElemType: &at}
}
Esempio n. 10
0
File: api.go Progetto: smessier/goa
// Origin defines the CORS policy for a given origin. The origin can use a wildcard prefix
// such as "https://*.mydomain.com". The special value "*" defines the policy for all origins
// (in which case there should be only one Origin DSL in the parent resource).
// The origin can also be a regular expression wrapped into "/".
// Example:
//
//        Origin("http://swagger.goa.design", func() { // Define CORS policy, may be prefixed with "*" wildcard
//                Headers("X-Shared-Secret")           // One or more authorized headers, use "*" to authorize all
//                Methods("GET", "POST")               // One or more authorized HTTP methods
//                Expose("X-Time")                     // One or more headers exposed to clients
//                MaxAge(600)                          // How long to cache a preflight request response
//                Credentials()                        // Sets Access-Control-Allow-Credentials header
//        })
//
//        Origin("/[api|swagger].goa.design/", func() {}) // Define CORS policy with a regular expression
func Origin(origin string, dsl func()) {
	cors := &design.CORSDefinition{Origin: origin}

	if strings.HasPrefix(origin, "/") && strings.HasSuffix(origin, "/") {
		cors.Regexp = true
		cors.Origin = strings.Trim(origin, "/")
	}

	if !dslengine.Execute(dsl, cors) {
		return
	}
	var parent dslengine.Definition
	switch def := dslengine.CurrentDefinition().(type) {
	case *design.APIDefinition:
		parent = def
		if def.Origins == nil {
			def.Origins = make(map[string]*design.CORSDefinition)
		}
		def.Origins[origin] = cors
	case *design.ResourceDefinition:
		parent = def
		if def.Origins == nil {
			def.Origins = make(map[string]*design.CORSDefinition)
		}
		def.Origins[origin] = cors
	default:
		dslengine.IncompatibleDSL()
		return
	}
	cors.Parent = parent
}
Esempio n. 11
0
File: api.go Progetto: smessier/goa
// UseTrait executes the API trait with the given name. UseTrait can be used inside a Resource,
// Action, Type, MediaType or Attribute DSL. UseTrait takes a variable number
// of trait names.
func UseTrait(names ...string) {
	var def dslengine.Definition

	switch typedDef := dslengine.CurrentDefinition().(type) {
	case *design.ResourceDefinition:
		def = typedDef
	case *design.ActionDefinition:
		def = typedDef
	case *design.AttributeDefinition:
		def = typedDef
	case *design.MediaTypeDefinition:
		def = typedDef
	default:
		dslengine.IncompatibleDSL()
	}

	if def != nil {
		for _, name := range names {
			if trait, ok := design.Design.Traits[name]; ok {
				dslengine.Execute(trait.DSLFunc, def)
			} else {
				dslengine.ReportError("unknown trait %s", name)
			}
		}
	}
}
Esempio n. 12
0
// Params describe the action parameters, either path parameters identified via wildcards or query
// string parameters if there is no corresponding path parameter. Each parameter is described via
// the Param function which uses the same DSL as the Attribute DSL. Here is an example:
//
//	Params(func() {
//		Param("id", Integer)		// A path parameter defined using e.g. GET("/:id")
//		Param("sort", String, func() {	// A query string parameter
//			Enum("asc", "desc")
//		})
//	})
//
// Params can be used inside Action to define the action parameters, Resource to define common
// parameters to all the resource actions or API to define common parameters to all the API actions.
//
// If Params is used inside Resource or Action then the resource base media type attributes provide
// default values for all the properties of params with identical names. For example:
//
//     var BottleMedia = MediaType("application/vnd.bottle", func() {
//         Attributes(func() {
//             Attribute("name", String, "The name of the bottle", func() {
//                 MinLength(2) // BottleMedia has one attribute "name" which is a
//                              // string that must be at least 2 characters long.
//             })
//         })
//         View("default", func() {
//             Attribute("name")
//         })
//     })
//
//     var _ = Resource("Bottle", func() {
//         DefaultMedia(BottleMedia) // Resource "Bottle" uses "BottleMedia" as default
//         Action("show", func() {   // media type.
//             Routing(GET("/:name"))
//             Params(func() {
//                 Param("name") // inherits type, description and validation from
//                               // BottleMedia "name" attribute
//             })
//         })
//     })
//
func Params(dsl func()) {
	var params *design.AttributeDefinition
	switch def := dslengine.CurrentDefinition().(type) {
	case *design.ActionDefinition:
		params = newAttribute(def.Parent.MediaType)
	case *design.ResourceDefinition:
		params = newAttribute(def.MediaType)
	case *design.APIDefinition:
		params = new(design.AttributeDefinition)
	default:
		dslengine.IncompatibleDSL()
	}
	params.Type = make(design.Object)
	if !dslengine.Execute(dsl, params) {
		return
	}
	switch def := dslengine.CurrentDefinition().(type) {
	case *design.ActionDefinition:
		def.Params = def.Params.Merge(params) // Useful for traits
	case *design.ResourceDefinition:
		def.Params = def.Params.Merge(params) // Useful for traits
	case *design.APIDefinition:
		def.Params = def.Params.Merge(params) // Useful for traits
	}
}
Esempio n. 13
0
func payload(isOptional bool, p interface{}, dsls ...func()) {
	if len(dsls) > 1 {
		dslengine.ReportError("too many arguments given to Payload")
		return
	}
	if a, ok := actionDefinition(); ok {
		var att *design.AttributeDefinition
		var dsl func()
		switch actual := p.(type) {
		case func():
			dsl = actual
			att = newAttribute(a.Parent.MediaType)
			att.Type = design.Object{}
		case *design.AttributeDefinition:
			att = design.DupAtt(actual)
		case *design.UserTypeDefinition:
			if len(dsls) == 0 {
				a.Payload = actual
				a.PayloadOptional = isOptional
				return
			}
			att = design.DupAtt(actual.Definition())
		case *design.MediaTypeDefinition:
			att = design.DupAtt(actual.AttributeDefinition)
		case string:
			ut, ok := design.Design.Types[actual]
			if !ok {
				dslengine.ReportError("unknown payload type %s", actual)
			}
			att = design.DupAtt(ut.AttributeDefinition)
		case *design.Array:
			att = &design.AttributeDefinition{Type: actual}
		case *design.Hash:
			att = &design.AttributeDefinition{Type: actual}
		case design.Primitive:
			att = &design.AttributeDefinition{Type: actual}
		default:
			dslengine.ReportError("invalid Payload argument, must be a type, a media type or a DSL building a type")
			return
		}
		if len(dsls) == 1 {
			if dsl != nil {
				dslengine.ReportError("invalid arguments in Payload call, must be (type), (dsl) or (type, dsl)")
			}
			dsl = dsls[0]
		}
		if dsl != nil {
			dslengine.Execute(dsl, att)
		}
		rn := camelize(a.Parent.Name)
		an := camelize(a.Name)
		a.Payload = &design.UserTypeDefinition{
			AttributeDefinition: att,
			TypeName:            fmt.Sprintf("%s%sPayload", an, rn),
		}
		a.PayloadOptional = isOptional
	}
}
Esempio n. 14
0
File: api.go Progetto: smessier/goa
// Contact sets the API contact information.
func Contact(dsl func()) {
	contact := new(design.ContactDefinition)
	if !dslengine.Execute(dsl, contact) {
		return
	}
	if a, ok := apiDefinition(); ok {
		a.Contact = contact
	}
}
Esempio n. 15
0
File: api.go Progetto: smessier/goa
// License sets the API license information.
func License(dsl func()) {
	license := new(design.LicenseDefinition)
	if !dslengine.Execute(dsl, license) {
		return
	}
	if a, ok := apiDefinition(); ok {
		a.License = license
	}
}
Esempio n. 16
0
// PATCH creates a route using the PATCH HTTP method.
func PATCH(path string, dsl ...func()) *design.RouteDefinition {
	route := &design.RouteDefinition{Verb: "PATCH", Path: path}
	if len(dsl) != 0 {
		if !dslengine.Execute(dsl[0], route) {
			return nil
		}
	}
	return route
}
Esempio n. 17
0
// Attribute implements the attribute definition DSL. An attribute describes a data structure
// recursively. Attributes are used for describing request headers, parameters and payloads -
// response bodies and headers - media types	 and types. An attribute definition is recursive:
// attributes may include other attributes. At the basic level an attribute has a name,
// a type and optionally a default value and validation rules. The type of an attribute can be one of:
//
// * The primitive types Boolean, Integer, Number or String.
//
// * A type defined via the Type function.
//
// * A media type defined via the MediaType function.
//
// * An object described recursively with child attributes.
//
// * An array defined using the ArrayOf function.
//
// * An hashmap defined using the HashOf function.
//
// Attributes can be defined using the Attribute, Param, Member or Header functions depending
// on where the definition appears. The syntax for all these DSL is the same.
// Here are some examples:
//
//	Attribute("name")					// Defines an attribute of type String
//
//	Attribute("name", func() {
//		Pattern("^foo")					// Adds a validation rule to the attribute
//	})
//
//	Attribute("name", Integer)				// Defines an attribute of type Integer
//
//	Attribute("name", Integer, func() {
//		Default(42)					// With a default value
//	})
//
//	Attribute("name", Integer, "description")		// Specifies a description
//
//	Attribute("name", Integer, "description", func() {
//		Enum(1, 2)					// And validation rules
//	})
//
// Nested attributes:
//
//	Attribute("nested", func() {
//		Description("description")
//		Attribute("child")
//		Attribute("child2", func() {
//			// ....
//		})
//		Required("child")
//	})
//
// Here are all the valid usage of the Attribute function:
//
//	Attribute(name string, dataType DataType, description string, dsl func())
//
//	Attribute(name string, dataType DataType, description string)
//
//	Attribute(name string, dataType DataType, dsl func())
//
//	Attribute(name string, dataType DataType)
//
//	Attribute(name string, dsl func())	// dataType is String or Object (if DSL defines child attributes)
//
//	Attribute(name string)			// dataType is String
func Attribute(name string, args ...interface{}) {
	var parent *design.AttributeDefinition

	switch def := dslengine.CurrentDefinition().(type) {
	case *design.AttributeDefinition:
		parent = def
	case *design.MediaTypeDefinition:
		parent = def.AttributeDefinition
	case design.ContainerDefinition:
		parent = def.Attribute()
	default:
		dslengine.IncompatibleDSL()
	}

	if parent != nil {
		if parent.Type == nil {
			parent.Type = design.Object{}
		}
		if _, ok := parent.Type.(design.Object); !ok {
			dslengine.ReportError("can't define child attributes on attribute of type %s", parent.Type.Name())
			return
		}

		var baseAttr *design.AttributeDefinition
		if parent.Reference != nil {
			if att, ok := parent.Reference.ToObject()[name]; ok {
				baseAttr = design.DupAtt(att)
			}
		}

		dataType, description, dsl := parseAttributeArgs(baseAttr, args...)
		if baseAttr != nil {
			if description != "" {
				baseAttr.Description = description
			}
			if dataType != nil {
				baseAttr.Type = dataType
			}
		} else {
			baseAttr = &design.AttributeDefinition{
				Type:        dataType,
				Description: description,
			}
		}
		baseAttr.Reference = parent.Reference
		if dsl != nil {
			dslengine.Execute(dsl, baseAttr)
		}
		if baseAttr.Type == nil {
			// DSL did not contain an "Attribute" declaration
			baseAttr.Type = design.String
		}
		parent.Type.(design.Object)[name] = baseAttr
	}
}
Esempio n. 18
0
// Contact sets the API contact information.
func Contact(dsl func()) {
	contact := new(design.ContactDefinition)
	if !dslengine.Execute(dsl, contact) {
		return
	}
	if a, ok := apiDefinition(false); ok {
		a.Contact = contact
	} else if v, ok := versionDefinition(true); ok {
		v.Contact = contact
	}
}
Esempio n. 19
0
// License sets the API license information.
func License(dsl func()) {
	license := new(design.LicenseDefinition)
	if !dslengine.Execute(dsl, license) {
		return
	}
	if a, ok := apiDefinition(false); ok {
		a.License = license
	} else if v, ok := versionDefinition(true); ok {
		v.License = license
	}
}
Esempio n. 20
0
// Params describe the action parameters, either path parameters identified via wildcards or query
// string parameters. Each parameter is described via the `Param` function which uses the same DSL
// as the Attribute DSL. Here is an example:
//
//	Params(func() {
//		Param("id", Integer)		// A path parameter defined using e.g. GET("/:id")
//		Param("sort", String, func() {	// A query string parameter
//			Enum("asc", "desc")
//		})
//	})
//
// Params can be used inside Action to define the action parameters or Resource to define common
// parameters to all the resource actions.
func Params(dsl func()) {
	switch def := dslengine.CurrentDefinition().(type) {
	case *design.ActionDefinition:
		params := newAttribute(def.Parent.MediaType)
		params.Type = make(design.Object)
		if dslengine.Execute(dsl, params) {
			def.Params = params
		}

	case *design.ResourceDefinition:
		params := newAttribute(def.MediaType)
		params.Type = make(design.Object)
		if dslengine.Execute(dsl, params) {
			def.Params = params
		}

	default:
		dslengine.IncompatibleDSL()
	}
}
Esempio n. 21
0
// View adds a new view to a media type. A view has a name and lists attributes that are
// rendered when the view is used to produce a response. The attribute names must appear in the
// media type definition. If an attribute is itself a media type then the view may specify which
// view to use when rendering the attribute using the View function in the View apidsl. If not
// specified then the view named "default" is used. Examples:
//
//	View("default", func() {
//		Attribute("id")		// "id" and "name" must be media type attributes
//		Attribute("name")
//	})
//
//	View("extended", func() {
//		Attribute("id")
//		Attribute("name")
//		Attribute("origin", func() {
//			View("extended")	// Use view "extended" to render attribute "origin"
//		})
//	})
func View(name string, apidsl ...func()) {
	switch def := dslengine.CurrentDefinition().(type) {
	case *design.MediaTypeDefinition:
		mt := def

		if !mt.Type.IsObject() && !mt.Type.IsArray() {
			dslengine.ReportError("cannot define view on non object and non collection media types")
			return
		}
		if mt.Views == nil {
			mt.Views = make(map[string]*design.ViewDefinition)
		} else {
			if _, ok := mt.Views[name]; ok {
				dslengine.ReportError("multiple definitions for view %#v in media type %#v", name, mt.TypeName)
				return
			}
		}
		at := &design.AttributeDefinition{}
		ok := false
		if len(apidsl) > 0 {
			ok = dslengine.Execute(apidsl[0], at)
		} else if mt.Type.IsArray() {
			// inherit view from collection element if present
			elem := mt.Type.ToArray().ElemType
			if elem != nil {
				if pa, ok2 := elem.Type.(*design.MediaTypeDefinition); ok2 {
					if v, ok2 := pa.Views[name]; ok2 {
						at = v.AttributeDefinition
						ok = true
					} else {
						dslengine.ReportError("unknown view %#v", name)
						return
					}
				}
			}
		}
		if ok {
			view, err := buildView(name, mt, at)
			if err != nil {
				dslengine.ReportError(err.Error())
				return
			}
			mt.Views[name] = view
		}

	case *design.AttributeDefinition:
		def.View = name

	default:
		dslengine.IncompatibleDSL()
	}
}
Esempio n. 22
0
// Docs provides external documentation pointers.
func Docs(dsl func()) {
	docs := new(design.DocsDefinition)
	if !dslengine.Execute(dsl, docs) {
		return
	}
	if a, ok := apiDefinition(false); ok {
		a.Docs = docs
	} else if v, ok := versionDefinition(false); ok {
		v.Docs = docs
	} else if a, ok := actionDefinition(true); ok {
		a.Docs = docs
	}
}
Esempio n. 23
0
// Attribute implements the attribute definition DSL. An attribute describes a data structure
// recursively. Attributes are used for describing request headers, parameters and payloads -
// response bodies and headers - media types and types. An attribute definition is recursive:
// attributes may include other attributes. At the basic level an attribute has a name,
// a type and optionally a default value and validation rules. The type of an attribute can be one of:
//
// * The primitive types Boolean, Integer, Number or String.
//
// * A type defined via the Type function.
//
// * A media type defined via the MediaType function.
//
// * An object described recursively with child attributes.
//
// * An array defined using the ArrayOf function.
//
// * An hashmap defined using the HashOf function.
//
// Attributes can be defined using the Attribute, Param, Member or Header functions depending
// on where the definition appears. The syntax for all these DSL is the same.
// Here are some examples:
//
//	Attribute("name")					// Defines an attribute of type String
//
//	Attribute("name", func() {
//		Pattern("^foo")					// Adds a validation rule to the attribute
//	})
//
//	Attribute("name", Integer)				// Defines an attribute of type Integer
//
//	Attribute("name", Integer, func() {
//		Default(42)					// With a default value
//	})
//
//	Attribute("name", Integer, "description")		// Specifies a description
//
//	Attribute("name", Integer, "description", func() {
//		Enum(1, 2)					// And validation rules
//	})
//
// Nested attributes:
//
//	Attribute("nested", func() {
//		Description("description")
//		Attribute("child")
//		Attribute("child2", func() {
//			// ....
//		})
//		Required("child")
//	})
//
// Here are all the valid usage of the Attribute function:
//
//	Attribute(name string, dataType DataType, description string, dsl func())
//
//	Attribute(name string, dataType DataType, description string)
//
//	Attribute(name string, dataType DataType, dsl func())
//
//	Attribute(name string, dataType DataType)
//
//	Attribute(name string, dsl func())	// dataType is String or Object (if DSL defines child attributes)
//
//	Attribute(name string)			// dataType is String
func Attribute(name string, args ...interface{}) {
	var parent *design.AttributeDefinition
	if at, ok := attributeDefinition(false); ok {
		parent = at
	} else if mt, ok := mediaTypeDefinition(true); ok {
		parent = mt.AttributeDefinition
	}

	if parent != nil {
		if parent.Type == nil {
			parent.Type = design.Object{}
		}
		if _, ok := parent.Type.(design.Object); !ok {
			dslengine.ReportError("can't define child attributes on attribute of type %s", parent.Type.Name())
			return
		}

		var baseAttr *design.AttributeDefinition
		if parent.Reference != nil {
			if att, ok := parent.Reference.ToObject()[name]; ok {
				baseAttr = design.DupAtt(att)
			}
		}

		dataType, description, dsl := parseAttributeArgs(baseAttr, args...)
		if baseAttr != nil {
			if description != "" {
				baseAttr.Description = description
			}
			if dataType != nil {
				baseAttr.Type = dataType
			}
		} else {
			baseAttr = &design.AttributeDefinition{
				Type:        dataType,
				Description: description,
			}
		}
		baseAttr.Reference = parent.Reference
		if dsl != nil {
			dslengine.Execute(dsl, baseAttr)
		}
		if baseAttr.Type == nil {
			// DSL did not contain an "Attribute" declaration
			baseAttr.Type = design.String
		}
		parent.Type.(design.Object)[name] = baseAttr
	}
}
Esempio n. 24
0
File: api.go Progetto: ajoulie/goa
// Docs provides external documentation pointers.
func Docs(dsl func()) {
	docs := new(design.DocsDefinition)
	if !dslengine.Execute(dsl, docs) {
		return
	}

	switch def := dslengine.CurrentDefinition().(type) {
	case *design.APIDefinition:
		def.Docs = docs
	case *design.ActionDefinition:
		def.Docs = docs
	default:
		dslengine.IncompatibleDSL()
	}
}
Esempio n. 25
0
// Files defines an API endpoint that serves static assets. The logic for what to do when the
// filename points to a file vs. a directory is the same as the standard http package ServeFile
// function. The path may end with a wildcard that matches the rest of the URL (e.g. *filepath). If
// it does the matching path is appended to filename to form the full file path, so:
//
// 	Files("/index.html", "/www/data/index.html")
//
// Returns the content of the file "/www/data/index.html" when requests are sent to "/index.html"
// and:
//
//	Files("/assets/*filepath", "/www/data/assets")
//
// returns the content of the file "/www/data/assets/x/y/z" when requests are sent to
// "/assets/x/y/z".
// The file path may be specified as a relative path to the current path of the process.
// Files support setting a description, security scheme and doc links via additional DSL:
//
//    Files("/index.html", "/www/data/index.html", func() {
//        Description("Serve home page")
//        Docs(func() {
//            Description("Download docs")
//            URL("http//cellarapi.com/docs/actions/download")
//        })
//        Security("oauth2", func() {
//            Scope("api:read")
//        })
//    })
func Files(path, filename string, dsls ...func()) {
	if r, ok := resourceDefinition(); ok {
		server := &design.FileServerDefinition{
			Parent:      r,
			RequestPath: path,
			FilePath:    filename,
		}
		if len(dsls) > 0 {
			if !dslengine.Execute(dsls[0], server) {
				return
			}
		}
		r.FileServers = append(r.FileServers, server)
	}
}
Esempio n. 26
0
File: action.go Progetto: intfrr/goa
// Payload implements the action payload DSL. An action payload describes the HTTP request body
// data structure. The function accepts either a type or a DSL that describes the payload members
// using the Member DSL which accepts the same syntax as the Attribute DSL. This function can be
// called passing in a type, a DSL or both. Examples:
//
//	Payload(BottlePayload)		// Request payload is described by the BottlePayload type
//
//	Payload(func() {		// Request payload is an object and is described inline
//		Member("Name")
//	})
//
//	Payload(BottlePayload, func() {	// Request payload is described by merging the inline
//		Required("Name")	// definition into the BottlePayload type.
//	})
//
func Payload(p interface{}, dsls ...func()) {
	if len(dsls) > 1 {
		dslengine.ReportError("too many arguments given to Payload")
		return
	}
	if a, ok := actionDefinition(true); ok {
		var att *design.AttributeDefinition
		var dsl func()
		switch actual := p.(type) {
		case func():
			dsl = actual
			att = newAttribute(a.Parent.MediaType)
			att.Type = design.Object{}
		case *design.AttributeDefinition:
			att = actual.Dup()
		case design.DataStructure:
			att = actual.Definition().Dup()
		case string:
			ut, ok := design.Design.Types[actual]
			if !ok {
				dslengine.ReportError("unknown payload type %s", actual)
			}
			att = ut.AttributeDefinition.Dup()
		case *design.Array:
			att = &design.AttributeDefinition{Type: actual}
		case *design.Hash:
			att = &design.AttributeDefinition{Type: actual}
		case design.Primitive:
			att = &design.AttributeDefinition{Type: actual}
		}
		if len(dsls) == 1 {
			if dsl != nil {
				dslengine.ReportError("invalid arguments in Payload call, must be (type), (dsl) or (type, dsl)")
			}
			dsl = dsls[0]
		}
		if dsl != nil {
			dslengine.Execute(dsl, att)
		}
		rn := inflect.Camelize(a.Parent.Name)
		an := inflect.Camelize(a.Name)
		a.Payload = &design.UserTypeDefinition{
			AttributeDefinition: att,
			TypeName:            fmt.Sprintf("%s%sPayload", an, rn),
		}
	}
}
Esempio n. 27
0
func (m *MediaTypeDefinition) projectCollection(view string) (*MediaTypeDefinition, *UserTypeDefinition, error) {
	// Project the collection element media type
	e := m.ToArray().ElemType.Type.(*MediaTypeDefinition) // validation checked this cast would work
	pe, le, err2 := e.Project(view)
	if err2 != nil {
		return nil, nil, fmt.Errorf("collection element: %s", err2)
	}

	// Build the projected collection with the results
	desc := m.TypeName + " is the media type for an array of " + e.TypeName + " (" + view + " view)"
	p := &MediaTypeDefinition{
		Identifier: m.projectIdentifier(view),
		UserTypeDefinition: &UserTypeDefinition{
			AttributeDefinition: &AttributeDefinition{
				Description: desc,
				Type:        &Array{ElemType: &AttributeDefinition{Type: pe}},
				Example:     m.Example,
			},
			TypeName: pe.TypeName + "Collection",
		},
	}
	p.Views = map[string]*ViewDefinition{"default": &ViewDefinition{
		AttributeDefinition: DupAtt(pe.Views["default"].AttributeDefinition),
		Name:                "default",
		Parent:              p,
	}}

	// Run the DSL that was created by the CollectionOf function
	if !dslengine.Execute(p.DSL(), p) {
		return nil, nil, dslengine.Errors
	}

	// Build the links user type
	var links *UserTypeDefinition
	if le != nil {
		lTypeName := le.TypeName + "Array"
		links = &UserTypeDefinition{
			AttributeDefinition: &AttributeDefinition{
				Type:        &Array{ElemType: &AttributeDefinition{Type: le}},
				Description: fmt.Sprintf("%s contains links to related resources of %s.", lTypeName, m.TypeName),
			},
			TypeName: lTypeName,
		}
	}

	return p, links, nil
}
Esempio n. 28
0
// UseTrait executes the API trait with the given name. UseTrait can be used inside a Resource,
// Action or Attribute DSL.
func UseTrait(name string) {
	var def dslengine.Definition
	if r, ok := resourceDefinition(false); ok {
		def = r
	} else if a, ok := actionDefinition(false); ok {
		def = a
	} else if a, ok := attributeDefinition(true); ok {
		def = a
	}
	if def != nil {
		if trait, ok := design.Design.Traits[name]; ok {
			dslengine.Execute(trait.DSLFunc, def)
		} else {
			dslengine.ReportError("unknown trait %s", name)
		}
	}
}
Esempio n. 29
0
// BaseParams defines the API base path parameters. These parameters may correspond to wildcards in
// the BasePath or URL query string values.
// The DSL for describing each Param is the Attribute DSL.
func BaseParams(dsl func()) {
	params := new(design.AttributeDefinition)
	if !dslengine.Execute(dsl, params) {
		return
	}
	params.NonZeroAttributes = make(map[string]bool)
	for n := range params.Type.ToObject() {
		params.NonZeroAttributes[n] = true
	}
	if a, ok := apiDefinition(false); ok {
		a.BaseParams = params
	} else if v, ok := versionDefinition(false); ok {
		v.BaseParams = params
	} else if r, ok := resourceDefinition(true); ok {
		r.BaseParams = params
	}
}
Esempio n. 30
0
// Action implements the action definition DSL. Action definitions describe specific API endpoints
// including the URL, HTTP method and request parameters (via path wildcards or query strings) and
// payload (data structure describing the request HTTP body). An action belongs to a resource and
// "inherits" default values from the resource definition including the URL path prefix, default
// response media type and default payload attribute properties (inherited from the attribute with
// identical name in the resource default media type). Action definitions also describe all the
// possible responses including the HTTP status, headers and body. Here is an example showing all
// the possible sub-definitions:
//    Action("Update", func() {
//        Description("Update account")
//        Docs(func() {
//            Description("Update docs")
//            URL("http//cellarapi.com/docs/actions/update")
//        })
//        Scheme("http")
//        Routing(
//            PUT("/:id"),                     // Action path is relative to parent resource base path
//            PUT("//orgs/:org/accounts/:id"), // The // prefix indicates an absolute path
//        )
//        Params(func() {                      // Params describe the action parameters
//            Param("org", String)             // Parameters may correspond to path wildcards
//            Param("id", Integer)
//            Param("sort", func() {           // or URL query string values.
//                Enum("asc", "desc")
//            })
//        })
//        Security("oauth2", func() {          // Security sets the security scheme used to secure requests
//            Scope("api:read")
//            Scope("api:write")
//        })
//        Headers(func() {                     // Headers describe relevant action headers
//            Header("Authorization", String)
//            Header("X-Account", Integer)
//            Required("Authorization", "X-Account")
//        })
//        Payload(UpdatePayload)                // Payload describes the HTTP request body
//        // OptionalPayload(UpdatePayload)     // OptionalPayload defines an HTTP request body which may be omitted
//        Response(NoContent)                   // Each possible HTTP response is described via Response
//        Response(NotFound)
//    })
func Action(name string, dsl func()) {
	if r, ok := resourceDefinition(); ok {
		if r.Actions == nil {
			r.Actions = make(map[string]*design.ActionDefinition)
		}
		action, ok := r.Actions[name]
		if !ok {
			action = &design.ActionDefinition{
				Parent: r,
				Name:   name,
			}
		}
		if !dslengine.Execute(dsl, action) {
			return
		}
		r.Actions[name] = action
	}
}