github.com/ccrossley/goa@v1.3.1/design/apidsl/response.go (about)

     1  package apidsl
     2  
     3  import (
     4  	"github.com/goadesign/goa/design"
     5  	"github.com/goadesign/goa/dslengine"
     6  )
     7  
     8  // Response implements the response definition DSL. Response takes the name of the response as
     9  // first parameter. goa defines all the standard HTTP status name as global variables so they can be
    10  // readily used as response names. The response body data type can be specified as second argument.
    11  // If a type is specified it overrides any type defined by in the response media type. Response also
    12  // accepts optional arguments that correspond to the arguments defined by the corresponding response
    13  // template (the response template with the same name) if there is one, see ResponseTemplate.
    14  //
    15  // A response may also optionally use an anonymous function as last argument to specify the response
    16  // status code, media type and headers overriding what the default response or response template
    17  // specifies:
    18  //
    19  //        Response(OK, "text/plain")              // OK response template accepts one argument:
    20  //                                                // the media type identifier
    21  //
    22  //        Response(OK, BottleMedia)               // or a media type defined in the design
    23  //
    24  //        Response(OK, "application/vnd.bottle")  // optionally referred to by identifier
    25  //
    26  //        Response(OK, func() {
    27  //                Media("application/vnd.bottle") // Alternatively media type is set with Media
    28  //        })
    29  //
    30  //        Response(OK, BottleMedia, func() {
    31  //                Headers(func() {                // Headers list the response HTTP headers
    32  //                        Header("X-Request-Id")  // Header syntax is identical to Attribute's
    33  //                })
    34  //        })
    35  //
    36  //        Response(OK, BottleMedia, func() {
    37  //                Status(201)                     // Set response status (overrides template's)
    38  //        })
    39  //
    40  //        Response("MyResponse", func() {         // Define custom response (using no template)
    41  //                Description("This is my response")
    42  //                Media(BottleMedia)
    43  //                Headers(func() {
    44  //                        Header("X-Request-Id", func() {
    45  //                                Pattern("[a-f0-9]+")
    46  //                        })
    47  //                })
    48  //                Status(200)
    49  //        })
    50  //
    51  // goa defines a default response template for all the HTTP status code. The default template simply sets
    52  // the status code. So if an action can return NotFound for example all it has to do is specify
    53  // Response(NotFound) - there is no need to specify the status code as the default response already
    54  // does it, in other words:
    55  //
    56  //	Response(NotFound)
    57  //
    58  // is equivalent to:
    59  //
    60  //	Response(NotFound, func() {
    61  //		Status(404)
    62  //	})
    63  //
    64  // goa also defines a default response template for the OK response which takes a single argument:
    65  // the identifier of the media type used to render the response. The API DSL can define additional
    66  // response templates or override the default OK response template using ResponseTemplate.
    67  //
    68  // The media type identifier specified in a response definition via the Media function can be
    69  // "generic" such as "text/plain" or "application/json" or can correspond to the identifier of a
    70  // media type defined in the API DSL. In this latter case goa uses the media type definition to
    71  // generate helper response methods. These methods know how to render the views defined on the media
    72  // type and run the validations defined in the media type during rendering.
    73  func Response(name string, paramsAndDSL ...interface{}) {
    74  	switch def := dslengine.CurrentDefinition().(type) {
    75  	case *design.ActionDefinition:
    76  		if def.Responses == nil {
    77  			def.Responses = make(map[string]*design.ResponseDefinition)
    78  		}
    79  		if _, ok := def.Responses[name]; ok {
    80  			dslengine.ReportError("response %s is defined twice", name)
    81  			return
    82  		}
    83  		if resp := executeResponseDSL(name, paramsAndDSL...); resp != nil {
    84  			if resp.Status == 200 && resp.MediaType == "" {
    85  				resp.MediaType = def.Parent.MediaType
    86  				resp.ViewName = def.Parent.DefaultViewName
    87  			}
    88  			resp.Parent = def
    89  			def.Responses[name] = resp
    90  		}
    91  
    92  	case *design.ResourceDefinition:
    93  		if def.Responses == nil {
    94  			def.Responses = make(map[string]*design.ResponseDefinition)
    95  		}
    96  		if _, ok := def.Responses[name]; ok {
    97  			dslengine.ReportError("response %s is defined twice", name)
    98  			return
    99  		}
   100  		if resp := executeResponseDSL(name, paramsAndDSL...); resp != nil {
   101  			if resp.Status == 200 && resp.MediaType == "" {
   102  				resp.MediaType = def.MediaType
   103  				resp.ViewName = def.DefaultViewName
   104  			}
   105  			resp.Parent = def
   106  			def.Responses[name] = resp
   107  		}
   108  
   109  	default:
   110  		dslengine.IncompatibleDSL()
   111  	}
   112  }
   113  
   114  // Status sets the Response status.
   115  func Status(status int) {
   116  	if r, ok := responseDefinition(); ok {
   117  		r.Status = status
   118  	}
   119  }
   120  
   121  func executeResponseDSL(name string, paramsAndDSL ...interface{}) *design.ResponseDefinition {
   122  	var params []string
   123  	var dsl func()
   124  	var ok bool
   125  	var dt design.DataType
   126  	if len(paramsAndDSL) > 0 {
   127  		d := paramsAndDSL[len(paramsAndDSL)-1]
   128  		if dsl, ok = d.(func()); ok {
   129  			paramsAndDSL = paramsAndDSL[:len(paramsAndDSL)-1]
   130  		}
   131  		if len(paramsAndDSL) > 0 {
   132  			t := paramsAndDSL[0]
   133  			if dt, ok = t.(design.DataType); ok {
   134  				paramsAndDSL = paramsAndDSL[1:]
   135  			}
   136  		}
   137  		params = make([]string, len(paramsAndDSL))
   138  		for i, p := range paramsAndDSL {
   139  			params[i], ok = p.(string)
   140  			if !ok {
   141  				dslengine.ReportError("invalid response template parameter %#v, must be a string", p)
   142  				return nil
   143  			}
   144  		}
   145  	}
   146  	var resp *design.ResponseDefinition
   147  	if len(params) > 0 {
   148  		if tmpl, ok := design.Design.ResponseTemplates[name]; ok {
   149  			resp = tmpl.Template(params...)
   150  		} else if tmpl, ok := design.Design.DefaultResponseTemplates[name]; ok {
   151  			resp = tmpl.Template(params...)
   152  		} else {
   153  			dslengine.ReportError("no response template named %#v", name)
   154  			return nil
   155  		}
   156  	} else {
   157  		if ar, ok := design.Design.Responses[name]; ok {
   158  			resp = ar.Dup()
   159  		} else if ar, ok := design.Design.DefaultResponses[name]; ok {
   160  			resp = ar.Dup()
   161  			resp.Standard = true
   162  		} else {
   163  			resp = &design.ResponseDefinition{Name: name}
   164  		}
   165  	}
   166  	if dsl != nil {
   167  		if !dslengine.Execute(dsl, resp) {
   168  			return nil
   169  		}
   170  		resp.Standard = false
   171  	}
   172  	if dt != nil {
   173  		if mt, ok := dt.(*design.MediaTypeDefinition); ok {
   174  			resp.MediaType = mt.Identifier
   175  		}
   176  		resp.Type = dt
   177  		resp.Standard = false
   178  	}
   179  	return resp
   180  }