Code Monkey home page Code Monkey logo

goa's Introduction

Goa

Design First!

Release Go Doc GitHub Action: Test Go Report Card Software License
Slack: Goa Slack: Sign-up Twitter: @goadesign

Wizard Logo

Goa Design Wizard

Use the Goa Design Wizard to:

  • Create Goa designs in seconds
  • Review existing designs
  • Explore the Goa DSL

(requires a ChatGPT Plus subscription)

Overview

Goa takes a different approach to building services by making it possible to describe the design of the service API using a simple Go DSL. Goa uses the description to generate specialized service helper code, client code and documentation. Goa is extensible via plugins, for example the goakit plugin generates code that leverage the Go kit library.

The service design describes the transport independent layer of the services in the form of simple methods that accept a context and a payload and return a result and an error. The design also describes how the payloads, results and errors are serialized in the transport (HTTP or gRPC). For example a service method payload may be built from an HTTP request by extracting values from the request path, headers and body. This clean separation of layers makes it possible to expose the same service using multiple transports. It also promotes good design where the service business logic concerns are expressed and implemented separately from the transport logic.

The Goa DSL consists of Go functions so that it may be extended easily to avoid repetition and promote standards. The design code itself can easily be shared across multiple services by simply importing the corresponding Go package again promoting reuse and standardization across services.

Sponsors

incident.io

incident.io: Bounce back stronger after every incident

Use our platform to empower your team to run incidents end-to-end. Rapidly fix and learn from incidents, so you can build more resilient products.

Learn more
Speakeasy

Speakeasy: Enterprise DevEx for your API

Our platform makes it easy to create feature-rich production ready SDKs. Speed up integrations and reduce errors by giving your API the DevEx it deserves.

Integrate with Goa

Code Generation

The Goa tool accepts the Go design package import path as input and produces the interface as well as the glue that binds the service and client code with the underlying transport. The code is specific to the API so that for example there is no need to cast or "bind" any data structure prior to using the request payload or response result. The design may define validations in which case the generated code takes care of validating the incoming request payload prior to invoking the service method on the server, and validating the response prior to invoking the client code.

Installation

go install goa.design/goa/v3/cmd/goa@v3

Current Release: v3.16.1

Getting Started

1. Design

Create a new Goa project:

mkdir -p calcsvc/design
cd calcsvc
go mod init calcsvc

Create the file design.go in the design directory with the following content:

package design

import . "goa.design/goa/v3/dsl"

// API describes the global properties of the API server.
var _ = API("calc", func() {
        Title("Calculator Service")
        Description("HTTP service for multiplying numbers, a goa teaser")
        Server("calc", func() {
                Host("localhost", func() { URI("http://localhost:8088") })
        })
})

// Service describes a service
var _ = Service("calc", func() {
        Description("The calc service performs operations on numbers")
        // Method describes a service method (endpoint)
        Method("multiply", func() {
                // Payload describes the method payload
                // Here the payload is an object that consists of two fields
                Payload(func() {
                        // Attribute describes an object field
                        Attribute("a", Int, "Left operand")
                        Attribute("b", Int, "Right operand")
                        // Both attributes must be provided when invoking "multiply"
                        Required("a", "b")
                })
                // Result describes the method result
                // Here the result is a simple integer value
                Result(Int)
                // HTTP describes the HTTP transport mapping
                HTTP(func() {
                        // Requests to the service consist of HTTP GET requests
                        // The payload fields are encoded as path parameters
                        GET("/multiply/{a}/{b}")
                        // Responses use a "200 OK" HTTP status
                        // The result is encoded in the response body
                        Response(StatusOK)
                })
        })
})

This file contains the design for a calc service which accepts HTTP GET requests to /multiply/{a}/{b} where {a} and {b} are placeholders for integer values. The API returns the product of a multiplied by b in the HTTP response body.

2. Implement

Now that the design is done, let's run goa on the design package. In the calcsvc directory run:

goa gen calcsvc/design

This produces a gen directory with the following directory structure:

gen
β”œβ”€β”€ calc
β”‚   β”œβ”€β”€ client.go
β”‚   β”œβ”€β”€ endpoints.go
β”‚   └── service.go
└── http
    β”œβ”€β”€ calc
    β”‚   β”œβ”€β”€ client
    β”‚   β”‚   β”œβ”€β”€ cli.go
    β”‚   β”‚   β”œβ”€β”€ client.go
    β”‚   β”‚   β”œβ”€β”€ encode_decode.go
    β”‚   β”‚   β”œβ”€β”€ paths.go
    β”‚   β”‚   └── types.go
    β”‚   └── server
    β”‚       β”œβ”€β”€ encode_decode.go
    β”‚       β”œβ”€β”€ paths.go
    β”‚       β”œβ”€β”€ server.go
    β”‚       └── types.go
    β”œβ”€β”€ cli
    β”‚   └── calc
    β”‚       └── cli.go
    β”œβ”€β”€ openapi.json
    └── openapi.yaml

7 directories, 15 files
  • calc contains the service endpoints and interface as well as a service client.
  • http contains the HTTP transport layer. This layer maps the service endpoints to HTTP handlers server side and HTTP client methods client side. The http directory also contains a complete OpenAPI 3.0 spec for the service.

The goa tool can also generate example implementations for both the service and client. These examples provide a good starting point:

goa example calcsvc/design

calc.go
cmd/calc-cli/http.go
cmd/calc-cli/main.go
cmd/calc/http.go
cmd/calc/main.go

The tool generated the main functions for two commands: one that runs the server and one the client. The tool also generated a dummy service implementation that prints a log message. Again note that the example command is intended to generate just that: an example, in particular it is not intended to be re-run each time the design changes (as opposed to the gen command which should be re-run each time the design changes).

Let's implement our service by providing a proper implementation for the multiply method. Goa generated a payload struct for the multiply method that contains both fields. Goa also generated the transport layer that takes care of decoding the request so all we have to do is to perform the actual multiplication. Edit the file calc.go and change the code of the multiply function as follows:

// Multiply returns the multiplied value of attributes a and b of p.
func (s *calcsrvc) Multiply(ctx context.Context, p *calc.MultiplyPayload) (res int, err error) {
        return p.A * p.B, nil
}

That's it! we have now a full-fledged HTTP service with a corresponding OpenAPI specification and a client tool.

3. Run

Now let's compile and run the service:

cd cmd/calc
go build
./calc
[calcapi] 16:10:47 HTTP "Multiply" mounted on GET /multiply/{a}/{b}
[calcapi] 16:10:47 HTTP server listening on "localhost:8088"

Open a new console and compile the generated CLI tool:

cd calcsvc/cmd/calc-cli
go build

and run it:

./calc-cli calc multiply -a 2 -b 3
6

The tool includes contextual help:

./calc-cli --help

Help is also available on each command:

./calc-cli calc multiply --help

Now let's see how robust our code is and try to use non integer values:

./calc-cli calc multiply -a 1 -b foo
invalid value for b, must be INT
run './calccli --help' for detailed usage.

The generated code validates the command line arguments against the types defined in the design. The server also validates the types when decoding incoming requests so that your code only has to deal with the business logic.

The service now returns an integer, but most OpenAPI services expect JSON. Lets fix that now!

In design.go, change Result(Int) so it reads like this:

Result(func() {
    Attribute("result", Int)
    Required("result")
})

Inside of calc.go, replace the func block:

func (s *calcsrvc) Multiply(ctx context.Context, p *calc.MultiplyPayload) (res *calc.MultiplyResult, err error) {
	return &calc.MultiplyResult{Result: p.A * p.B}, nil
}

Finally rebuild the app by running the build parts again:

goa gen calcsvc/design
cd cmd/calc
go build
./calc

You can now test and verify that your service is returning JSON:

curl -X 'GET' 'http://localhost:8088/multiply/10/10' -H 'accept: application/json' | jq .

If all goes well, you should see:

{
  "result": 100
}

4. Document

The http directory contains OpenAPI 2.0 and 3.0 specifications in both YAML and JSON format.

The specification can easily be served from the service itself using a file server. The Files DSL function makes it possible to serve a static file. Edit the file design/design.go and add:

var _ = Service("openapi", func() {
	// Serve the file gen/http/openapi3.json for requests sent to
	// /openapi.json. The HTTP file system is created below.
	Files("/openapi.json", "openapi3.json")
})

Re-run goa gen calcsvc/design and note the new directory gen/openapi and gen/http/openapi which contain the implementation for a HTTP handler that serves the openapi.json file.

All we need to do is mount the handler on the service mux. Add the corresponding import statement to cmd/calc/http.go:

import openapisvr "calcsvc/gen/http/openapi/server"

and mount the handler by adding the following line in the same file and after the mux creation (e.g. one the line after the // Configure the mux. comment):

svr := openapisvr.New(nil, mux, dec, enc, nil, nil, http.Dir("../../gen/http"))
openapisvr.Mount(mux, svr)

That's it! we now have a self-documenting service. Stop the running service with CTRL-C. Rebuild and re-run it then make requests to the newly added /openapi.json endpoint:

^C[calcapi] 16:17:37 exiting (interrupt)
[calcapi] 16:17:37 shutting down HTTP server at "localhost:8088"
[calcapi] 16:17:37 exited
go build
./calc

In a different console:

curl localhost:8088/openapi.json
{"openapi":"3.0.3","info":{"title":"Calculator Service","description":...

Resources

Docs

The goa.design website provides a high level overview of Goa and the DSL.

In particular the page Implementing a Goa Service explains how to leverage the generated code to implement an HTTP or gRPC service.

The DSL Go Doc contains a fully documented reference of all the DSL functions.

Instrumentation and System Example

The clue project provides observability packages that work in tandem with Goa. The packages cover logging, tracing, metrics, health checks and service client mocking. clue also includes a fully featured example consisting of three instrumented Goa microservices that communicate with each other.

Getting Started Guides

A couple of Getting Started guides produced by the community.

Joseph Ocol from Pelmorex Corp. goes through a complete example writing a server and client service using both HTTP and gRPC transports.

GOA Design Tutorial

Gleidson Nascimento goes through how to create a complete service that using both CORS and JWT based authentication to secure access.

API Development in Go Using Goa

Examples

The examples directory contains simple examples illustrating basic concepts.

Troubleshooting

Q: I'm seeing an error that says:

generated code expected goa.design/goa/v3/codegen/generator to be present in the vendor directory, see documentation for more details

How do I fix this?

A: If you are vendoring your dependencies Goa will not attempt to satisfy its dependencies by retrieving them with go get. If you see the above error message, it means that the goa.design/goa/v3/codegen/generator package is not included in your vendor directory.

To fix, ensure that goa.design/goa/v3/codegen/generator is being imported somewhere in your project. This can be as a bare import (e.g. import _ "goa.design/goa/v3/codegen/generator") in any file or you can use a dedicated tools.go file (see Manage Go tools via Go modules and golang/go/issues/25922 for more details.) Finally, run go mod vendor to ensure the imported packages are properly vendored.

Contributing

See CONTRIBUTING.

goa's People

Contributors

abourget avatar arangamani avatar aslakknutsen avatar bits01 avatar bketelsen avatar cquinn avatar cubic3d avatar deepsource-autofix[bot] avatar dependabot[bot] avatar derekperkins avatar dominicm avatar douglaswth avatar ernesto-jimenez avatar ikawaha avatar kitagry avatar maleck13 avatar michaelurman avatar moorereason avatar nii236 avatar nikolaas avatar nitinmohan87 avatar on99 avatar psschroeter avatar raphael avatar sevein avatar slatteryjim avatar smessier avatar tchssk avatar tmcladam avatar townewgokgok avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

goa's Issues

Rendering Media Type with nullable primitive attribute

Hi,

As discussed on Slack#goa, I'm summing up problem with optional/nullable Media Type attributes.

All primitive attributes generated by Goa are being generated with the type stated in the design. Such generation however doesn't allow these primitive types be nullable and forces producer of Media Type instance to set (keep) a default value instead.

This problem is not present for Attributes having custom Media Type (Qux), which are being generated with type of pointer to the Media Type (Qux *Qux) - pointer is nullable.

Hence I'm missing a possibility to state in the Media Type Attribute design that the Attribute is nullable, so it would be generated with pointer type. Something like this:

Baz := MediaType("Baz", func() {
    Attributes(func() {
        Attribute("Count", Integer)
    }
}
MyMediaType := MediaType("MyMediaType", func() {
    Attributes(func() {
        Attribute("Foo", Integer, Nullable) // missing
        Attribute("Bar", Integer) // already present
        Attribute("Baz", Baz) // already present
    }
}

Would generate following:

struct MyMediaType {
    Foo *int, // missing
    Bar int, // already present
    Baz *Baz, // already present
}

Generated error - cannot build

I try generating code from your examples/cellar/design and get some error from generated code.

./account.go:16: cannot use AccountController literal (type *AccountController) as type AccountController in return argument
./bottle.go:16: cannot use BottleController literal (type *BottleController) as type BottleController in return argument
./main.go:20: not enough arguments in call to NewAccountController
./main.go:21: cannot use c (type AccountController) as type app.AccountController in argument to app.MountAccountController:
    AccountController does not implement app.AccountController (Create method has pointer receiver)
./main.go:23: not enough arguments in call to NewBottleController
./main.go:24: cannot use c2 (type BottleController) as type app.BottleController in argument to app.MountBottleController:
    BottleController does not implement app.BottleController (Create method has pointer receiver)

Here what I get from generated code https://github.com/MathieuDoyon/goa-test

"bare" value support

My use case: wrap K/V stores with a type interface (github.com/asteris-llc/gestalt, functionality is currently in the feature/web fork).

I'd like to use interface{} as a payload/return type (the value in a K/V store), instead of having to marshal/unmarshal that myself. In addition, it'd be super if I could use Hash(String, Any) as a media type directly (for a hash of values)

Using ArrayOf(HashOf(String, Any)) causes issues when building the project

package design

import (
    . "github.com/raphael/goa/design"
    . "github.com/raphael/goa/design/dsl"
)

var _ = API("goa_is_awesome", func() {
    Title("Goa is awesome")
})

var _ = Resource("foo", func() {
    Action("create", func() {
        Routing(
            POST("/foo"),
        )
        Payload(CreatePayload)
        Response(OK)
    })
})

var CreatePayload = Type("input", func() {
    Attribute("details", ArrayOf(HashOf(String, Any)), "The details")
})

causes the following issue when building the project after code generation:

# git.arangamani.net/arangamani/playground/goa-issue/app
app/user_types.go:38: cannot use tmp4 (type map[interface {}]interface {}) as type map[string]interface {} in assignment

Here is the block of generated code that causes the issue

func MarshalInput(source *Input, inErr error) (target map[string]interface{}, err error) {
    err = inErr
    tmp2 := map[string]interface{}{}
    if source.Details != nil {
        tmp3 := make([]map[string]interface{}, len(source.Details))
        for i, r := range source.Details {
            tmp4 := make(map[interface{}]interface{}, len(r))
            for k, v := range r {
                var mk interface{}
                mk = k
                var mv interface{}
                mv = v
                tmp4[mk] = mv
            }
            tmp3[i] = tmp4
        }
        tmp2["details"] = tmp3
    }
    target = tmp2
    return
}

To be specific: the statement tmp3[i] = tmp4 is the one causing the error.

Defining a Parent in the design does not create parent parameters for swagger specs

Hi,

I think there is an issue with the swagger.json generated through design.

Let's say you have something like:

  var _ = Resource("parent", func() {
    //...
    BasePath("parents")
    Action("show", func() {
        Routing(GET("/:parentID"))
        Params(func() {
            Param("parentID", String, "Parent ID")
        })
                //...
    })
    //...
  }

  //...

  var _ = Resource("child", func() {
    //...
    BasePath("children")
    Parent("parent")
    Action("show", func() {
        Routing(GET("/:childID"))
        Params(func() {
            Param("childID", String, "Child ID")
        })
                //...
    })
    //...
  }

Then, you have following endpoints open:

GET /parents/{ParentID}
GET /parents/{ParentID}/children/{ChildrenID}

If you use the auto-generated swagger.json with the official swagger-UI, for the case of GET parent, everything works fine.

But for the case of GET children, the UI does not offer the parameter parentID. However it does, correctly, the parameter childID.

Running the action, takes you to a failure, since you cannot supply the parentID, any how.

Therefore, my assumption is that, in design, when you state Parent('parent') in design and auto-generate the code, the parent parameters are not checked to properly generate the swagger.json file, for the children case.

Not sure if the explanation is clear.

Any tip on this?

Thanks in advance!

Migrate core middleware to goa-middleware

I think that all the middleware should exist in one place, either here in the main goa repo or over in github.com/raphael/goa-middleware. As is, there are a few "blessed" middleware that are a part of the main package.

The most compelling reason I can think of to include middleware in goa is if there is any need to share types through an internal package.

Documentation issue - installation

Hi,

I started from http://goa.design/getting-started.html
I installed following instructions from : http://goa.design/goagen.html

Neither one mentions use/dependency on goimports so initially I hadn't installed it.

The code gen completes successfully but the resulting code doesn't build due to incorrect imports - took me a while to stumble on the requirement for goimports.

I suggest you make the code gen step fail if goimports isn't installed. Can you also update the docs on the web page we reference to it.

Finally, the example on the getting started needs updating, app.Bottle isn't valid, needs changing to app.GoaExampleBottle.

Thanks,
Alan

API Version and Schemes

I create a test description:

package design

import (
    . "github.com/raphael/goa/design"
    . "github.com/raphael/goa/design/dsl"
)

var _ = API("I_love_goa", func() {
    Title("Test of API")
    Host("localhost:8080")
    Scheme("http")
    BasePath("/api/v1")
})

var _ = Resource("test", func() {
    BasePath("/test")
    DefaultMedia(TestMedia)
    Action("list", func() {
        Routing(
            GET(""),
        )
        Response(OK)
    })
})

var TestMedia = MediaType("application/vnd.test+json", func() {
    Attributes(func() {
        Attribute("id", String, "ID of object")
    })
    View("default", func() {
        Attribute("id")
        Required("id")
    })
})

and generate swagger.json file:

{  
   "swagger":"2.0",
   "info":{  
      "title":"Test of API",
      "version":""
   },
   "host":"localhost:8080",
   "basePath":"/api/v1",
   "schemes":[  
      "http"
   ],
   "consumes":[  
      "application/json"
   ],
   "produces":[  
      "application/json"
   ],
   "paths":{  
      "/test":{  
         "get":{  
            "operationId":"test#list",
            "consumes":[  
               "application/json"
            ],
            "produces":[  
               "application/json"
            ],
            "responses":{  
               "200":{  
                  "description":"",
                  "schema":{  
                     "$ref":"#/definitions/Test"
                  }
               }
            },
            "schemes":[  
               "https"
            ]
         }
      }
   },
   "definitions":{  
      "Test":{  
         "title":"Mediatype identifier: application/vnd.test+json",
         "type":"object",
         "properties":{  
            "id":{  
               "type":"string",
               "description":"ID of place"
            }
         }
      }
   }
}
  • How I can set a info.version in definition?
  • In schemes defined only http, but in resource definition I have https.
  • Is consumes and produces always only application/json?
  • Empty description in responses.

CORS for Swagger UI is broken

Hi,

I am struggle on this. Let's see if anybody can help me.

Since I have pulled the last version, my auto-generated swagger/swagger.go looks like:

func MountController(service goa.Service) {
    service.ServeFiles("/swagger.json", "swagger/swagger.json")
}

This makes sense since swagger.json is a static file.

However, before it looked like:

// MountController mounts the swagger spec controller under "/swagger.json".
func MountController(service goa.Service) {
    ctrl := service.NewController("Swagger")
    service.Info("mount", "ctrl", "Swagger", "action", "Show", "route", "GET /swagger.json")
    h := ctrl.NewHTTPRouterHandle("Show", getSwagger)
    service.HTTPHandler().(*httprouter.Router).Handle("GET", "/swagger.json", h)
}

// getSwagger is the httprouter handle that returns the Swagger spec.
// func getSwagger(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
func getSwagger(ctx *goa.Context) error {
    ctx.Header().Set("Content-Type", "application/swagger+json")
    ctx.Header().Set("Cache-Control", "public, max-age=3600")
    return ctx.Respond(200, []byte(spec))
}

// Generated spec
const spec = `{...} `

If I understood, this old version sets a handler for the given path to serve the swagger spec.

Now, once we set up CORS using the new goa-middleware/cors the library parses handlers adding required headers.

However, now the controller does not tie path to handler, therefore, the request serves the file but does not change any header to allow CORS.

Any tip to solve this?

Thanks!

goagen command help output no longer displays subcommands

goagen --help
usage: codegen help [...]

Show help.

Flags:
--help Show context-sensitive help (also try --help-long and --help-man).
--version Show application version.
-o, --out="/home/bketelsen" output directory
-d, --design=DESIGN design package path
--debug enable debug mode

Args:
[] Show help on command.

Subcommands:
{END of output from help command}

question: DSL: Require nested attribute in payload

Hi,

how would one mark a nested attribute as required in a payload?

For example:

var DataType = Type("DataType", func() {
    Attribute("id", Integer)
})

var TestPayload = Type("TestPayload", func() {
    Attribute("test", String)
    Attribute("data", DataType)
})

....

Payload(TestPayload, func() {
    Required("test")
})

How would one mark the test DataType id attribute as required in the Payload?

ArrayOf(A) + MinLength generates invalid code

Using the attached design file, goagen app generates invalid code. The exact errors at compile time are:

app/contexts.go:73: cannot use tmp1 (type []string) as type string in argument to goa.InvalidLengthError
app/media_types.go:46: cannot use mt.Strings (type []string) as type string in argument to goa.InvalidLengthError
app/media_types.go:67: cannot use source.Strings (type []string) as type string in argument to goa.InvalidLengthError
app/media_types.go:104: cannot use tmp7 (type []string) as type string in argument to goa.InvalidLengthError

If there's something I'm doing wrong here, I'd love to know what it is. Otherwise, this looks like a bug?


package test

import (
    . "github.com/raphael/goa/design"
    . "github.com/raphael/goa/design/dsl"
)

func init() {
    API("test", func() {
        Title("reproduce a bug, maybe")
    })

    test := MediaType("application/vnd.test+json", func() {
        attrs := func() {
            Attribute("strings", ArrayOf(String), func() { MinLength(1) })

            Required("strings")
        }

        Attributes(attrs)
        View("default", attrs)
    })

    Resource("test", func() {
        Action("create", func() {
            Routing(POST("/"))

            Payload(test)

            Response(Created, func() { Media(test) })
        })
    })
}

Support for type "File"

The swagger spec allows for something like:

name: avatar
in: formData
description: The avatar of the user
required: true
type: file

with type: file and in: formData .. I've also seen in: body over here

I've crawling the docs and I can't see a way to currently handle a file upload.

Have you guys needed to do such thing? Do you simply use a String field and base64 encode everything, and write it as text in the description of your service ?

Code generation error when using DSL Minimum(0)

The generated validation code for Minimum(0) will be something like:

if v > <no value> {
...
}

which is invalid.

The template for generating this is https://github.com/raphael/goa/blob/master/goagen/codegen/validation.go#L242. It use the value of the key min of the map data to determine whether the template is dealing with Minimum() or Maximum().

From the document of text/template, {{if pipeline}} is checking against the empty value of pipeline, where the empty value is defined as:

The empty values are false, 0, any nil pointer or interface value, and any array, slice, map, or string of length zero.

So for Minimum(0), the template will actually think it is dealing with Maximum(), but the corresponding key max is missing from data, thus the generated text will be > <no value>.

I think MinLength() also has this problem, but no one will use MinLength(0), so it's fine.

One solution for this, I think, would be to abuse the data map to store the actual validation type in it, and check against that in the template.

Handle cyclic Type dependencies

The following:

package design

import (
    . "github.com/raphael/goa/design"
    . "github.com/raphael/goa/design/dsl"
)

var Account = MediaType("application/vnd.example.account", func() {
    Attributes(func() {
        Attribute("bottles", CollectionOf("application/vnd.example.bottle"))
    })
    View("default", func() {
        Attribute("bottles")
    })
})

var Bottle = MediaType("application/vnd.example.bottle", func() {
    Attributes(func() {
        Attribute("account", Account)
    })
    View("default", func() {
        Attribute("account")
    })
})

causes goagen to hang as the rendering code does not handle the cyclic dependency properly and loops indefinitely. Add code to detect this situation.

Support Security specs

In a swagger file, you can specify:

securityDefinitions:
  jwtAuth:
    type: apiKey
    name: Authorization
    in: header
security:
  - jwtAuth: []

and the Sagger edito and other things will help you authenticate. It also specifies to the user which authentication mechanism you want to use.

Also, it seems possible to list the security per action or resources..

It seems that would be a great addition to the DSL, having some sort of SecurityDefinition(func() {} and Security("method") somewhere, wouldn't it ?

Refs: http://swagger.io/specification/#securityDefinitionsObject

import of `fmt` missing and/or imported but not used

Generated files for app import fmt but do not use it. Prevents building with those generated files.

I am using gb for dependency management, but this does not appear to be related to that.

Using go version go1.5.1 darwin/amd64

package design

import (
    . "github.com/raphael/goa/design"
    . "github.com/raphael/goa/design/dsl"
)

var _ = API("example", func() {
    Title("The API")
    Description("An API")
    Scheme("http")
    Host("localhost:8080")
})

var _ = Resource("story", func() {
    BasePath("/stories")
    DefaultMedia(StoryMedia)
    Action("show", func() {
        Description("Get a Story with a given ID")
        Routing(GET("/:storyID"))
        Params(func() {
            Param("storyID", Integer, "Story ID")
        })
        Response(OK)
        Response(NotFound)
    })
})

var StoryMedia = MediaType("application/vnd.goa.example.story+json", func() {
    Description("A Story")
    Attributes(func() {
        Attribute("id", Integer, "Unique Story ID")
        Attribute("title", String, "Title of Story")
    })

    View("default", func() {
        Attribute("id")
        Attribute("title")
    })
})

Running the following command to build app package:

./bin/goagen -d project/design app -o src/project

And this to build:

gb build project

Output (truncated):

contexts.go:50: undefined: fmt in fmt.Errorf
hrefs.go:17: undefined: fmt in fmt.Sprintf
media_types.go:17: imported and not used: "fmt"
FATAL: command "build" failed: exit status 2

`Unmarshal(Marshal(userType))` fails with []string field

Trying to validate an object by Marshalling it and then Unmarshalling it, but getting this error:

{
   "id":    3,
   "title": "invalid attribute type",
   "msg":   "type of load.Database.Global.ReadHosts must be array but got value []string{\"localhost\"}"
}

There seems to be an asymmetry in the generated code with handling a []string field:

Perhaps UnmarshalConfig() could handle both []interface or []string?

Make it possible to use the media type identifier with `CollectionOf`

So that it's possible to do:

var Account = MediaType("application/vnd.example.account", func() {
  Attributes(func() {
    Attribute("bottles", CollectionOf("application.vnd.example.bottle"))
  })
})

var Bottle = MediaType("application/vnd.example.bottle", func() {
  Attributes(func() {
    Attribute("account", Account)
  })
})

Since using Bottle instead of "application.vnd.example.bottle" in the first definition would cause a compilation error (Initialization loop).

Trying to use Teaser example and it fails.

This is likely a simple noob question. I was trying to use goa. As an initial example I was trying the teaser app on the main page of the repo. I executed the exact steps specified and I get the following error.

panic: template: main:13: function "targetPkg" not defined

goroutine 1 [running]:
github.com/raphael/goa/goagen/codegen.(_SourceFile).ExecuteTemplate(0xc208152260, 0x4fdf20, 0x4, 0x60c4b0, 0x453, 0x0, 0x49ae00, 0xc2080c04d0, 0x0, 0x0)
/Users/ChrisRobinson/go2/src/github.com/raphael/goa/goagen/codegen/workspace.go:263 +0x14c
github.com/raphael/goa/goagen/gen_main.funcΒ·005(0xc2080c04d0, 0x0, 0x0)
/Users/ChrisRobinson/go2/src/github.com/raphael/goa/goagen/gen_main/generator.go:146 +0x471
github.com/raphael/goa/design.(_APIDefinition).IterateResources(0xc2080ebef0, 0xc20814fe30, 0x0, 0x0)
/Users/ChrisRobinson/go2/src/github.com/raphael/goa/design/api.go:355 +0x267
github.com/raphael/goa/goagen/gen_main.(*Generator).Generate(0xc20810eb00, 0xc2080ebef0, 0x0, 0x0, 0x0, 0x0, 0x0)
/Users/ChrisRobinson/go2/src/github.com/raphael/goa/goagen/gen_main/generator.go:155 +0x15e6
github.com/raphael/goa/goagen/gen_main.Generate(0xc2080ebef0, 0x0, 0x0, 0x0, 0x0, 0x0)
/Users/ChrisRobinson/go2/src/github.com/raphael/goa/goagen/gen_main/generator.go:31 +0xa7
main.main()
/var/folders/wq/hw1k0rbs56d7_lp7ln6v30j00000gn/T/goagen068784337/src/design/main.go:26 +0x5b

goroutine 5 [syscall]:
os/signal.loop()
/usr/local/go/src/os/signal/signal_unix.go:21 +0x1f
created by os/signal.initΒ·1
/usr/local/go/src/os/signal/signal_unix.go:27 +0x35

goroutine 26 [chan receive]:
github.com/raphael/goa/goagen/utils.Catch(0x7a6c60, 0x6, 0x6, 0xc2080f1750)
/Users/ChrisRobinson/go2/src/github.com/raphael/goa/goagen/utils/signal.go:15 +0xb1
created by github.com/raphael/goa/goagen/gen_main.(*Generator).Generate
/Users/ChrisRobinson/go2/src/github.com/raphael/goa/goagen/gen_main/generator.go:63 +0x149

goroutine 29 [chan send]:
text/template/parse.lexRightDelim(0xc208114200, 0x5fb5b8)
/usr/local/go/src/text/template/parse/lex.go:263 +0xb7
text/template/parse.(*lexer).run(0xc208114200)
/usr/local/go/src/text/template/parse/lex.go:198 +0x5d
created by text/template/parse.lex
/usr/local/go/src/text/template/parse/lex.go:191 +0x1ac

I'm running this on go 1.4, any ideas why this failure is occurring? I was trying to track it down. This failure is consistent for me, I tried in two different separate go path's that I had. Same issue. Am I missing a setting or was there a change that requires a change in the dsl to get this to work?

Error response clarification

I love the error responses, but "kind" with an integer is confusing. "error_number" or something similar would be more clear.

HTTP response is Golang specific

Tiny issue with a confusing error message.. When POSTing no data to an action that has required parameters, I get this 400 Bad Request response:

[
    {
        "kind": 3, 
        "msg": "type of payload must be map[string]interface{} but got value <nil>", 
        "title": "invalid attribute type"
    }
]

The msg was a little confusing since it talks in Golang terms: map[string]interface{}. It really just wants me to submit some valid JSON, like at least {}.

Got compilation errors running the basic example

I'm trying to set up the basic example but I'm getting some errors due some bad imports.

package design

import (
        . "github.com/raphael/goa/design"
        . "github.com/raphael/goa/design/dsl"
)

var _ = API("cellar", func() {
        Title("The virtual wine cellar")
        Description("A basic example of an API implemented with goa")
        Scheme("http")
        Host("localhost:8080")
})

var _ = Resource("bottle", func() {
        BasePath("/bottles")
        DefaultMedia(BottleMedia)
        Action("show", func() {
                Description("Retrieve bottle with given id")
                Routing(GET("/:bottleID"))
                Params(func() {
                        Param("bottleID", Integer, "Bottle ID")
                })
                Response(OK)
                Response(NotFound)
        })
})

var BottleMedia = MediaType("application/vnd.goa.example.bottle+json", func() {
        Description("A bottle of wine")
        Attributes(func() {
                Attribute("id", Integer, "Unique bottle ID")
                Attribute("href", String, "API href for making requests on the bottle")
                Attribute("name", String, "Name of wine")
        })
        View("default", func() {
                Attribute("id")
                Attribute("href")
                Attribute("name")
        })
})
schema/schema.go:25: undefined: httprouter in httprouter.Router
app/contexts.go:73: undefined: fmt in fmt.Errorf
app/contexts.go:112: undefined: fmt in fmt.Errorf
app/media_types.go:17: imported and not used: "fmt"

In the scheme file, it's missing a httprouter import.

package schema

import (
    "github.com/raphael/goa"
)


// MountController mounts the API JSON schema controller under "/schema.json".
func MountController(service goa.Service) {
    ctrl := service.NewController("Schema")
    service.Info("mount", "ctrl", "Schema", "action", "Show", "route", "GET /schema.json")
    h := ctrl.NewHTTPRouterHandle("Show", getSchema)
    service.HTTPHandler().(*httprouter.Router).Handle("GET", "/schema.json", h)
}

In the contexts.go file it's missing the fmt import.

import (
    "github.com/raphael/goa"
    "strconv"
)
.
.
// OK sends a HTTP response with status code 200.
    func (ctx *ShowBottleContext) OK(resp *GoaExampleBottle) error {
    r, err := resp.Dump()
    if err != nil {
        return fmt.Errorf("invalid response: %s", err)
    }
    ctx.Header().Set("Content-Type", "application/vnd.goa.example.bottle+json; charset=utf-8")
    return ctx.JSON(200, r)
}

In the media_types.go the fmt package is not needed.

import (
    "github.com/raphael/goa"
    "fmt"
)

It seems to me that I misunderstood something. Does anyone know what I'm doing bad?

Thanks.

Support `Consumes()` and `Produces()` DSL

The idea is to support mimetypes other than application/json, which is hard-coded right now (at least in the swagger generation).

application/json could be the default, but specifying Consumes() at the API() or Action() level could override the fallback value, and you could add one or more of those mime types.

When generating the app, we could do all the magic and validation when application/json is present,
but otherwise leave the user free to do all the unmarshalling / reading the body if it's not application/json..

What do you think ?

ResponseTemplates, allow multiple of the same HTTP code ?

I'm wondering why it wouldn't be possible to have multiple ResponseTemplates for BadRequest (or other codes), provided the signature of the func() is not the same.

This forces me to use:

        Response(BadRequest, "Branch or build not found")

when I defined:

    ResponseTemplate(BadRequest, func(reason string) {
        msg := "Bad Request"
        if reason != "" {
            msg = fmt.Sprintf("%s: %s", msg, reason)
        }
        Description(msg)
        Status(400)
        Media(ErrorModel)
    })

because of the reason. Not specifying a reason seems to fallback on some internal definitions.. but I really do want any BadRequest to use the ErrorModel media.

I tried to add:

    ResponseTemplate(BadRequest, func() {
        Description("Bad Request")
        Status(400)
        Media(ErrorModel)
    })

to also support:

        Response(BadRequest)

but I got an error message saying it was already defined. It's true.. but not the exact same signature!

btw, thanks for the awesome framework !

Response Templates defined but not referenced in Swagger output

Given the following API snippet:

const (
    // For Response Templates
    Deleted = "Deleted"
    Updated = "Updated"
)

var _ = API("test", func() {
    Title("Test API")
    Description("A Test API")

    Scheme("http")
    Host("localhost:8080")

    ResponseTemplate(Created, func(pattern string) {
        Description("Resource created")
        Status(201)
        Headers(func() {
            Header("Location", String, "href to created resource", func() {
                Pattern(pattern)
            })
        })
    })

    ResponseTemplate(Deleted, func() {
        Description("Resource deleted")
        Status(204)
    })

    ResponseTemplate(Updated, func() {
        Description("Resource updated")
        Status(204)
    })
})

var _ = Resource("organization", func() {
    BasePath("orgs")
    DefaultMedia(OrganizationMedia)

    Action("create", func() {
        Description("Create new organization")
        Routing(POST(""))
        Payload(func() {
            Member("name")
            Required("name")
        })
        Response(Created, "^/orgs/[0-9]+")
    })

    Action("delete", func() {
        Description("Delete an organization")
        Routing(DELETE("/:organizationId"))
        Params(func() {
            Param("organizationId", Integer, "Organization ID")
        })
        Response(Deleted)
    })

    Action("update", func() {
        Description("Update an organization")
        Routing(PUT("/:organizationId"))
        Params(func() {
            Param("organizationId", Integer, "Organization ID")
        })
        Payload(func() {
            Member("name")
            Required("name")
        })
        Response(Updated)
    })
})

var OrganizationMedia = MediaType("application/vnd.test.organization+json", func() {
    Description("An organization")
    Attributes(func() {
        Attribute("organization_id", Integer, "Unique organization ID")
        Attribute("name", String, "Organization Name")
    })
    View("default", func() {
        Attribute("organization_id")
        Attribute("name")
    })
})

The Swagger JSON output renders the responses as such:

"responses":{
  "201":{
    "description":"Resource created",
    "headers":{
      "Location":{
        "description":"href to created resource",
        "type":"string",
        "pattern":"^/orgs/[0-9]+"
      }
    }
  }
},
...
"responses":{"204":{"description":"Resource deleted"}},
...
"responses":{"204":{"description":"Resource updated"}},
...
// End of the JSON file contains these response definitions:
"responses":{
  "Deleted":{
    "description":"Resource deleted"
  },
  "Updated":{
    "description":"Resource updated"
  }
}

Loading the generated swagger.json into the Swagger Editor, the editor displays these warnings:

  • ⚠️ Definition is not used: #/responses/Deleted
  • ⚠️ Definition is not used: #/responses/Updated

goagen fails to find the design package when GOPATH is colon-separated list of locations

STRs

  1. Set GOPATH to colon separated list of locations and run goagen
    Notice it fails
goagen app -d github.com/rightscale/acl/design 
cannot find design package at path
"/Users/avinash/.gvm/pkgsets/go1.5/acl:/Users/avinash/go:/Users/avinash/.gvm/pkgsets/go1.5/global/src/github.com/rightscale/acl/design"
make: *** [acl] Error 1
localhost:acl avinash$ pwd
/Users/avinash/go/src/github.com/rightscale/acl
localhost:acl avinash$ echo $GOPATH
/Users/avinash/.gvm/pkgsets/go1.5/acl:/Users/avinash/go:/Users/avinash/.gvm/pkgsets/go1.5/global
  1. Change the GOPATH to the go workspace ~/go
    Notice the goagen succeeds

Looking at the code the problems seems to be here
https://github.com/raphael/goa/blob/v5/codegen/meta/generator.go#L63-L70

Missing dependency packages

There are several dependencies missing in makefile so make cannot pass, fix it by

Add missing packages to makefile

or

vendor godep

JWT middleware usage example

Would it be possible to have example of how to use JWT middleware ?
CORS was quite easy and there are docs on the site but I can't get my head around how you use JWT

Nested View marshal default instead of View("tiny")

I want to embed a nested collection of X media type with tiny view instead of default. How can we do that.

Example of what I need.

package design

import (
    . "github.com/raphael/goa/design"
    . "github.com/raphael/goa/design/dsl"
)

var _ = API("test", func() {
    Title("Nested View test API")
    Host("localhost:8080")
    Scheme("http")
})

var _ = Resource("event", func() {
    BasePath("/events")
    DefaultMedia(Event)
    Action("list", func() {
        Routing(GET(""))
        Description("List all event with tiny embeded prices")
        Response(OK, func() {
            Media(CollectionOf(Event, func() {
                View("default")
                View("tiny")
            }))
        })
        Response(NotFound)
    })
    Action("show", func() {
        Description("Retrieve event with given id")
        Routing(GET("/:eventID"))
        Params(func() {
            Param("eventID", String, "Event ID")
        })
        Response(OK)
        Response(NotFound)
    })
})

// Event media type
var Event = MediaType("application/vnd.event+json", func() {
    Description("A event information")
    Attributes(func() {
        Attribute("id", String, "Unique event ID")
        Attribute("href", String, "API href of event")
        Attribute("title", String, "Title")
        Attribute("prices", ArrayOf(Price), "Price collection for this event")

        Required("id", "title")
    })
    View("default", func() {
        Attribute("id")
        Attribute("href")
        Attribute("title")
        // ... more attributes here
        Attribute("prices", func() {
            View("default")
        })
    })
    View("tiny", func() {
        Attribute("id")
        Attribute("title")
        Attribute("href")
        Attribute("prices", func() {
            View("tiny")
        })
    })

})

var _ = Resource("price", func() {
    Parent("event")
    BasePath("prices")
    DefaultMedia(Price)
    Action("list", func() {
        Routing(GET(""))
        Description("List all price price in event")
        Response(OK, func() {
            Media(CollectionOf(Price, func() {
                View("default")
            }))
        })
        Response(NotFound)
    })
    Action("show", func() {
        Description("Retrieve price with given id")
        Routing(GET("/:priceID"))
        Params(func() {
            Param("priceID", String, "Price ID")
        })
        Response(OK)
        Response(NotFound)
    })
})

// Price price media type
var Price = MediaType("application/vnd.price+json", func() {
    Description("Price")
    Attributes(func() {
        Attribute("id", String, "Unique price ID")
        Attribute("name", String, "general name to use for ticket")
        Attribute("order", Integer, "ascending order of tickets as shown on event page")
        Attribute("price", Number, "the value of the ticket, 0.00 when free")

        Required("id", "name")
    })

    View("default", func() {
        Attribute("id")
        Attribute("name")
        Attribute("order")
        Attribute("price")
    })

    View("tiny", func() {
        Attribute("id")
        Attribute("price")
    })

    View("link", func() {
        Attribute("id")
        Attribute("price")
    })
})

generated code will look like

// MarshalEventTiny validates and renders an instance of Event into a interface{}
// using view "tiny".
func MarshalEventTiny(source *Event, inErr error) (target map[string]interface{}, err error) {
    ...
    if source.Prices != nil {
        ...
            tmp6[tmp7], err = MarshalPrice(tmp8, err)
        ...
    }
    ...
}

Should be tmp6[tmp7], err = MarshalPriceTiny(tmp8, err)

Collections are not rendered in Swagger responses

Given the following API snippet:

var _ = Resource("organization", func() {
    Action("list", func() {
        Description("Retrieve list of organizations")
        Routing(GET(""))
        Response(OK, func() {
            Media(CollectionOf(OrganizationMedia, func() {
                View("default")
            }))
        })
    })
})

var OrganizationMedia = MediaType("application/vnd.test.organization+json", func() {
    Description("An organization")
    Attributes(func() {
        Attribute("organization_id", Integer, "Unique organization ID")
        Attribute("name", String, "Organization Name")
    })
    View("default", func() {
        Attribute("organization_id")
        Attribute("name")
    })
})

The generated swagger output only contains the following for the response:

"responses": {
  "200": {
    "description": ""
  }
}

I would expect goa to generate something like this:

"responses": {
  "200": {
    "description": "",
    "schema": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/TestOrganization"
      }
    }
  }
}

It looks like genswagger.responseFromDefinition is where it's falling apart. A collection response has a MediaType like application/vnd.test.organization+json; type=collection instead of application/vnd.test.organization.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.