{"id":27786326,"url":"https://github.com/clever/wag","last_synced_at":"2025-04-30T15:59:46.265Z","repository":{"id":37548706,"uuid":"65332703","full_name":"Clever/wag","owner":"Clever","description":"sWAGger - Web API Generator","archived":false,"fork":false,"pushed_at":"2025-04-22T17:18:06.000Z","size":225728,"stargazers_count":77,"open_issues_count":20,"forks_count":7,"subscribers_count":57,"default_branch":"master","last_synced_at":"2025-04-22T18:39:19.457Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Clever.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"docs/contributing.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2016-08-09T22:34:45.000Z","updated_at":"2025-04-09T18:23:09.000Z","dependencies_parsed_at":"2023-12-20T07:27:34.341Z","dependency_job_id":"a7b3a1cb-7010-437c-a784-8c3c6db77e8b","html_url":"https://github.com/Clever/wag","commit_stats":{"total_commits":1206,"total_committers":52,"mean_commits":"23.192307692307693","dds":0.7180762852404643,"last_synced_commit":"bbed7c28012b15167c0285113ca63f2bbe0ab00d"},"previous_names":[],"tags_count":223,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Clever%2Fwag","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Clever%2Fwag/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Clever%2Fwag/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Clever%2Fwag/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Clever","download_url":"https://codeload.github.com/Clever/wag/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251737979,"owners_count":21635714,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-04-30T15:59:42.033Z","updated_at":"2025-04-30T15:59:46.253Z","avatar_url":"https://github.com/Clever.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# wag\n\nsWAGger - Web API Generator.\nA custom Web API Generator written by Clever.\nDespite the presence of a `swagger.yml` file, WAG does not support all of the Swagger standard.\nWAG is a custom re-implementation of a subset of the Swagger version `2.0` standard.\n\n## Usage\nWag requires Go 1.24+ to build, and the generated code also requires Go 1.24+.\n\n### Dependencies\n\nThe code generated by `wag` imposes dependencies that you should include in your `go.mod`. The `go.mod` file under `samples/` provides a list of versions that definitely work; pay special attention to the versions of `go.opentelemetry.io/*`, `github.com/go-swagger/*`, and `github.com/go-openapi/*`.\n\n\n### Generating Code\nCreate a swagger.yml file with your [service definition](http://editor.swagger.io/#/). Wag supports a [subset](https://github.com/Clever/wag#swagger-spec) of the Swagger spec.\nCopy the latest `wag.mk` from the [dev-handbook](https://github.com/Clever/dev-handbook/blob/master/make/wag.mk).\nSet up a `generate` target in your `Makefile` that will generate server and client code:\n\n```\ninclude wag.mk\n\nWAG_VERSION := latest\n\ngenerate: wag-generate-deps\n\t$(call wag-generate-mod,./swagger.yml, $(PKG))\n```\n\n`wag.mk` requires `WAG_VERSION` to be set. It can be either `latest` or a tagged release like `v8.0.0`. `latest` will use the most recent non-prerelease.\n\nDefine global BadRequest and InternalError response types. These are used internally for validation errors and unknown errors respectively. They must reference a definition with a message field. For example:\n\n```\nresponses:\n  BadRequest:\n    description: Bad Request\n    schema:\n      $ref: \"#/definitions/BadRequest\"\n  InternalError:\n    description: Internal Error\n    schema:\n      $ref: \"#/definitions/InternalError\"\n\ndefinitions:\n  BadRequest:\n    type: object\n    properties:\n      message:\n        type: string\n  InternalError:\n    type: object\n    properties:\n      message:\n        type: string\n```\n\nFor more information on error definitions see the Errors section below.\n\nThen generate your code:\n```\nmake generate\n```\n\nThis generates four directories. You should not have to modify any of the generated code:\n- gen-go/models: contains all the definitions in your Swagger file as well as the API input / output definitions\n- gen-go/server: contains the router, middleware, and handler logic\n- gen-go/client: contains the Go client library\n- gen-go/tracing: contains the code for both client and server for adding OpenTelemetry instrumentation.\n- gen-js: contains the javascript client library\n\n## Implementing and Running the Server\nTo implement and run the generated server you need to:\n- Implement the controller interface defined in `gen-go/server/interface.go`\n- Pass the controller into the Server constructor. For example:\n```\n  s := server.New(myController, \":8000\")\n  // Serve should not return\n  log.Fatal(s.Serve())\n```\nOr, with custom middleware:\n```\n  s := server.NewWithMiddleware(myController, \":8000\", []func(http.Handler) http.Handler{\n    myFirstMiddlware, mySecondMiddlware})\n  // Serve should not return\n  log.Fatal(s.Serve())\n```\n\n### Interface\n\nThe server interface defined in `gen-go/server/interface.go` has one method for each operation defined in the swagger.yml. We generate the interface based on the following rules:\n\n#### Input Parameters\n  * The first argument to each Wag operation is a `context.Context`. See below for more details on how Wag uses contexts.\n  * If one parameter is defined then Wag uses that input directly in the function definition.\n  `func F(ctx context.Context, input string) error`\n  * If more than one parameter is defined then Wag generates a input struct with all the parameters:\n  `func F(ctx context.Context, input *models.{{OperationID}}Input) error`\n    * Optional parameters that don't have defaults are pointers in the input struct so that the server can distinguish between parameters that aren't set and parameters that are set to the zero value.\n\n\n#### Response Parameters\n  * Wag only supports defining a single 2XX response status code and doesn't support 3XX status codes.\n    * If the success response type has a data type associated with it then Wag generates an interface that takes a pointer to that data type as the first argument.\n    `func(...) (*SuccessType, error)`\n    * If the operation uses `x-paging`, then Wag generates the an interface that takes the type of the page ID parameter as the second return type.\n    `func(...) (*SuccessType, PageParamType, error)`\n    * If the success response type doesn't define a data type then Wag generates an interface with only an error response. A nil error tells the client that the request succeeded.\n    `func(...) error`\n\n\n### Logging\n  The [kayvee middleware logger](https://godoc.org/gopkg.in/Clever/kayvee-go.v6/middleware) is automatically added to the context object.\n  It can be pulled out of the context object and used via the kayvee `FromContext` method:\n\n```go\nimport \"gopkg.in/Clever/kayvee-go.v6/logger\"\n...\nlogger.FromContext(ctx).Info(...)\n```\n\n  You should use this logger for all logging within your controller implementation.\n\n#### Application Log Routing\n\n**Note**: This is an internal Clever feature. Ignore this if you are using `wag` outside of Clever (also, hi!)\n\n`wag` is already set up for log routing if you so wish. To set up [application log routing](https://clever.atlassian.net/wiki/display/ENG/Application+Log+Routing) in your service, add your `kvconfig.yml` file to the same directory as your service executable. e.g. in your Dockerfile:\n\n```\nCOPY kvconfig.yml /usr/bin/kvconfig.yml\nCOPY bin/my-wag-service /usr/bin/my-wag-service\n```\n\n### Errors\n  * Wag supports three types of errors\n    * Global error response types\n    * Response types for a specific operation\n    * Unexpected errors\n\n  * Any of these can be returned from a controller. To return a global or response specific error type return a pointer to the model defintion for that error type. To return an unexpected error return any Go error. Wag automatically converts errors not defined swagger yml into the default 500 response.\n\n  * All error responses defined in the swagger yml must have a `Message` field. The field is used as the return value of the `Error()` for the corresponding Go error type.\n\n  * Wag has two built-in errors: `#/definitions/BadRequest` (400) and '#/responses/InternalError' (500). Any operation that doesn't explicitly define a 400 and/or 500 response gets these automatically so Wag can use them to return validation and internal errors respectively.\n\n  * Errors returned from your controller are logged by the\n  autogenerated handler code, so there is no need to separately log errors\n  yourself. If you use the `github.com/go-errors/errors` package, the\n  stacktrace will also be logged, making debugging easier.\n\n  For undefined error types the best practices are:\n    * If you receive an error from an external dependency, use\n      `errors.WrapPrefix(err, \"foopackage.func\", 0)` to return an error with a\n     stacktrace and prefix the source of the error (in this example, we received\n     an error from the function `func` in the package `foopackage`).\n   * If you generate a new error, use `errors.Errorf` or `errors.New` to build\n     the error.\n   * If you receive an error from an internal function, just return the error\n    directly since it should already have stacktrace information (either it is\n      a wrapped external error or a `go-errors`-generated internal error).\n\n### Input Parameters\n  * Wag supports four types of parameters\n    * Path parameters\n      * Must be required\n      * Must be a simple type (e.g. string, integer)\n      * Will not be pointers\n    * Body parameters\n      * Must reference a 'definition' schema\n      * Will be pointers\n      * Cannot have defaults\n    * Query parameters\n      * Must be a simple or array type. If an array must be an array of strings\n      * If the type is 'simple' and the parameter is not required, the type will be a pointer\n      * If the type is 'array' it won't be a pointer. Query parameters can't distinguish between an empty array and an nil array so it converts both these cases to a nil array. If you need to distinguish between the two use a body parameter\n      * In other cases the parameter is not a pointer\n    * Header parameters\n      * Must be simple types\n      * If marked required will ensure that the input isn't the nil value. Headers cannot have pointer types since HTTP doesn't distinguish between empty and missing headers.\n      * If it doesn't have a default value specified, the default value will be the nil value for the type\n\n### Paging\n  * Wag can help implement paging on endpoints if you use the `x-paging`\n    configuration:\n    ```\n    /books:\n      operationId: getBooks\n      x-paging:\n        pageParameter: startingAfter\n    ```\n  * `pageParameter` should be set to the name of a parameter you define on the\n    operation that specifies a page ID. The parameter can be a query, header,\n    or path parameter (it may not be the only path parameter).\n  * You can also specify `resourcePath` if the array you want iterate over\n    isn't the top-level success return for the operation. (See `/authors` in\n    samples/swagger.yml.)\n  * The server interface will be amended with a 2nd success return type so that\n    your controller can provide the next page ID the client should fetch.\n  * The autogenerated Go client will include a `New\u003cOperationID\u003eIter` function\n    that returns an iterator object that exposes a Next() function that will\n    return successive resources, requesting new pages as needed.\n  * The autogenerated JS client will include an `\u003coperationID\u003eIter` function\n    that exposes `map`, `forEach`, `forEachAsync` and `toArray` functions to iterate over the\n    results, again requesting new pages as needed.\n\n### Contexts\n  * The first argument to every Wag function is a `context.Context` (https://blog.golang.org/context). Contexts play a few important roles in Wag.\n    * They can be used to set request specific behavior like a retry policy in client libraries. This includes timeouts and cancellation.\n    * They are used to pass metadata, like tracing information, across API requests.\n\n  * To get these benefits pass the context object to any subsequent network requests you make.\n    Many client libraries accept the context object, e.g.:\n      * **net/http**: If you're making HTTP requests, use the [golang.org/x/net/context/ctxhttp](https://godoc.org/golang.org/x/net/context/ctxhttp) package.\n      * **wag** If your handler consumes a `wag`-generated client, then pass the context object to these client methods.\n\n  * If you don't have a context to pass to a Wag function you have two options\n    * context.Background() - use this when this is the creator of the request chain, like a test or a top-level service.\n    * context.TODO() - use this when you haven't been passed a context from a caller yet, but you expect the caller to send you one at some point.\n\n\n### DynamoDB Codegen\n\n  * Wag can auto-generate server code to save models to DynamoDB if you specify the `x-db` extension on a schema:\n  ```yaml\ndefinitions:\n  Thing:\n    x-db:\n      AllowOverwrites: false\n      DynamoDB:\n        KeySchema:\n          - AttributeName: name\n            KeyType: HASH\n          - AttributeName: version\n            KeyType: RANGE\n    type: object\n    properties:\n      name:\n        type: string\n      version:\n        type: integer\n  ```\n  The above will generate a `db` package with code to load/persist `Thing` objects from/to DynamoDB.\n  * `AllowOverwrites` specifies whether the auto-generated `Save` method should succeed if the object already exists.\n  * `DynamoDB` specifies the configuration for a DyanmoDB table for the schema.\n     It follows the format of the [`AWS::DynamoDB::Table`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html) CloudFormation resource.\n     Currently it supports a subset of the configuration allowed there.\n\n\n### Tracing\n\n`wag` instruments servers and clients with [opentelemetry-go](https://pkg.go.dev/go.opentelemetry.io/otel). You must initialize tracing before use with a snippet like this in your `main()`:\n```go\n// in your imports\nimport \u003cMY REPO NAME\u003e/gen-go/tracing\"\n\n// in your main\n\texp, prov, err := tracing.SetupGlobalTraceProviderAndExporter(context.Background())\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to setup tracing: %v\", err)\n\t}\n\tdefer exp.Shutdown(context.Background())\n\tdefer prov.Shutdown(context.Background())\n```\n\nIn order for the `defer` statements to work properly, this should be called in `main()` or the same function that you call `Serve()` to start the server.\n\nServer instrumentation is done via [go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux](https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux).\n\nClient instrumentation is done via [go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp](https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp).\n\nTraces are sent to [opentelemetry-collector](https://github.com/open-telemetry/opentelemetry-collector) running at `localhost:4317`.\nIf running locally, all samples are traced.\nOtherwise, the probability of sampling is 0.01 (i.e. one in one hundred traces) and can be overriden by setting the environment variable `TRACING_SAMPLING_PROBABILITY`.\n\nThese details (the function to setup tracing, the addresses to which traces are sent, and the `TRACING_SAMPLING_PROBABILITY` variable) are stable for 7.x, but other details of tracing, such as the instrumention and the tags that are sent, are subject to changes even within 7.x.\n\nIn addition, tracing relies on a specific version of the `opentelemetry` modules; you can find a complete list of dependencies that work in `samples/go.mod`.\n\n## Using the Go Client\nInitialize the client with `New`\n```\nc := client.New(\"https://url_of_your_service:port\")\n```\n\nMake an API call\n```\nbooks, err := c.GetBooks(ctx, GetBookByIDInput{Authors: []string{\"Twain\"}})\nif err != nil {\n  // Do something with the error\n}\n```\n\nIf you're using the client from another WAG-ified service you should pass in the `ctx` object you get in your server handler. Otherwise you can use `context.Background()`\n\n### Custom String Validation\nWe've added custom string validation for mongo-ids to avoid repeating: \"^[0-9a-f]{24}$\"` throughout the swagger.yml. To use it you have must:\n\n- Change you swagger.yml file to have the `mongo-id` format. For example:\n```\nauthorID:\n        type: string\n        format: mongo-id\n```\n\n- Import `github.com/Clever/wag/swagger` and call `swagger.InitCustomFormats()` in your server code.\n\nNote that custom string validation only applies to input parameters and does not have any impact on objects defined in '#/definitions'.\n\nRight now we do not allow user-defined custom strings, but this is something we may add if there's sufficient demand.\n\n\n## Using the Javascript Client\nYou can initialize the client by either passing a url or by using [discovery](https://github.com/Clever/discovery-node).\n\n```javascript\nimport * as SampleClientLib from '@clever/sample-client-lib-js';\n\nconst sampleClient = new SampleClientLib({address: \"https://url_of_your_service:port\"}); // Explicit url\n// OR\nconst sampleClient = new SampleClientLib({discovery: true}); // Using discovery\n```\n\nYou may also configure a global timeout for requests when initalizing the client.\n\n```javascript\nconst sampleClient = new SampleClientLib({discovery: true, timeout: 1000}); // Timeout any requests taking longer than 1 second\n```\n\nYou may then call methods on the client. Methods support callbacks and promises.\n\n```javascript\n// Promises\nsampleClient.getBookById(\"bookID\").then((book) =\u003e {\n  // ...\n}).catch((err) =\u003e {\n  // ...\n});\n\n// Callbacks\nsampleClient.getBookById(\"bookID\", (err, book) =\u003e {\n  // ...\n});\n```\n\nYou can also pass an optional options argument. This can have the following options\n- `timeout` - overide the global timeout for this specific call\n\n```javascript\nconst options = {\n  timeout: 5000 // Timeout after 5 seconds\n}\n\nsampleClient.getBookById(\"bookID\", options, (err, book) =\u003e {\n  // ...\n});\n```\n\n#### Tracing\n\nAs of v7, `wag` no longer provides any special tracing experience for Javascript clients. We recommend using the `@opentelemetry` [packages](https://github.com/open-telemetry/opentelemetry-js) to add client-side instrumentation.\n\n## Tests\n```\nmake test\n```\n\n## Swagger Spec\n\nCurrently, Wag doesn't implement the entire Swagger Spec. A couple things to keep in mind:\n- All schemas should reference type definitions in /definitions. Any schemas defined in /paths will cause an error.\n- Scheme, produces, and consumers can only be defined in the top-level swagger object, not individual operations. On the top level object the scheme must be 'http', produces must be 'application/json' and consumes must be 'application/json'\n\nBelow is a more comprehensive list of the features we don't yet support\n\n### Unsupported Features\nMime Types\n\nMulti-File Swagger Definitions\n\nSchema:\n- host\n- tags\n- scheme (must be http)\n- consumes\n- produces\n- securityDefinitions\n- security\n\nConsumes:\n- produces (must be application/json)\n- consumes (must be application/json)\n- schemes\n- security\n\nForm parameter type\nParameter:\n- file parameter type\n- collectionFormat\n- global definitions\n- possibly the json schema requirements? (uniqueItems, multipleOf, etc...)\n\nSchema object (all these have to be defined in /definitions and are generated by go-swagger)\n\nDiscriminators\n\nXML Modeling\n\nSecurity Objects\n\nResponse:\n  - Headers\n\n## Serving Custom Routes\nThe `New()` and `NewWithMiddleware()` will return a `Server` that can start an HTTP server with\nthe endpoints defined in the swagger yml. If you want to extend the autogenerated endpoints or\nif you need to make use of the `ResponseWriter` or `Request` within a given endpoint, you can\nuse `NewRouter()` to expose the `*mux.Router` and then `AttachMiddleware()` to attach the\nwag middleware and return a `Server`. For example:\n\n```\n// Create new controller with all the swagger functions implemented\nc := controller.New()\n\n// Create new *mux.Router\nrouterHandler := server.NewRouter(c)\n\n// Inject our own authorize endpoint to wrap around the swagger handler\nrouter.Methods(\"GET\").Path(\"/authorize\").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\tinput, _ := sanitizeAuthorizeInput(r)\n\tresp, _ := c.AuthorizeUserForApp(r.Context(), input)\n\thttp.Redirect(w, r, fmt.Sprintf(\"%s\", *resp.RedirectURI), 302)\n})\n\n// Make and start server\ns := server.MakeServer(router, *addr, middleware)\nif err := s.Serve(); err != nil {\n\tlog.ErrorD(\"error-server-setup\", logger.M{\n\t\t\"error\": err.Error(),\n\t})\n\tos.Exit(1)\n}\n```\n\nThe created `/authorize` endpoint uses the autogenerated `AuthorizeUserForApp` function, but will\nmake use of the `ResponseWriter` and `Request` to redirect the caller to the `RedirectURI`\n\n## Development\n\nThe following directories and files are generated and should not be manually edited:\n- samples/gen-\u0026ast;/\u0026ast;\n- hardcoded/hardcoded.go\n\nOnce you've made your changes, run `make test` and check that the generated code looks ok, then check in those changes.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclever%2Fwag","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclever%2Fwag","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclever%2Fwag/lists"}