github.com/josephspurrier/go-swagger@v0.2.1-0.20221129144919-1f672a142a00/design.md (about)

     1  # Framework design
     2  
     3  The goals are to be as unintrusive as possible. The swagger spec is the source of truth for your application.
     4  
     5  The reference framework will make use of a swagger API that is based on the denco router.
     6  
     7  The general idea is that it is a middleware which you provide with the swagger spec.
     8  This document can be either JSON or YAML as both are supported.
     9  
    10  In addition to the middleware there are some generator commands that will use the swagger spec to generate models, parameter models, operation interfaces and a mux.
    11  
    12  ## The middleware
    13  
    14  Takes a raw spec document either as a []byte, and it adds the /api-docs route to serve this spec up.
    15  
    16  The middleware performs validation, data binding and security as defined in the swagger spec.
    17  It also uses the API to match request paths to functions of `func(paramsObject) (responseModel, error)`
    18  The middleware does this by building up a series of rules for each operation. When the spec.Document is first created it analyzes the swagger spec and builds a routing, validation and binding rules for each operation in the specification. Before doing that it expands all the $ref fields in the swagger document. After expanding all the rules it validates the registrations made in the API and will take an configurable action for missing operations.
    19  
    20  When a request comes in that doesn't match the /swagger.json endpoint it will look for it in the swagger spec routes.  It doesn't need to create a plan for anything anymore it did that at startup time but it will then execute the plan with the request and route params.
    21  These are provided in the API. There is a tool to generate a statically typed API, based on operation names and operation interfaces
    22  
    23  ### The API
    24  
    25  The reference API will use the denco router to register route handlers.
    26  The actual request handler implementation is always the same.  The API must be designed in such a way that other frameworks can use their router implementation and perhaps their own validation infrastructure.
    27  
    28  An API is served over http by a router, the default implementation is a router based on denco. This is just an interface implementation so it can be replaced with another router should you so desire.
    29  
    30  The API comes in 2 flavors an untyped one and a typed one.
    31  
    32  #### Untyped API
    33  
    34  The untyped API is the main glue. It takes registrations of operation ids to operation handlers.
    35  It also takes the registrations for mime types to consumers and producers. And it links security schemes to authentication handlers.
    36  
    37  ```go
    38  type OperationHandler func(interface{}) (interface{}, error)
    39  ```
    40  
    41  The API has methods to register consumers, producers, auth handlers and operation handlers
    42  
    43  The register consumer and producer methods are responsible for attaching extra serializers to media types. These are then used during content negotiation phases for look up and binding the data.
    44  
    45  When an API is used to initialize a router it goes through a validation step.
    46  This validation step will verify that all the operations in the spec have a handler registered to them.
    47  It also ensures that for all the mentioned media types there are consumers and producers provided.
    48  And it checks if for each authentication scheme there is a handler present.
    49  If this is not the case it will exit the application with a non-zero exit code.
    50  
    51  The register method takes an operation name and a swagger operation handler.  
    52  It will then use that to build a path pattern for the router and it uses the swagger operation handler to produce a result based on the information in an incoming web request. It does this by injecing the handler in the swagger web request handler.
    53  
    54  #### Typed API
    55  
    56  The typed API uses a swagger spec to generate a typed API.
    57  
    58  For this there is a generator that will take the swagger spec document.
    59  It will then generate an interface for each operation and optionally a default implementation of that interface.
    60  The default implementation of an interface just returns a not implemented api error.
    61  
    62  When all the interfaces and default implementations are generated it will generate a swagger mux implementation.
    63  This swagger mux implementation links all the interface implementations to operation names.
    64  
    65  The typed API avoids reflection as much as possible, there are 1 or 2 validations that require it. For now it needs to include the swagger.json in the code for a few reasons.
    66  
    67  
    68  ### The request handler
    69  
    70  The request handler does the following things:
    71  
    72  1. Authenticate and authorize if necessary
    73  2. Validate the request data
    74  3. Bind the request data to the parameter struct based on the swagger schema
    75  4. Validate the parameter struct based on the swagger schema
    76  5. Produce a model or an error by invoking the operation interface
    77  6. Create a response with status code etc based on the operation interface invocation result
    78  
    79  #### Authentication
    80  
    81  The authentication integration should execute security handlers. A security handler performs 2 functions it should authenticate and upon successful authentication it should authorize the request if the security scheme requires authorization. The authorization should be mainly required for the oauth2 based authentication flows.
    82  
    83  ```go
    84  type Authenticator func(interface{}) (matched bool, principal interface{}, err error)
    85  ```
    86  
    87  basic auth and api key type authorizations require the request for their authentication to work.
    88  
    89  When we've determined a route matches we should check if the request is allowed to proceed.
    90  To do this our middleware knows how to deal with basic auth and how to retrieve access tokens etc.
    91  It does this by using the information in the security scheme object registered for a handler with the same scheme name.
    92  
    93  
    94  #### Binding
    95  
    96  Binding makes use of plain vanilla golang serializers and they are identified by the media type they consume and produce.
    97  
    98  Binding is not only about request bodies but also about values obtained from headers, query string parameters and potentially the route path pattern. So the binding should make use of the full request object to produce a model.
    99  
   100  It determines a serializer to use by looking in the the merged consumes values and the `Content-Type` header to determine which deserializer to use.  
   101  When a result is produced it will do the same thing by making use of the `Accept` http header etc and the merged produces clauses for the operation endpoint.
   102  
   103  ```go
   104  type RequestBinder interface {
   105    BindRequest(*http.Request, *router.MatchedRoute, swagger.Consumer) error
   106  }
   107  ```
   108  
   109  #### Validation
   110  
   111  When the muxer registers routes it also builds a suite of validation plans, one for each operation.
   112  Validation allows for adding custom validations for types through implementing a Validatable interface. This interface does not override but extends the validations provided by the swagger schema.
   113  
   114  There is a mapping from validation name to status code, this mapping is also prioritized so that in the event of multiple validation errors that would required different status codes we get a consistent result. This prioritization can be done by the user by providing a ServeError function.
   115  
   116  ```go
   117  type Validatable interface {
   118    Validate(strfmt.Registry) error
   119  }
   120  
   121  type Error struct {
   122    Code     int32
   123    Path     string
   124    In       string
   125    Value    interface{}
   126    Message  string
   127  }
   128  ```
   129  
   130  #### Execute operation
   131  
   132  When all the middlewares have finished processing the request ends up in the operation handling middleware.  
   133  This middleware is responsible for taking the bound model produced in the validation middleware and executing the registered operation handler for this request.
   134  By the time the operation handler is executed we're sure the request is **authorized and valid**.
   135  
   136  The result it gets from the operation handler will be turned into a response. Should the result of the operation handler be an error or a series of errors it will determine an appropriate status code and render the error result.
   137  
   138  
   139  # Codegen
   140  
   141  Codegen consists out of 2 parts. There is generating a server application from a swagger spec.
   142  The second part is generating a swagger spec from go code based on annotations and information retrieved from the AST.
   143  
   144  ## Go generation
   145  
   146  The goal of this code generation is to just take care of the boilerplate.
   147  It uses a very small runtime to accommodate the swagger workflow. These are just small helpers for sharing some common
   148  code.  The server application uses plain go http do do its thing. All other code is generated so you can read what it
   149  does and if you think it's worth it.
   150  
   151  The go server api generator however won't reuse those templates but define its own set, because currently no proper go support exists in that project. Once I'm happy with what they generate I'll contribute them back to the swagger-codegen project.
   152  
   153  A generated client needs to have support for uploading files as multipart entries. The model generating code is shared between client and server. The things that operate with those models will be different.
   154  A generated client could implement validation on the client side for the request parameters and received response. The meat of the client is not actually implemented as generated code but a single submit function that knows how to perform all the shared operations and then issue the request.
   155  A client typically has only one consumer and producer registered. The content type for the request is the media type of the producer, the accept header is the media type of the consumer.
   156  
   157  https://engineering.gosquared.com/building-better-api-docs