Skip to content

Commit

Permalink
Properly propagate user examples (goadesign#2699)
Browse files Browse the repository at this point in the history
From design to OpenAPI specifications.
  • Loading branch information
raphael committed Oct 30, 2020
1 parent 8f1963f commit 54bb729
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 22 deletions.
39 changes: 21 additions & 18 deletions dsl/attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func Default(def interface{}) {
// 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
// description. The second 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.
//
Expand Down Expand Up @@ -279,24 +279,27 @@ func Example(args ...interface{}) {
}
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)
a, ok := eval.Current().(*expr.AttributeExpr)
if !ok {
eval.IncompatibleDSL()
return
}
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()) {
Expand Down
2 changes: 1 addition & 1 deletion dsl/result_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ func Attributes(fn func()) {
eval.IncompatibleDSL()
return
}
eval.Execute(fn, mt)
eval.Execute(fn, mt.AttributeExpr)
}

// mediaTypeToResultType returns the formatted identifier and the result type
Expand Down
19 changes: 19 additions & 0 deletions expr/attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,19 @@ func (a *AttributeExpr) AddMeta(name string, vals ...string) {
a.Meta[name] = append(a.Meta[name], vals...)
}

// ExtractUserExamples return the examples defined in the design directly on the
// attribute or on its type.
func (a *AttributeExpr) ExtractUserExamples() []*ExampleExpr {
if len(a.UserExamples) > 0 {
return a.UserExamples
}
ut, ok := a.Type.(UserType)
if !ok {
return nil
}
return ut.Attribute().ExtractUserExamples()
}

// Debug dumps the attribute to STDOUT in a goa developer friendly way.
func (a *AttributeExpr) Debug(prefix string) { a.debug(prefix, make(map[*AttributeExpr]int), 0) }
func (a *AttributeExpr) debug(prefix string, seen map[*AttributeExpr]int, indent int) {
Expand Down Expand Up @@ -535,6 +548,12 @@ func (a *AttributeExpr) debug(prefix string, seen map[*AttributeExpr]int, indent
if v := a.Validation; v != nil {
v.Debug(indent + 1)
}
if len(a.UserExamples) > 0 {
fmt.Printf("%sexamples\n", tab)
for _, ex := range a.UserExamples {
fmt.Printf("%s- %s: %#v\n", tab+" ", ex.Summary, ex.Value)
}
}
if len(a.Meta) > 0 {
fmt.Printf("%smeta\n", tab)
for k, v := range a.Meta {
Expand Down
4 changes: 2 additions & 2 deletions expr/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ const (
// isn't such a value then Example computes a random value for the attribute
// using the given random value producer.
func (a *AttributeExpr) Example(r *Random) interface{} {
if l := len(a.UserExamples); l > 0 {
if ex := a.ExtractUserExamples(); len(ex) > 0 {
// Return the last item in the slice so that examples can be overridden
// in the DSL. Overridden examples are always appended to the UserExamples
// slice.
return a.UserExamples[l-1].Value
return ex[len(ex)-1].Value
}

if value, ok := a.Meta.Last("swagger:example"); ok && value == "false" {
Expand Down
4 changes: 4 additions & 0 deletions expr/mapped_attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ func (ma *MappedAttributeExpr) Remap() {
ma.reverseMap[elems[1]] = elems[0]
}
}

// Conserve examples defined on user type
ma.AttributeExpr.UserExamples = ma.ExtractUserExamples()

ma.Type = n
}

Expand Down
3 changes: 2 additions & 1 deletion expr/result_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ func projectSingle(m *ResultTypeExpr, view string, seen ...map[string]*Attribute
ut.TypeName = typeName
ut.UID = id
ut.AttributeExpr.Type = Dup(v.Type)
ut.AttributeExpr.UserExamples = v.UserExamples
projected := &ResultTypeExpr{
Identifier: id,
UserTypeExpr: ut,
Expand All @@ -289,7 +290,7 @@ func projectSingle(m *ResultTypeExpr, view string, seen ...map[string]*Attribute
}}

projectedObj := projected.Type.(*Object)
mtObj := m.Type.(*Object)
mtObj := AsObject(m.Type)
for _, nat := range *viewObj {
if at := mtObj.Attribute(nat.Name); at != nil {
pat, err := projectRecursive(at, nat, view, seen...)
Expand Down

0 comments on commit 54bb729

Please sign in to comment.