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

     1  package apidsl
     2  
     3  import (
     4  	"github.com/goadesign/goa/design"
     5  	"github.com/goadesign/goa/dslengine"
     6  )
     7  
     8  // Type implements the type definition dsl. A type definition describes a data structure consisting
     9  // of attributes. Each attribute has a type which can also refer to a type definition (or use a
    10  // primitive type or nested attibutes). The dsl syntax for define a type definition is the
    11  // Attribute dsl, see Attribute.
    12  //
    13  // On top of specifying any attribute type, type definitions can also be used to describe the data
    14  // structure of a request payload. They can also be used by media type definitions as reference, see
    15  // Reference. Here is an example:
    16  //
    17  //	Type("createPayload", func() {
    18  //		Description("Type of create and upload action payloads")
    19  //		Attribute("name", String, "name of bottle")
    20  //		Attribute("origin", Origin, "Details on wine origin")  // See Origin definition below
    21  //		Required("name")
    22  //	})
    23  //
    24  //	var Origin = Type("origin", func() {
    25  //		Description("Origin of bottle")
    26  //		Attribute("Country")
    27  //	})
    28  //
    29  // This function returns the newly defined type so the value can be used throughout the dsl.
    30  func Type(name string, dsl func()) *design.UserTypeDefinition {
    31  	if design.Design.Types == nil {
    32  		design.Design.Types = make(map[string]*design.UserTypeDefinition)
    33  	} else if _, ok := design.Design.Types[name]; ok {
    34  		dslengine.ReportError("type %#v defined twice", name)
    35  		return nil
    36  	}
    37  
    38  	if !dslengine.IsTopLevelDefinition() {
    39  		dslengine.IncompatibleDSL()
    40  		return nil
    41  	}
    42  
    43  	t := &design.UserTypeDefinition{
    44  		TypeName:            name,
    45  		AttributeDefinition: &design.AttributeDefinition{DSLFunc: dsl},
    46  	}
    47  	if dsl == nil {
    48  		t.Type = design.String
    49  	} else {
    50  		t.Type = make(design.Object)
    51  	}
    52  	design.Design.Types[name] = t
    53  	return t
    54  }
    55  
    56  // ArrayOf creates an array type from its element type. The result can be used
    57  // anywhere a type can. Examples:
    58  //
    59  //	var Bottle = Type("bottle", func() {
    60  //		Attribute("name")
    61  //	})
    62  //
    63  //	Action("update", func() {
    64  //		Params(func() {
    65  //			Param("ids", ArrayOf(Integer))
    66  //		})
    67  //		Payload(ArrayOf(Bottle))
    68  //	})
    69  //
    70  // ArrayOf accepts an optional DSL as second argument which allows providing
    71  // validations for the elements of the array:
    72  //
    73  //	Action("update", func() {
    74  //		Params(func() {
    75  //			Param("ids", ArrayOf(Integer, func() {
    76  //				Minimum(1)
    77  //			}))
    78  //		})
    79  //		Payload(ArrayOf(Bottle))
    80  //	})
    81  //
    82  // If you are looking to return a collection of elements in a Response clause,
    83  // refer to CollectionOf. ArrayOf creates a type, where CollectionOf creates a
    84  // media type.
    85  func ArrayOf(v interface{}, dsl ...func()) *design.Array {
    86  	t := resolveType(v)
    87  	// never return nil to avoid panics, errors are reported after DSL execution
    88  	res := &design.Array{ElemType: &design.AttributeDefinition{Type: design.String}}
    89  	if t == nil {
    90  		dslengine.ReportError("invalid ArrayOf argument: not a type and not a known user type name")
    91  		return res
    92  	}
    93  	if len(dsl) > 1 {
    94  		dslengine.ReportError("ArrayOf: too many arguments")
    95  		return res
    96  	}
    97  	at := design.AttributeDefinition{Type: t}
    98  	if len(dsl) == 1 {
    99  		dslengine.Execute(dsl[0], &at)
   100  	}
   101  	return &design.Array{ElemType: &at}
   102  }
   103  
   104  // HashOf creates a hash map from its key and element types. The result can be
   105  // used anywhere a type can. Examples:
   106  //
   107  //	var Bottle = Type("bottle", func() {
   108  //		Attribute("name")
   109  //	})
   110  //
   111  //	var RatedBottles = HashOf(String, Bottle)
   112  //
   113  //	Action("updateRatings", func() {
   114  //		Payload(func() {
   115  //			Member("ratings", HashOf(String, Integer))
   116  //			Member("bottles", RatedBottles)
   117  //			// Member("bottles", "RatedBottles") // can use name of user type
   118  //	})
   119  //
   120  // HashOf accepts optional DSLs as third and fourth argument which allows
   121  // providing validations for the keys and values of the hash respectively:
   122  //
   123  //	var RatedBottles = HashOf(String, Bottle, func() {
   124  //          Pattern("[a-zA-Z]+") // Validate bottle names
   125  //      })
   126  //
   127  //      func ValidateKey() {
   128  //          Pattern("^foo")
   129  //      }
   130  //
   131  //      func TypeValue() {
   132  //          Metadata("struct:field:type", "json.RawMessage", "encoding/json")
   133  //      }
   134  //
   135  //	var Mappings = HashOf(String, String, ValidateKey, TypeValue)
   136  //
   137  func HashOf(k, v interface{}, dsls ...func()) *design.Hash {
   138  	tk := resolveType(k)
   139  	tv := resolveType(v)
   140  	if tk == nil || tv == nil {
   141  		// never return nil to avoid panics, errors are reported after DSL execution
   142  		dslengine.ReportError("HashOf: invalid type name")
   143  		return &design.Hash{
   144  			KeyType:  &design.AttributeDefinition{Type: design.String},
   145  			ElemType: &design.AttributeDefinition{Type: design.String},
   146  		}
   147  	}
   148  	kat := design.AttributeDefinition{Type: tk}
   149  	vat := design.AttributeDefinition{Type: tv}
   150  	if len(dsls) > 2 {
   151  		// never return nil to avoid panics, errors are reported after DSL execution
   152  		dslengine.ReportError("HashOf: too many arguments")
   153  		return &design.Hash{KeyType: &kat, ElemType: &vat}
   154  	}
   155  	if len(dsls) >= 1 {
   156  		dslengine.Execute(dsls[0], &kat)
   157  		if len(dsls) == 2 {
   158  			dslengine.Execute(dsls[1], &vat)
   159  		}
   160  	}
   161  	return &design.Hash{KeyType: &kat, ElemType: &vat}
   162  }
   163  
   164  func resolveType(v interface{}) design.DataType {
   165  	if t, ok := v.(design.DataType); ok {
   166  		return t
   167  	}
   168  	if name, ok := v.(string); ok {
   169  		if ut, ok := design.Design.Types[name]; ok {
   170  			return ut
   171  		}
   172  		if mt, ok := design.Design.MediaTypes[name]; ok {
   173  			return mt
   174  		}
   175  	}
   176  	return nil
   177  }