-
-
Notifications
You must be signed in to change notification settings - Fork 564
/
Copy pathattribute.go
366 lines (352 loc) Β· 10.6 KB
/
attribute.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
package dsl
import (
"fmt"
"goa.design/goa/eval"
"goa.design/goa/expr"
)
// Attribute describes a field of an object.
//
// An attribute has a name, a type and optionally a default value, an example
// value and validation rules.
//
// The type of an attribute can be one of:
//
// * The primitive types Boolean, Float32, Float64, Int, Int32, Int64, UInt,
// UInt32, UInt64, String or Bytes.
//
// * A user type defined via the Type function.
//
// * An array defined using the ArrayOf function.
//
// * An map defined using the MapOf function.
//
// * An object defined inline using Attribute to define the type fields
// recursively.
//
// * The special type Any to indicate that the attribute may take any of the
// types listed above.
//
// Attribute must appear in ResultType, Type, Attribute or Attributes.
//
// Attribute accepts one to four arguments, the valid usages of the function
// are:
//
// Attribute(name) // Attribute of type String with no description, no
// // validation, default or example value
//
// Attribute(name, fn) // Attribute of type object with inline field
// // definitions, description, validations, default
// // and/or example value
//
// Attribute(name, type) // Attribute with no description, no validation,
// // no default or example value
//
// Attribute(name, type, fn) // Attribute with description, validations,
// // default and/or example value
//
// Attribute(name, type, description) // Attribute with no validation,
// // default or example value
//
// Attribute(name, type, description, fn) // Attribute with description,
// // validations, default and/or
// // example value
//
// Where name is a string indicating the name of the attribute, type specifies
// the attribute type (see above for the possible values), description a string
// providing a human description of the attribute and fn the defining DSL if
// any.
//
// When defining the type inline using Attribute recursively the function takes
// the second form (name and DSL defining the type). The description can be
// provided using the Description function in this case.
//
// Examples:
//
// Attribute("name")
//
// Attribute("driver", Person) // Use type defined with Type function
//
// Attribute("driver", "Person") // May also use the type name
//
// Attribute("name", String, func() {
// Pattern("^foo") // Adds a validation rule
// })
//
// Attribute("driver", Person, func() {
// Required("name") // Add required field to list of
// }) // fields already required in Person
//
// Attribute("name", String, func() {
// Default("bob") // Sets a default value
// })
//
// Attribute("name", String, "name of driver") // Sets a description
//
// Attribute("age", Int32, "description", func() {
// Minimum(2) // Sets both a description and
// // validations
// })
//
// The definition below defines an attribute inline. The resulting type
// is an object with three attributes "name", "age" and "child". The "child"
// attribute is itself defined inline and has one child attribute "name".
//
// Attribute("driver", func() { // Define type inline
// Description("Composite attribute") // Set description
//
// Attribute("name", String) // Child attribute
// Attribute("age", Int32, func() { // Another child attribute
// Description("Age of driver")
// Default(42)
// Minimum(2)
// })
// Attribute("child", func() { // Defines a child attribute
// Attribute("name", String) // Grand-child attribute
// Required("name")
// })
//
// Required("name", "age") // List required attributes
// })
//
func Attribute(name string, args ...interface{}) {
var parent *expr.AttributeExpr
{
switch def := eval.Current().(type) {
case *expr.AttributeExpr:
parent = def
case expr.CompositeExpr:
parent = def.Attribute()
default:
eval.IncompatibleDSL()
return
}
if parent == nil {
eval.ReportError("invalid syntax, attribute %#v has no parent", name)
return
}
if parent.Type == nil {
parent.Type = &expr.Object{}
}
if _, ok := parent.Type.(*expr.Object); !ok {
eval.ReportError("can't define child attribute %#v on attribute of type %s", name, parent.Type.Name())
return
}
}
var attr *expr.AttributeExpr
{
for _, ref := range parent.References {
if att := expr.AsObject(ref).Attribute(name); att != nil {
attr = expr.DupAtt(att)
break
}
}
dataType, description, fn := parseAttributeArgs(attr, args...)
if attr != nil {
if description != "" {
attr.Description = description
}
if dataType != nil {
attr.Type = dataType
}
} else {
attr = &expr.AttributeExpr{
Type: dataType,
Description: description,
}
}
attr.References = parent.References
attr.Bases = parent.Bases
if fn != nil {
eval.Execute(fn, attr)
}
if attr.Type == nil {
// DSL did not contain an "Attribute" declaration
attr.Type = expr.String
}
}
parent.Type.(*expr.Object).Set(name, attr)
}
// Field is syntactic sugar to define an attribute with the "rpc:tag" meta
// set with the value of the first argument.
//
// Field must appear wherever Attribute can.
//
// Field takes the same arguments as Attribute with the addition of the tag
// value as first argument.
//
// Example:
//
// Field(1, "ID", String, func() {
// Pattern("[0-9]+")
// })
//
func Field(tag interface{}, name string, args ...interface{}) {
fn := func() { Meta("rpc:tag", fmt.Sprintf("%v", tag)) }
if len(args) > 0 {
if d, ok := args[len(args)-1].(func()); ok {
old := fn
fn = func() { d(); old() }
args = args[:len(args)-1]
}
}
Attribute(name, append(args, fn)...)
}
// Default sets the default value for an attribute.
//
// Default must appear in an Attribute DSL.
//
// Default takes one parameter: the default value.
func Default(def interface{}) {
a, ok := eval.Current().(*expr.AttributeExpr)
if !ok {
eval.IncompatibleDSL()
return
}
if a.Type != nil && !a.Type.IsCompatible(def) {
eval.ReportError("default value %#v is incompatible with attribute of type %s",
def, expr.QualifiedTypeName(a.Type))
return
}
a.SetDefault(def)
}
// Example provides an example value for a type, a parameter, a header or any
// attribute. Example supports two syntaxes: one syntax accepts two arguments
// where the first argument is a summary describing the example and the second a
// value provided directly or via a DSL which may also specify a long
// description. The other syntax accepts a single argument and is equivalent to
// using the first syntax where the summary is the string "default". When using
// a DSL the Value function can be used to provide the example value.
//
// If no example is explicitly provided in an attribute expression then a random
// example is generated unless the "swagger:example" meta is set to "false".
// See Meta.
//
// Example must appear in a Attributes, Attribute, Params, Param, Headers or
// Header DSL.
//
// Example takes one or two arguments: an optional summary and the example value
// or defining DSL.
//
// Examples:
//
// Params(func() {
// Param("ZipCode:zip-code", String, "Zip code filter", func() {
// Example("Santa Barbara", "93111")
// Example("93117") // same as Example("default", "93117")
// })
// })
//
// Attributes(func() {
// Attribute("ID", Int64, "ID is the unique bottle identifier")
// Example("The first bottle", func() {
// Description("This bottle has an ID set to 1")
// Value(Val{"ID": 1})
// })
// Example("Another bottle", func() {
// Description("This bottle has an ID set to 5")
// Value(Val{"ID": 5})
// })
// })
//
func Example(args ...interface{}) {
if len(args) == 0 {
eval.ReportError("not enough arguments")
return
}
if len(args) > 2 {
eval.ReportError("too many arguments")
return
}
var (
summary string
arg interface{}
)
if len(args) == 1 {
summary = "default"
arg = args[0]
} else {
var ok bool
summary, ok = args[0].(string)
if !ok {
eval.InvalidArgError("summary (string)", summary)
return
}
arg = args[1]
}
if a, ok := eval.Current().(*expr.AttributeExpr); ok {
ex := &expr.ExampleExpr{Summary: summary}
if dsl, ok := arg.(func()); ok {
eval.Execute(dsl, ex)
} else {
ex.Value = arg
}
if ex.Value == nil {
eval.ReportError("example value is missing")
return
}
if a.Type != nil && !a.Type.IsCompatible(ex.Value) {
eval.ReportError("example value %#v is incompatible with attribute of type %s",
ex.Value, a.Type.Name())
return
}
a.UserExamples = append(a.UserExamples, ex)
}
}
func parseAttributeArgs(baseAttr *expr.AttributeExpr, args ...interface{}) (expr.DataType, string, func()) {
var (
dataType expr.DataType
description string
fn func()
ok bool
)
parseDataType := func(expected string, index int) {
if name, ok2 := args[index].(string); ok2 {
// Lookup type by name
if dataType = expr.Root.UserType(name); dataType == nil {
eval.InvalidArgError(expected, args[index])
}
return
}
if dataType, ok = args[index].(expr.DataType); !ok {
eval.InvalidArgError(expected, args[index])
}
}
parseDescription := func(expected string, index int) {
if description, ok = args[index].(string); !ok {
eval.InvalidArgError(expected, args[index])
}
}
parseDSL := func(index int, success, failure func()) {
if fn, ok = args[index].(func()); ok {
success()
return
}
failure()
}
success := func() {}
switch len(args) {
case 0:
if baseAttr != nil {
dataType = baseAttr.Type
} else {
dataType = expr.String
}
case 1:
success = func() {
if baseAttr != nil {
dataType = baseAttr.Type
}
}
parseDSL(0, success, func() { parseDataType("type, type name or func()", 0) })
case 2:
parseDataType("type or type name", 0)
parseDSL(1, success, func() { parseDescription("string or func()", 1) })
case 3:
parseDataType("type or type name", 0)
parseDescription("string", 1)
parseDSL(2, success, func() { eval.InvalidArgError("func()", args[2]) })
default:
eval.ReportError("too many arguments in call to Attribute")
}
return dataType, description, fn
}