github.com/thetreep/go-swagger@v0.0.0-20240223100711-35af64f14f01/generator/templates/contrib/stratoscale/README.md (about) 1 # swagger 2 3 In Stratoscale, we really like the idea of API-first services, and we also really like Go. 4 We saw the go-swagger library, and thought that most of it can really help us. Generating code from 5 swagger files is a big problem with a lot of corner cases, and go-swagger is doing great job. 6 7 The one thing that we felt missing, is customization of the server to run with our design principles: 8 9 * Custom `main` function 10 * Dependency injection 11 * Limited scopes with unit testing. 12 13 Also: 14 15 * Adding you functions to the generated `configure_swagger_*.go` seems to be a burden. 16 * Lack of Interface that the service implement. 17 * Complicated and custom http clients and runtime. 18 19 Those are the changes that this contributor templates are providing: 20 21 ## Server 22 23 ### The new `restapi` package exposes interfaces 24 25 * Those interfaces can implemented by the developer and are the business logic of the service. 26 * The implementation of those is extensible. 27 * The implementation is separated from the generated code. 28 29 ### The `restapi` returns an `http.Handler` 30 31 The `restapi.Handler` (see [example](./example/restapi/configure_swagger_petstore.go)) function returns 32 a standard `http.Handler` 33 34 * Given objects that implements the business logic, we can create a simple http handler. 35 * This handler is standard go http.Handler, so we can now use any other middleware, library, or framework 36 that support it. 37 * This handler is standard, so we understand it better. 38 39 ## Client 40 41 * The new client package exposes interfaces, so functions in our code can receive those 42 interfaces which can be mocked for testing. 43 * The new client has a config that gets an `*url.URL` to customize the endpoint. 44 * The new client has a config that gets an `http.RoundTripper` to customize client with libraries, middleware or 45 frameworks that support the standard library's objects. 46 47 # Example Walk-Through 48 49 In the [example package](https://github.com/Stratoscale/swagger/tree/master/example) you'll find generated code and usage of the pet-store 50 [swagger file](./example/swagger.yaml). 51 52 * The `restapi`, `models` and `client` are auto-generated by the stratoscale/swagger docker file. 53 * The `internal` package was manually added and contains the server's business logic. 54 * The `main.go` file is the entrypoint and contains initializations and dependency injections of the project. 55 56 ## Server 57 58 ### [restapi](https://github.com/Stratoscale/swagger/tree/master/example/restapi) 59 60 This package is autogenerated and contains the server routing and parameters parsing. 61 62 The modified version contains `restapi.PetAPI` and `restapi.StoreAPI` which were auto generated. 63 64 ```go 65 // PetAPI 66 type PetAPI interface { 67 PetCreate(ctx context.Context, params pet.PetCreateParams) middleware.Responder 68 PetDelete(ctx context.Context, params pet.PetDeleteParams) middleware.Responder 69 PetGet(ctx context.Context, params pet.PetGetParams) middleware.Responder 70 PetList(ctx context.Context, params pet.PetListParams) middleware.Responder 71 PetUpdate(ctx context.Context, params pet.PetUpdateParams) middleware.Responder 72 } 73 74 //go:generate mockery --name StoreAPI --inpackage 75 76 // StoreAPI 77 type StoreAPI interface { 78 InventoryGet(ctx context.Context, params store.InventoryGetParams) middleware.Responder 79 OrderCreate(ctx context.Context, params store.OrderCreateParams) middleware.Responder 80 // OrderDelete is For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors 81 OrderDelete(ctx context.Context, params store.OrderDeleteParams) middleware.Responder 82 // OrderGet is For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions 83 OrderGet(ctx context.Context, params store.OrderGetParams) middleware.Responder 84 } 85 ``` 86 87 Each function matches an `operationId` in the swagger file and they are grouped according to 88 the operation `tags`. 89 90 There is also a `restapi.Config`: 91 92 ```go 93 // Config is configuration for Handler 94 type Config struct { 95 PetAPI 96 StoreAPI 97 Logger func(string, ...interface{}) 98 // InnerMiddleware is for the handler executors. These do not apply to the swagger.json document. 99 // The middleware executes after routing but before authentication, binding and validation 100 InnerMiddleware func(http.Handler) http.Handler 101 } 102 ``` 103 104 This config is auto generated and contains all the declared interfaces above. 105 It is used to initiate an http.Handler with the `Handler` function: 106 107 ```go 108 // Handler returns an http.Handler given the handler configuration 109 // It mounts all the business logic implementers in the right routing. 110 func Handler(c Config) (http.Handler, error) { 111 ... 112 ``` 113 114 Let's look how we use this generated code to build our server. 115 116 ### [internal](https://github.com/Stratoscale/swagger/tree/master/example/internal) 117 118 The `internal` package is **not** auto generated and contains the business logic of our server. 119 We can see two structs that implements the `restapi.PetAPI` and `restapi.StoreAPI` interfaces, 120 needed to make our server work. 121 122 When adding or removing functions from our REST API, we can just add or remove functions to those 123 business logic units. We can also create new logical units when they are added to our REST API. 124 125 ### [main.go](./example/main.go) 126 127 The main function is pretty straight forward. We initiate our business logic units. 128 Then create a config for our rest API. We then create a standard `http.Handler` which we can 129 update with middleware, test with `httptest`, or to use with other standard tools. 130 The last piece is to run the handler with `http.ListenAndServe` or to use it with an `http.Server` - 131 it is all very customizable. 132 133 ```go 134 func main() { 135 // Initiate business logic implementers. 136 // This is the main function, so here the implementers' dependencies can be 137 // injected, such as database, parameters from environment variables, or different 138 // clients for different APIs. 139 p := internal.Pet{} 140 s := internal.Store{} 141 142 // Initiate the http handler, with the objects that are implementing the business logic. 143 h, err := restapi.Handler(restapi.Config{ 144 PetAPI: &p, 145 StoreAPI: &s, 146 Logger: log.Printf, 147 }) 148 if err != nil { 149 log.Fatal(err) 150 } 151 152 // Run the standard http server 153 log.Fatal(http.ListenAndServe(":8080", h)) 154 } 155 ``` 156 157 ## Client 158 159 The client code is in the [client package](https://github.com/Stratoscale/swagger/tree/master/example/client) and is autogenerated. 160 161 To create a new client we use the `client.Config` struct: 162 163 ```go 164 type Config struct { 165 // URL is the base URL of the upstream server 166 URL *url.URL 167 // Transport is an inner transport for the client 168 Transport http.RoundTripper 169 } 170 ``` 171 172 This enables us to use custom server endpoint or custom client middleware. Easily, with the 173 standard components, and with any library that accepts them. 174 175 The client is then generated with the New method: 176 177 ```go 178 // New creates a new swagger petstore HTTP client. 179 func New(c Config) *SwaggerPetstore { ... } 180 ``` 181 182 This method returns an object that has two important fields: 183 184 ```go 185 type SwaggerPetstore { 186 ... 187 Pet *pet.Client 188 Store *store.Client 189 } 190 ``` 191 192 Thos fields are objects, which implements interfaces declared in the [pet](./example/client/pet) and 193 [store](./example/client/store) packages: 194 195 For example: 196 197 ```go 198 // API is the interface of the pet client 199 type API interface { 200 // PetCreate adds a new pet to the store 201 PetCreate(ctx context.Context, params *PetCreateParams) (*PetCreateCreated, error) 202 // PetDelete deletes a pet 203 PetDelete(ctx context.Context, params *PetDeleteParams) (*PetDeleteNoContent, error) 204 // PetGet gets pet by it s ID 205 PetGet(ctx context.Context, params *PetGetParams) (*PetGetOK, error) 206 // PetList lists pets 207 PetList(ctx context.Context, params *PetListParams) (*PetListOK, error) 208 // PetUpdate updates an existing pet 209 PetUpdate(ctx context.Context, params *PetUpdateParams) (*PetUpdateCreated, error) 210 } 211 ``` 212 213 They are very similar to the server interfaces, and can be used by consumers of those APIs 214 (instead of using the actual client or the `*Pet` struct) 215 216 # Authentication 217 218 Authenticating and policy enforcement of the application is done in several stages, described below. 219 220 ## Define security in swagger.yaml 221 222 Add to the root of the swagger.yaml the security and security definitions sections. 223 224 ```yaml 225 securityDefinitions: 226 token: 227 type: apiKey 228 in: header 229 name: Cookie 230 231 security: 232 - token: [] 233 ``` 234 235 The securityDefinitions section defines different security types that your application can handle. 236 The supported types by go-swagger are: 237 * `apiKey` - token that should be able to processed. 238 * `oauth2` - token and scopes that should be processed. 239 * and `basic` - user/password that should be processed. 240 241 Here we defined an apiKey, that is passed through the Cookie header. 242 243 The `security` section defines the default security enforcement for the application. You can select 244 different securityDefinitions, as the keys, and apply "scopes" as the values. Those default definitions 245 can be overriden in each route by a section with the same name: 246 247 ```yaml 248 paths: 249 /pets: 250 post: 251 [...] 252 security: 253 - token: [admin] 254 ``` 255 256 Here we overriden the scope of token in the POST /pets URL so that only admin can use this API. 257 258 Let's see how we can use this functionality. 259 260 ## Writing Security Handlers 261 262 Once we created a security definition named "token", a function called "AuthToken" was added to the `restapi.Config`: 263 264 ```go 265 type Config struct { 266 ... 267 // AuthToken Applies when the "Cookie" header is set 268 AuthToken func(token string) (interface{}, error) 269 } 270 ``` 271 272 This function gets the content of the Cookie header, and should return an `interface{}` and `error`. 273 The `interface{}` is the object that should represent the user that performed the request, it should 274 be nil to return an unauthorized 401 HTTP response. If the returned `error` is not nil, an HTTP 500, 275 internal server error will be returned. 276 277 The returned object, will be stored in the request context under the `restapi.AuthKey` key. 278 279 There is another function that we should know about, in the `restapi.Config` struct: 280 281 ```go 282 type Config struct { 283 ... 284 // Authorizer is used to authorize a request after the Auth function was called using the "Auth*" functions 285 // and the principal was stored in the context in the "AuthKey" context value. 286 Authorizer func(*http.Request) error 287 } 288 ``` 289 290 This one is a custom defined function that gets the request and can return an error. 291 If the returned error is not nil, and 403 HTTP error will be returned to the client - here the policy 292 enforcement comes to place. 293 There are two things that this function should be aware of: 294 295 1. The user - it can retrieve the user information from the context: `ctx.Value(restapi.AuthKey).(MyUserType)`. 296 Usually, a server will have a function for extracting this user information and returns a concrete 297 type which could be used by all the routes. 298 2. The route - it can retrieve the route using the go-swagger function: `middleware.MatchedRouteFrom(*http.Request)`. 299 So no need to parse URL and test the request method. 300 This route struct contains the route information. If for example, we want to check the scopes that were 301 defined for the current route in the swagger.yaml we can use the code below: 302 303 ```go 304 for _, auth := range route.Authenticators { 305 for scopeName, scopeValues := range auth.Scopes { 306 for _, scopeValue := range scopeValues { 307 ... 308 } 309 } 310 } 311 ```