Пример #1
0
// Store represents a database.  Gorma lets you specify
// a database type, but it's currently not used for any generation
// logic.
func Store(name string, storeType gorma.RelationalStorageType, dsl func()) {
	if name == "" || len(name) == 0 {
		dslengine.ReportError("Relational Store requires a name.")
		return
	}
	if len(storeType) == 0 {
		dslengine.ReportError("Relational Store requires a RelationalStoreType.")
		return
	}
	if dsl == nil {
		dslengine.ReportError("Relational Store requires a dsl.")
		return
	}
	if s, ok := storageGroupDefinition(true); ok {
		if s.RelationalStores == nil {
			s.RelationalStores = make(map[string]*gorma.RelationalStoreDefinition)
		}
		store, ok := s.RelationalStores[name]
		if !ok {
			store = &gorma.RelationalStoreDefinition{
				Name:             name,
				DefinitionDSL:    dsl,
				Parent:           s,
				Type:             storeType,
				RelationalModels: make(map[string]*gorma.RelationalModelDefinition),
			}
		} else {
			dslengine.ReportError("Relational Store %s can only be declared once.", name)
		}
		s.RelationalStores[name] = store
	}

}
Пример #2
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
}
Пример #3
0
// Trait defines an API trait. A trait encapsulates arbitrary DSL that gets executed wherever the
// trait is called via the UseTrait function.
func Trait(name string, val ...func()) {
	var ver *design.APIVersionDefinition
	if a, ok := apiDefinition(false); ok {
		ver = a.APIVersionDefinition
	} else if v, ok := versionDefinition(true); ok {
		ver = v
	}
	if len(val) < 1 {
		dslengine.ReportError("missing trait DSL for %s", name)
		return
	} else if len(val) > 1 {
		dslengine.ReportError("too many arguments given to Trait")
		return
	}
	if ver == nil {
		return
	}
	if _, ok := ver.Traits[name]; ok {
		dslengine.ReportError("multiple definitions for trait %s%s", name, ver.Context())
		return
	}
	trait := &dslengine.TraitDefinition{Name: name, DSLFunc: val[0]}
	if ver.Traits == nil {
		ver.Traits = make(map[string]*dslengine.TraitDefinition)
	}
	ver.Traits[name] = trait
}
Пример #4
0
// Response implements the response definition DSL. Response takes the name of the response as
// first parameter. goa defines all the standard HTTP status name as global variables so they can be
// readily used as response names. Response also accepts optional arguments that correspond to the
// arguments defined by the corresponding response template (the response template with the same
// name) if there is one, see ResponseTemplate.
//
// A response may also optionally use an anonymous function as last argument to specify the response
// status code, media type and headers overriding what the default response or response template
// specifies:
//
//	Response(OK, "vnd.goa.bottle", func() {	// OK response template accepts one argument: the media type identifier
//		Headers(func() {		// Headers list the response HTTP headers, see Headers
//			Header("X-Request-Id")
//		})
//	})
//
//	Response(NotFound, func() {
//		Status(404)			// Not necessary as defined by default NotFound response.
//		Media("application/json")	// Override NotFound response default of "text/plain"
//	})
//
//	Response(Created, func() {
//		Media(BottleMedia)
//	})
//
// goa defines a default response for all the HTTP status code. The default response simply sets
// the status code. So if an action can return NotFound for example all it has to do is specify
// Response(NotFound) - there is no need to specify the status code as the default response already
// does it, in other words:
//
//	Response(NotFound)
//
// is equivalent to:
//
//	Response(NotFound, func() {
//		Status(404)
//	})
//
// goa also defines a default response template for the OK response which takes a single argument:
// the identifier of the media type used to render the response. The API DSL can define additional
// response templates or override the default OK response template using ResponseTemplate.
//
// The media type identifier specified in a response definition via the Media function can be
// "generic" such as "text/plain" or "application/json" or can correspond to the identifier of a
// media type defined in the API DSL. In this latter case goa uses the media type definition to
// generate helper response methods. These methods know how to render the views defined on the media
// type and run the validations defined in the media type during rendering.
func Response(name string, paramsAndDSL ...interface{}) {
	if a, ok := actionDefinition(false); ok {
		if a.Responses == nil {
			a.Responses = make(map[string]*design.ResponseDefinition)
		}
		if _, ok := a.Responses[name]; ok {
			dslengine.ReportError("response %s is defined twice", name)
			return
		}
		if resp := executeResponseDSL(name, paramsAndDSL...); resp != nil {
			if resp.Status == 200 && resp.MediaType == "" {
				resp.MediaType = a.Parent.MediaType
			}
			resp.Parent = a
			a.Responses[name] = resp
		}
	} else if r, ok := resourceDefinition(true); ok {
		if r.Responses == nil {
			r.Responses = make(map[string]*design.ResponseDefinition)
		}
		if _, ok := r.Responses[name]; ok {
			dslengine.ReportError("response %s is defined twice", name)
			return
		}
		if resp := executeResponseDSL(name, paramsAndDSL...); resp != nil {
			if resp.Status == 200 && resp.MediaType == "" {
				resp.MediaType = r.MediaType
			}
			resp.Parent = r
			r.Responses[name] = resp
		}
	}
}
Пример #5
0
// 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
}
Пример #6
0
// ResponseTemplate defines a response template that action definitions can use to describe their
// responses. The template may specify the HTTP response status, header specification and body media
// type. The template consists of a name and an anonymous function. The function is called when an
// action uses the template to define a response. Response template functions accept string
// parameters they can use to define the response fields. Here is an example of a response template
// definition that uses a function with one argument corresponding to the name of the response body
// media type:
//
//	ResponseTemplate(OK, func(mt string) {
//		Status(200)				// OK response uses status code 200
//		Media(mt)				// Media type name set by action definition
//		Headers(func() {
//			Header("X-Request-Id", func() {	// X-Request-Id header contains a string
//				Pattern("[0-9A-F]+")	// Regexp used to validate the response header content
//			})
//			Required("X-Request-Id")	// Header is mandatory
//		})
//	})
//
// This template can the be used by actions to define the OK response as follows:
//
//	Response(OK, "vnd.goa.example")
//
// goa comes with a set of predefined response templates (one per standard HTTP status code). The
// OK template is the only one that accepts an argument. It is used as shown in the example above to
// set the response media type. Other predefined templates do not use arguments. ResponseTemplate
// makes it possible to define additional response templates specific to the API.
func ResponseTemplate(name string, p interface{}) {
	var v *design.APIVersionDefinition
	if a, ok := apiDefinition(false); ok {
		v = a.APIVersionDefinition
	} else if ver, ok := versionDefinition(true); ok {
		v = ver
	}
	if v == nil {
		return
	}
	if v.Responses == nil {
		v.Responses = make(map[string]*design.ResponseDefinition)
	}
	if v.ResponseTemplates == nil {
		v.ResponseTemplates = make(map[string]*design.ResponseTemplateDefinition)
	}
	if _, ok := v.Responses[name]; ok {
		dslengine.ReportError("multiple definitions for response template %s", name)
		return
	}
	if _, ok := v.ResponseTemplates[name]; ok {
		dslengine.ReportError("multiple definitions for response template %s", name)
		return
	}

	setupResponseTemplate(v, name, p)
}
Пример #7
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
		}
	}
}
Пример #8
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
	}
}
Пример #9
0
// 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}
}
Пример #10
0
// Scope defines an authorization scope. Used within SecurityScheme, a description may be provided
// explaining what the scope means. Within a Security block, only a scope is needed.
func Scope(name string, desc ...string) {
	switch current := dslengine.CurrentDefinition().(type) {
	case *design.SecurityDefinition:
		if len(desc) >= 1 {
			dslengine.ReportError("too many arguments")
			return
		}
		current.Scopes = append(current.Scopes, name)
	case *design.SecuritySchemeDefinition:
		if len(desc) > 1 {
			dslengine.ReportError("too many arguments")
			return
		}
		if current.Scopes == nil {
			current.Scopes = make(map[string]string)
		}
		d := "no description"
		if len(desc) == 1 {
			d = desc[0]
		}
		current.Scopes[name] = d
	default:
		dslengine.IncompatibleDSL()
	}
}
Пример #11
0
// Maximum adds a "maximum" validation to the attribute.
// See http://json-schema.org/latest/json-schema-validation.html#anchor17.
func Maximum(val interface{}) {
	if a, ok := attributeDefinition(); ok {
		if a.Type != nil && a.Type.Kind() != design.IntegerKind && a.Type.Kind() != design.NumberKind {
			incompatibleAttributeType("maximum", a.Type.Name(), "an integer or a number")
		} else {
			var f float64
			switch v := val.(type) {
			case float32, float64, int, int8, int16, int32, int64, uint8, uint16, uint32, uint64:
				f = reflect.ValueOf(v).Convert(reflect.TypeOf(float64(0.0))).Float()
			case string:
				var err error
				f, err = strconv.ParseFloat(v, 64)
				if err != nil {
					dslengine.ReportError("invalid number value %#v", v)
					return
				}
			default:
				dslengine.ReportError("invalid number value %#v", v)
				return
			}
			if a.Validation == nil {
				a.Validation = &dslengine.ValidationDefinition{}
			}
			a.Validation.Maximum = &f
		}
	}
}
Пример #12
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()
	}
}
Пример #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
	}
}
Пример #14
0
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,
		}
	}
}
Пример #15
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()
	}
}
Пример #16
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
}
Пример #17
0
// Version is the top level design language function which defines the API global property values
// for a given version. The DSL used to define the property values is identical to the one used by
// the API function.
func Version(ver string, dsl func()) *design.APIVersionDefinition {
	verdef := &design.APIVersionDefinition{Version: ver, DSLFunc: dsl}
	if _, ok := design.Design.APIVersions[ver]; ok {
		dslengine.ReportError("API Version %s defined twice", ver)
		return verdef
	}
	if design.Design.APIVersions == nil {
		design.Design.APIVersions = make(map[string]*design.APIVersionDefinition)
	}
	if ver == "" {
		dslengine.ReportError("version cannot be an empty string")
	}
	design.Design.APIVersions[ver] = verdef
	return verdef
}
Пример #18
0
// API implements the top level API DSL. It defines the API name, default description and other
// default global property values for all API versions. Here is an example showing all the possible
// API sub-definitions:
//
//	API("API name", func() {
//		Title("title")				// API title used in documentation
//		Description("description")		// API description used in documentation
//		TermsOfService("terms")
//		Contact(func() {			// API Contact information
//			Name("contact name")
//			Email("contact email")
//			URL("contact URL")
//		})
//		License(func() {			// API Licensing information
//			Name("license name")
//			URL("license URL")
//		})
//	 	Docs(func() {
//			Description("doc description")
//			URL("doc URL")
//		})
//		Host("goa.design")			// API hostname
//		Scheme("http")
//		BasePath("/base/:param")		// Common base path to all API actions
//		BaseParams(func() {			// Common parameters to all API actions
//			Param("param")
//		})
//		Consumes("application/xml", "text/xml") // Built-in encoders and decoders
//		Consumes("application/json")
//		Produces("application/gob")
//		Produces("application/json", func() {   // Custom encoder
//			Package("github.com/goadesign/encoding/json")
//		})
//		ResponseTemplate("static", func() {	// Response template for use by actions
//			Description("description")
//			Status(404)
//			MediaType("application/json")
//		})
//		ResponseTemplate("dynamic", func(arg1, arg2 string) {
//			Description(arg1)
//			Status(200)
//			MediaType(arg2)
//		})
//		Trait("Authenticated", func() {		// Traits define DSL that can be run anywhere
//			Headers(func() {
//				Header("header")
//				Required("header")
//			})
//		})
//	}
//
func API(name string, dsl func()) *design.APIDefinition {
	if design.Design.Name != "" {
		dslengine.ReportError("multiple API definitions, only one is allowed")
		return nil
	}
	if !dslengine.TopLevelDefinition(true) {
		return nil
	}
	if name == "" {
		dslengine.ReportError("API name cannot be empty")
	}
	design.Design.Name = name
	design.Design.DSLFunc = dsl
	return design.Design
}
Пример #19
0
// PopulateFromModeledType creates fields for the model
// based on the goa UserTypeDefinition it models.
// This happens before fields are processed, so it's
// ok to just assign without testing.
func (f *RelationalModelDefinition) PopulateFromModeledType() {
	if f.BuiltFrom == nil {
		return
	}
	for _, mt := range f.BuiltFrom {
		obj := mt.ToObject()
		obj.IterateAttributes(func(name string, att *design.AttributeDefinition) error {
			rf := &RelationalFieldDefinition{}
			rf.Parent = f
			rf.Name = codegen.Goify(name, true)
			if strings.HasSuffix(rf.Name, "Id") {
				rf.Name = strings.TrimSuffix(rf.Name, "Id")
				rf.Name = rf.Name + "ID"
			}
			switch att.Type.Kind() {
			case design.BooleanKind:
				rf.Datatype = Boolean
			case design.IntegerKind:
				rf.Datatype = Integer
			case design.NumberKind:
				rf.Datatype = Decimal
			case design.StringKind:
				rf.Datatype = String
			case design.DateTimeKind:
				rf.Datatype = Timestamp
			default:
				dslengine.ReportError("Unsupported type: %#v ", att.Type.Kind())
			}
			f.RelationalFields[rf.Name] = rf
			return nil
		})
	}
	return
}
Пример #20
0
// Enum adds a "enum" validation to the attribute.
// See http://json-schema.org/latest/json-schema-validation.html#anchor76.
func Enum(val ...interface{}) {
	if a, ok := attributeDefinition(); ok {
		ok := true
		for i, v := range val {
			// When can a.Type be nil? glad you asked
			// There are two ways to write an Attribute declaration with the DSL that
			// don't set the type: with one argument - just the name - in which case the type
			// is set to String or with two arguments - the name and DSL. In this latter form
			// the type can end up being either String - if the DSL does not define any
			// attribute - or object if it does.
			// Why allowing this? because it's not always possible to specify the type of an
			// object - an object may just be declared inline to represent a substructure.
			// OK then why not assuming object and not allowing for string? because the DSL
			// where there's only one argument and the type is string implicitly is very
			// useful and common, for example to list attributes that refer to other attributes
			// such as responses that refer to responses defined at the API level or links that
			// refer to the media type attributes. So if the form that takes a DSL always ended
			// up defining an object we'd have a weird situation where one arg is string and
			// two args is object. Breaks the least surprise principle. Soooo long story
			// short the lesser evil seems to be to allow the ambiguity. Also tests like the
			// one below are really a convenience to the user and not a fundamental feature
			// - not checking in the case the type is not known yet is OK.
			if a.Type != nil && !a.Type.IsCompatible(v) {
				dslengine.ReportError("value %#v at index #d is incompatible with attribute of type %s",
					v, i, a.Type.Name())
				ok = false
			}
		}
		if ok {
			a.AddValues(val)
		}
	}
}
Пример #21
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(); ok {
		if m, ok := val.(*design.MediaTypeDefinition); ok {
			if m.UserTypeDefinition == nil {
				dslengine.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 {
			dslengine.ReportError("media type must be a string or a *design.MediaTypeDefinition, got %#v", val)
		}
	}
}
Пример #22
0
// 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)
			}
		}
	}
}
Пример #23
0
// Default sets the default value for an attribute.
// See http://json-schema.org/latest/json-schema-validation.html#anchor10.
func Default(def interface{}) {
	if a, ok := attributeDefinition(); ok {
		if a.Type != nil {
			if !a.Type.CanHaveDefault() {
				dslengine.ReportError("%s type cannot have a default value", qualifiedTypeName(a.Type))
			} else if !a.Type.IsCompatible(def) {
				dslengine.ReportError("default value %#v is incompatible with attribute of type %s",
					def, qualifiedTypeName(a.Type))
			} else {
				a.SetDefault(def)
			}
		} else {
			a.SetDefault(def)
		}
	}
}
Пример #24
0
// 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),
		}
	}
}
Пример #25
0
// Example sets the example of an attribute to be used for the documentation:
//
//	Attributes(func() {
//		Attribute("ID", Integer, func() {
//			Example(1)
//		})
//		Attribute("name", String, func() {
//			Example("Cabernet Sauvignon")
//		})
//		Attribute("price", String) //If no Example() is provided, goa generates one that fits your specification
//	})
//
// If you do not want an auto-generated example for an attribute, add NoExample() to it.
func Example(exp interface{}) {
	if a, ok := attributeDefinition(); ok {
		if pass := a.SetExample(exp); !pass {
			dslengine.ReportError("example value %#v is incompatible with attribute of type %s",
				exp, a.Type.Name())
		}
	}
}
Пример #26
0
// Media sets a response media type by name or by reference using a value returned by MediaType:
//
//	Response("NotFound", func() {
//		Status(404)
//		Media("application/json")
//	})
//
// If Media uses a media type defined in the design then it may optionally specify a view name:
//
//	Response("OK", func() {
//		Status(200)
//		Media(BottleMedia, "tiny")
//	})
//
// Specifying a media type is useful for responses that always return the same view.
//
// Media can be used inside Response or ResponseTemplate.
func Media(val interface{}, viewName ...string) {
	if r, ok := responseDefinition(); ok {
		if m, ok := val.(*design.MediaTypeDefinition); ok {
			if m != nil {
				r.MediaType = m.Identifier
			}
		} else if identifier, ok := val.(string); ok {
			r.MediaType = identifier
		} else {
			dslengine.ReportError("media type must be a string or a pointer to MediaTypeDefinition, got %#v", val)
		}
		if len(viewName) == 1 {
			r.ViewName = viewName[0]
		} else if len(viewName) > 1 {
			dslengine.ReportError("too many arguments given to DefaultMedia")
		}
	}
}
Пример #27
0
func securitySchemeRedefined(name string) bool {
	for _, previousScheme := range design.Design.SecuritySchemes {
		if previousScheme.SchemeName == name {
			dslengine.ReportError("cannot redefine SecurityScheme with name %q", name)
			return true
		}
	}
	return false
}
Пример #28
0
// Default sets the default value for an attribute.
func Default(def interface{}) {
	if a, ok := attributeDefinition(true); ok {
		if a.Type != nil && !a.Type.IsCompatible(def) {
			dslengine.ReportError("default value %#v is incompatible with attribute of type %s",
				def, a.Type.Name())
		} else {
			a.DefaultValue = def
		}
	}
}
Пример #29
0
// Host sets the API hostname.
func Host(host string) {
	if !hostnameRegex.MatchString(host) {
		dslengine.ReportError(`invalid hostname value "%s"`, host)
		return
	}

	if a, ok := apiDefinition(); ok {
		a.Host = host
	}
}
Пример #30
0
// ResponseTemplate defines a response template that action definitions can use to describe their
// responses. The template may specify the HTTP response status, header specification and body media
// type. The template consists of a name and an anonymous function. The function is called when an
// action uses the template to define a response. Response template functions accept string
// parameters they can use to define the response fields. Here is an example of a response template
// definition that uses a function with one argument corresponding to the name of the response body
// media type:
//
//	ResponseTemplate(OK, func(mt string) {
//		Status(200)				// OK response uses status code 200
//		Media(mt)				// Media type name set by action definition
//		Headers(func() {
//			Header("X-Request-Id", func() {	// X-Request-Id header contains a string
//				Pattern("[0-9A-F]+")	// Regexp used to validate the response header content
//			})
//			Required("X-Request-Id")	// Header is mandatory
//		})
//	})
//
// This template can the be used by actions to define the OK response as follows:
//
//	Response(OK, "vnd.goa.example")
//
// goa comes with a set of predefined response templates (one per standard HTTP status code). The
// OK template is the only one that accepts an argument. It is used as shown in the example above to
// set the response media type. Other predefined templates do not use arguments. ResponseTemplate
// makes it possible to define additional response templates specific to the API.
func ResponseTemplate(name string, p interface{}) {
	if a, ok := apiDefinition(); ok {
		if a.Responses == nil {
			a.Responses = make(map[string]*design.ResponseDefinition)
		}
		if a.ResponseTemplates == nil {
			a.ResponseTemplates = make(map[string]*design.ResponseTemplateDefinition)
		}
		if _, ok := a.Responses[name]; ok {
			dslengine.ReportError("multiple definitions for response template %s", name)
			return
		}
		if _, ok := a.ResponseTemplates[name]; ok {
			dslengine.ReportError("multiple definitions for response template %s", name)
			return
		}
		setupResponseTemplate(a, name, p)
	}
}