sashay

package module
v1.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 19, 2022 License: MIT Imports: 10 Imported by: 1

README

Build Status codecov

GoDoc license

Sashay

Sashay allows you to generate OpenAPI 3.0 (Swagger) files using executable Go code, including the same types you already use for parameter declaration and serialization.

You don't have to worry about creating extensive Swagger-specific comments or editing a Swagger file by hand. You can get a good enough Swagger document with very little work, using the code you already have!

  • Use your existing serializable Go structs to document what an endpoint returns. Really, Sashay will figure out the OpenAPI contents using reflection.
  • Declare your parameters using Go structs. If you are binding and validating using structs in your endpoint handlers, you can use the same structs for Sashay.
  • You can extend Sashay to handle your own types and struct tags, such as if you use custom time/date types, or want to parse validation struct tags into something you can place in your OpenAPI doc.

Creating a nicer OpenAPI 3.0 document from your existing code is generally a matter of adding a bit of annotation to struct tags or using some Sashay types around your API's types. For example, given the following code:

type Pet struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
    Tag  string `json:"tag"`
}
type Error struct {
    Code    int32  `json:"code"`
    Message string `json:"message"`
}

sw := sashay.New("PetStore API", "Manage your pet store with our API", "1.0.0")
sw.Add(sashay.NewOperation(
    "GET",
    "/pets",
    "Return all pets.",
    struct {
        Status string `query:"status"`
    }{},
    []Pet{},
    Error{},
))
sw.Add(sashay.NewOperation(
    "POST",
    "/pets",
    "Create a pet.",
    struct {
        Pretty bool   `query:"pretty" default:"true" description:"If true, return pretty-printed JSON."`
        Name   string `json:"name"`
    }{},
    Pet{},
    Error{},
))
sw.Add(sashay.NewOperation(
    "GET",
    "/pets/:id",
    "Fetch info about a pet.",
    struct {
        ID   int    `path:"id"`
        Name string `json:"name"`
    }{},
    Pet{},
    Error{},
))

You can generate the following YAML:

openapi: 3.0.0
info:
  title: PetStore API
  description: Manage your pet store with our API
  version: 1.0.0
paths:
  /pets:
    get:
      operationId: getPets
      summary: Return all pets.
      parameters:
        - name: status
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ok response
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Pet'
        'default':
          description: error response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    post:
      operationId: postPets
      summary: Create a pet.
      parameters:
        - name: pretty
          in: query
          description: If true, return pretty-printed JSON.
          schema:
            type: boolean
            default: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
      responses:
        '201':
          description: ok response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
        'default':
          description: error response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /pets/{id}:
    get:
      operationId: getPetsId
      summary: Fetch info about a pet.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: ok response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
        'default':
          description: error response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    Error:
      type: object
      properties:
        code:
          type: integer
          format: int32
        message:
          type: string
    Pet:
      type: object
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        tag:
          type: string

See the documentation at godoc.org for more information and tutorials about how to use Sashay. See https://swagger.io/specification/ for more info about the spec.

Documentation

Overview

Package sashay allows you to generate OpenAPI 3.0 (Swagger) files using executable Go code, including the same types you already use for parameter declaration and serialization.

You don't have to worry about creating extensive Swagger-specific comments or editing a Swagger file by hand. You can get a good enough Swagger document with very little work, using the code you already have!

- Use your existing serializable Go structs to document what an endpoint returns. Really, Sashay will figure out the OpenAPI contents using reflection.

- Declare your parameters using Go structs. If you are binding and validating using structs in your endpoint handlers, you can use the same structs for Sashay.

- You can extend Sashay to handle your own types and struct tags, such as if you use custom time/date types, or want to parse validation struct tags into something you can place in your OpenAPI doc.

Creating a nicer OpenAPI 3.0 document from your existing code is generally a matter of adding a bit of annotation to struct tags or using some Sashay types around your API's types.

See https://swagger.io/specification/ for more information about the OpenAPI 3.0 spec.

Sashay Tutorial

There are generally three parts to defining and generating Swagger docs using Sashay:

- Define the sashay.Sashay registry which holds all information that will be in the document. This is usually a singleton for an entire service, or passed to all route registration.

- Define new sashay.Operation instances where you have your handlers, adding them to the registry using the Add method as you go.

- Generate the YAML string using the WriteYAML method.

In the following sections, we will go through the steps to build something very similar to the "Pet Store API" Swagger example. This is the default example API at https:/editor.swagger.io/#/.

The "Pet Store API" OpenAPI 3.0 YAML file the service is based on is here: https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/petstore-expanded.yaml

There is code for a "Pet Store API" Go server here: https://github.com/swagger-api/swagger-codegen/blob/master/samples/server/petstore/go-api-server/go/routers.go Note that this is for their Swagger 2.0 definition, which is much larger than the 3.0 definition.

Our code will be based off that Pet Store code. There are many ways to structure a Go service; the Pet Store example is only one such structure, with centralized routing and general HTTP handlers. Sashay, being a library, can fit into any application setup. It just needs to get the right calls, which should be clear by the end, as the API has very few moving parts.

Tutorial Step 1- Define Service Level Settings

In our example petstore.yaml file, we have the following settings that apply to the service, rather than any specific paths, operations, or resources:

openapi: 3.0.0
info:
  title: Swagger Petstore
  description: A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification
  termsOfService: http://swagger.io/terms/
  contact:
    email: apiteam@swagger.io
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.0
tags:
  - name: pet
    description: Everything about your Pets
  - name: store
    description: Access to Petstore orders
  - name: user
    description: Operations about user
servers:
  - url: http://petstore.swagger.io/api
    description: Public API server
security:
  - apiKeyAuth: []

We can use the following code to create a *sashay.Sashay object that will generate that YAML. This can be a stateful singleton, placed somewhere accessible to all handlers and routers, like some common or config file. Later in our example, we create the instance in our main function, and pass it to the router:

sa := sashay.New(
	"Swagger Petstore",
	"A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification",
	"1.0.0").
	AddAPIKeySecurity("header", "api_key").
	SetTermsOfService("http://swagger.io/terms/").
	SetContact("", "", "apiteam@swagger.io").
	SetLicense("Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0.html").
	AddServer("http://petstore.swagger.io/api", "Public API server").
	AddTag("pet", "Everything about your Pets").
	AddTag("store", "Access to Petstore orders").
	AddTag("user", "Operations about user")

The way this code maps to the YAML should be pretty self-explanatory. For more information on any of these, you can refer to the OpenAPI documentation, as it maps cleanly.

This code uses "apiKey" security, via AddAPIKeySecurity. The sashay.Sashay object also has AddBasicAuthSecurity and AddJWTSecurity methods available.

Tutorial Step 2- Define Operations

An "operation" in OpenAPI 3.0 is a description for a path/route and method. For example, here is the GET /pets endpoint Swagger YAML:

paths:
  /pets:
    get:
      operationId: getPets
      summary: Returns all pets from the system that the user has access to
      parameters:
        - name: tags
          in: query
          description: tags to filter by
          schema:
            type: array
            items:
              type: string
        - name: limit
          in: query
          description: maximum number of results to return
          schema:
            type: integer
            format: int32
      responses:
        '200':
          description: ok response
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Pet'
        'default':
          description: error response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

Let's go through the Go code required for that YAML.

First there is the code for the models. These are probably not endpoint-specific, but shared for the entire application. There is nothing swagger-related to this code; it already exists for the service:

type Pet struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
	Tag  string `json:"tag"`
}
type Error struct {
	Code    int32  `json:"code"`
	Message string `json:"message"`
}

Next there is the actual route handler. This also has nothing Swagger-specific. It is code that already exists for your service.

func GetPets (http.ResponseWriter, *http.Request) {
	// Your code here
}

Finally, we get to the route definitions/registration. This, too, is something that needs to happen for any service. The changes here have to do with registering a route adding it both to your HTTP framework's router, and the sashay.Sashay registry. Note that the sashay.Operation object has the method and path necessary to register routes in pretty much every framework. In this code, we have a custom Route struct that marries the Operation along with an http.HandlerFunc.

type Route struct {
	operation sashay.Operation
	handler   http.HandlerFunc
}

func RegisterRoutes(router *FrameworkRouter, sw *sashay.Sashay) {
	for _, route := range routes {
		sw.Add(route.operation)
		router.AddRoute(route.operation.Method, route.operation.Path, route.handler)
	}
}

var routes = []Route{
	{
		sashay.NewOperation(
			"GET",
			"/pets",
			"Returns all pets from the system that the user has access to",
			struct {
				Tags  []string `query:"tags" description:"tags to filter by"`
				Limit int32    `query:"limit" description:"maximum number of results to return"`
			}{},
			[]Pet{},
			Error{},
		),
		GetPets,
	},
}

Tutorial Step 3- Generate the OpenAPI File

Finally, there is the server startup code, usually in some sort of main() function. This code initializes a new sashay.Sashay instance, registers routes, and writes to a yaml file if the program is run with a -swagger argument. This code in particular is going to be different depending on your conventions; the following code is just an idea to show how this all fits together.

func StartServer() {
	sw := PetStoreSwagger()
	router := &FrameworkRouter{}
	RegisterRoutes(router, sw)
	if len(os.Args) > 0 && os.Args[0] == "-swagger" {
		yaml := sw.BuildYAML()
		ioutil.WriteFile("swagger.yml", []byte(yaml), 0644)
		os.Exit(0)
	}
	http.ListenAndServe(":8080", router)
}

func PetStoreSwagger() *sashay.Sashay {
	return swagger.New(
		"Swagger Petstore",
		"A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification",
		"1.0.0").
		AddAPIKeySecurity("header", "api_key").
		SetTermsOfService("http://swagger.io/terms/").
		SetContact("", "", "apiteam@swagger.io").
		SetLicense("Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0.html").
		AddServer("http://petstore.swagger.io/api", "Public API server").
		AddTag("pet", "Everything about your Pets").
		AddTag("store", "Access to Petstore orders").
		AddTag("user", "Operations about user")
}

That's all there is to it. You can see a fuller example in the petstore_test.go file, which contains the preceding code but with more routes.

Sashay Detail- Basic Parameters

The sashay.Operation object supports defining an endpoint's parameters. Because parameter settings can be quite detailed, this package will parse some parameter settings from struct tags. Let's look at the Parameters field in the following sashay.Operation definition:

sashay.NewOperation(
	"POST",
	"/users/:id",
	"Update the user.",
	struct {
		ID int `path:"id" validate:"min=1"`
		Pretty bool `query:"pretty" description:"If true, return pretty-printed JSON." default:"true"`
		NoResponse bool `header:"X-NO-RESPONSE" description:"If true, return a 204 rather than the updated User."`
		Name string `json:"name"`
	}{},
	nil,
	nil,
)

The struct tags of "path", "header", and "query" define the name of the parameter in the path/header/query. Using the "json" tag indicates the parameter is included in the request body. This Operation generates the following YAML:

paths:
  /users/{id}:
	post:
	  operationId: postUsersId
	  summary: Update the user.
	  parameters:
		- name: id
		  in: path
		  required: true
		  schema:
			type: integer
			format: int64
		- name: pretty
		  in: query
		  description: If true, return pretty-printed JSON.
		  schema:
			type: boolean
			default: true
		- name: X-NO-RESPONSE
		  in: header
		  description: If true, return a 204 rather than the updated User.
		  schema:
			type: boolean
	  requestBody:
		required: true
		content:
		  application/json:
			schema:
			  type: object
			  properties:
				name:
				  type: string
	  responses:
		'204':
		  description: The operation completed successfully.
		'default':
		  description: error response

The parameter struct definitions are nice, but the best feature is that they are actually executable Go code that you can use for the parameter validation and binding in your own endpoints! In practice, your Operation definitions will look something like this:

type getUsersParams struct {
	Status string `query:"status" validate:"eq=active|eq=deleted"`
}
getUsersOp := sashay.NewOperation(
	"GET",
	"/users",
	"Get users",
	getUsersParams{},
	[]User{},
	ErrorModel{},
)
getUsersHandler := func(c echo.Context) error {
	params := getUsersParams{}
	if err := c.Bind(&params); err != nil {
		return err
	}
	if err := c.Validate(params); err != nil {
		return err
	}
	var users []User
	// Logic to get users
	return c.JSON(200, users)
}

sw.Add(getUsersOp)
router.Add(getUsersOp.Method, getUsersOp.Path, getUsersHandler)

The actual getUsersHandler code uses the same struct to describe itself as it does in code. The same is true for response types- the schema is built from the real objects, with the json struct tags, not separate documentation.

Note that Sashay never uses $ref for parameters (resources in POST/PUT request bodies). Even if the same type is used for a request and a response, it'll be expanded in the requestBody section and a $ref in the response section. This may change in the future.

Sashay Detail- Request Bodies

Struct types can also be used in parameters. Usually, these will be nested structs for request bodies:

sashay.NewOperation(
	"POST",
	"/users",
	"Create a user.",
	struct {
		Name struct {
			First string `json:"first"`
			Last string `json:"last"`
		} `json:"name"`
	}{},
	nil,
	nil,
)

You can see the requestBody YAML it generates:

paths:
  /users:
	post:
	  operationId: postUsers
	  summary: Create a user.
	  requestBody:
		required: true
		content:
		  application/json:
			schema:
			  type: object
			  properties:
				name:
				  type: object
				  properties:
					first:
					  type: string
					last:
					  type: string

Sashay Detail- Representing Custom Types

Note that out of the box, Sashay will treat simple custom types (like `type MyString string`) as their underlying simple type, and will walk any custom structs.

However, sometimes you want to use Go struct types that are represented as data types in Swagger. Times are an exampmle of this: time.Time is a Go struct type, but we want to represent it with a string data type in Swagger (type: string, format: date-time). For example, let's say "month" is a common concept in our API, so we represent it with a type:

type Month struct {
	Year int
	Month int
}

type Params struct {
	Month Month `query:"month"`
}

When we have a struct field with a type of MyTime, we would normally get a schema of:

type: object
properties:
  time:
    type: object
    properties:
      year:
        type: integer
      month:
        type: integer

However, what we actually want is something like this:

type: object
properties:
  time:
    type: string
    format: YYYY-MM

We can define a mapping between custom types and a "data type transformer" to do this. For example, to get the desired Swagger we would use a SimpleDataTyper transformer:

sa.DefineDataType(Month{}, SimpleDataTyper("string", "YYYY-MM"))

DefineDataType takes in an instance of a value to map into a data type and the DataTyper transformer function. SimpleDataTyper uses the given type and format strings.

Sashay includes other built-in DataTypers:

- DefaultDataTyper() will parse the "default" struct tag and write it into the "default" field.

- ChainDataTyper calls one DataTyper after another. The most common usage is to use this around SimpleDataTyper and DefaultDataTyper, but feel free to get creative.

- BuiltinDataTyperFor returns the default DataTyper behavior for a type. This is useful when you want to extend the behavior for a built-in type, but not entirely replace it (we use it below, for a custom string data typer behavior).

The DataTyper function can get more creative, too. For example, it can parse struct fields to inform what should write into the Swagger file. Consider a "unit of time" type that can be used for any unit, rather than custom month, day, etc types:

type UnitOfTime struct {
	time time.Time
	unit string
}

And using it for parameters looks like:

type Params struct {
	Month UnitOfTime `query:"month" timeunit:"month"`
}

We could use a DataTyper that reads the "timeunit" struct tag, and specifies the "format" field based on that:

sw.DefineDataType(UnitOfTime{}, func(f sashay.Field, of sashay.ObjectFields) {
	of["type"] = "string"
	if timeunit := f.StructField.Tag.Get("timeunit"); timeunit != "" {
		switch timeunit {
		case "date":
			of["format"] = "date"
		case "month":
			of["format"] = "YYYY-MM"
		}
	}
})

Sashay Detail- Other Advanced DataTyper Usage

We can use DefineDataType to customize all sorts of behavior. One common usage is parsing tags to specify other information about a field, like we did with "timeunit" above. Perhaps we want to parse an "enum" tag that specifies valid values for a string field:

extractEnum := func(f sashay.Field, of sashay.ObjectFields) {
	of["type"] = "string"
	if enum := f.StructField.Tag.Get("enum"); enum != "" {
		values := strings.Split(enum, "|")
		of["enum"] = fmt.Sprintf("['%s']", strings.Join(values, "', '"))
	}
}
sw.DefineDataType("", sashay.BuiltinDataTyperFor("", extractEnum))

Now, when we have a string with the "enum" struct tag, we will get the "enum" field in our YAML:

type Params struct {
	Status string `json:"status" enum:"on|off"`
}

schema:
  type: object
  properties:
    status:
      type: string
      enum: ['on', 'off']

The goal of Sashay is, you may recall, to reuse as much of your existing code as possible, and to build off it rather than require a bunch of custom annotation or documentation. In practice, this often means pulling this sort of data out of "validation" struct tags, rather than custom struct tags like "enum" or "timeunit", but the idea is the same.

For an example of this in action, and a good basis for hooking your own validation needs up to Sashay, see validator_data_typer_test.go. It includes a fully-functional example using go-validator style struct tags to inform data type fields.

Sashay Detail- Responses

The other part of sashay.Operation that may require some customization are usually responses. Sashay tries to be smart and enforce some conventions:

- Successful POSTs returns a 201. - All other successful methods return a 200. - All operations get a 'default' error response.

For example, let's look at the Go code to fetch an array of users (we can use an empty User slice, or a custom Users slice type would work fine).

sashay.NewOperation(
	"GET",
	"/users",
	"",
	nil,
	[]User{},
	ErrorModel{},
)

The 200 response is an array that points to references of the User schema, and the User and Error Model are defined in components/schemas:

paths:
  /users:
	get:
	  operationId: getUsers
	  responses:
		'200':
		  description: ok response
		  content:
			application/json:
			  schema:
				type: array
				items:
				  $ref: '#/components/schemas/User'
		'default':
		  description: error response
		  content:
			application/json:
			  schema:
				$ref: '#/components/schemas/ErrorModel'
components:
  schemas:
	ErrorModel:
	  type: object
	  properties:
		error:
		  type: object
		  properties:
			message:
			  type: string
			code:
			  type: integer
			  format: int64
	User:
	  type: object
	  properties:
		result:
		  type: object
		  properties:
			id:
			  type: integer
			  format: int64
			name:
			  type: string

However, sometimes you need more advanced response information. In particular, you may want to document specific error conditions or return type shapes. You can use the swagger.Response or swagger.Responses object for this:

sashay.NewOperation(
	"GET",
	"/is_teapot",
	"Error if the server is a teapot.",
	nil,
	swagger.Responses{
		swagger.NewResponse(200, "Not a teapot.", TeapotResponse{}),
		swagger.NewResponse(203, "I may not be a teapot", TeapotResponse{}),
	},
	swagger.NewResponse(418, "Yes, I am sure a teapot!", TeapotError{}),
)

Note the calls to NewResponse, and the Responses slice. In this way, the default codes can be overwritten, and multiple responses can be specified:

paths:
  /is_teapot:
	get:
	  operationId: getIsTeapot
	  summary: Error if the server is a teapot.
	  responses:
		'200':
		  description: Not a teapot.
		  content:
			application/json:
			  schema:
				$ref: '#/components/schemas/TeapotResponse'
		'203':
		  description: I may not be a teapot
		  content:
			application/json:
			  schema:
				$ref: '#/components/schemas/TeapotResponse'
		'418':
		  description: Yes, I am sure a teapot!
		  content:
			application/json:
			  schema:
				$ref: '#/components/schemas/TeapotError'

Finally, there are a couple special cases for responses:

- If a response is a string type, rather than a struct, it is assumed to be of content type text/plain.

- If a response is an empty struct (`struct{}{}`), use application/json with no schema.

Sashay Detail- Pointer Fields

Sashay treats value and pointer fields the same. In other words, *bool and bool will use the same data type/schema. When you register a data type (refer to DefineDataType), the same DataTyper is used for pointer fields of that type.

The primary use case for pointer fields in Go is to represent optional fields. There's nothing much for Sashay to do with that information, because both parameters and object fields are optional/not-required in Swagger by default. For example, in parameters, "required: false" is the default. And for schemas (request bodies, responses), the "nullable: true" attribute is quite semantically different than the "optional" meant by a Go pointer field.

In the future, Sashay may support more more extensive specification around required fields, but not right now.

Example (Petstore)
package main

import (
	"fmt"
	"github.com/rgalanakis/sashay"
	"io/ioutil"
	"net/http"
	"os"
)

// Stand-in for whatever HTTP framework you are using
type FrameworkRouter struct{}

func (FrameworkRouter) ServeHTTP(http.ResponseWriter, *http.Request)     {}
func (FrameworkRouter) AddRoute(method, path string, h http.HandlerFunc) {}

// main.go
func PetStoreSwagger() *sashay.Sashay {
	return sashay.New(
		"Swagger Petstore",
		"A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification",
		"1.0.0").
		AddAPIKeySecurity("header", "api_key").
		SetTermsOfService("http://swagger.io/terms/").
		SetContact("", "", "apiteam@swagger.io").
		SetLicense("Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0.html").
		AddServer("http://petstore.swagger.io/api", "Public API server").
		AddTag("pet", "Everything about your Pets").
		AddTag("store", "Access to Petstore orders").
		AddTag("user", "Operations about user")
}

// noinspection GoUnusedExportedFunction
func StartServer() {
	sw := PetStoreSwagger()
	router := &FrameworkRouter{}
	RegisterRoutes(router, sw)
	if len(os.Args) > 0 && os.Args[0] == "-swagger" {
		yaml := sw.BuildYAML()
		ioutil.WriteFile("swagger.yml", []byte(yaml), 0644)
		os.Exit(0)
	}
	http.ListenAndServe(":8080", router)
}

// handlers.go file
// noinspection GoUnusedParameter
func GetPets(http.ResponseWriter, *http.Request) {
	// Your code here
}

var CreatePet http.HandlerFunc
var GetPet http.HandlerFunc
var DeletePet http.HandlerFunc

// models.go file
type Pet struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
	Tag  string `json:"tag"`
}
type Error struct {
	Code    int32  `json:"code"`
	Message string `json:"message"`
}

// routes.go file
type Route struct {
	operation sashay.Operation
	handler   http.HandlerFunc
}

func RegisterRoutes(router *FrameworkRouter, sw *sashay.Sashay) {
	for _, route := range routes {
		sw.Add(route.operation)
		router.AddRoute(route.operation.Method, route.operation.Path, route.handler)
	}
}

var routes = []Route{
	{
		sashay.NewOperation(
			"GET",
			"/pets",
			"Returns all pets from the system that the user has access to",
			struct {
				Tags  []string `query:"tags" description:"tags to filter by"`
				Limit int32    `query:"limit" description:"maximum number of results to return"`
			}{},
			[]Pet{},
			Error{},
		), GetPets,
	}, {
		sashay.NewOperation(
			"POST",
			"/pets",
			"Creates a new pet in the store.  Duplicates are allowed",
			struct {
				Name string `json:"name"`
				Tag  string `json:"tag"`
			}{},
			sashay.NewResponse(200, "pet response", Pet{}), // Normally a 201
			Error{},
		), CreatePet,
	}, {
		sashay.NewOperation(
			"GET",
			"/pets/:id",
			"Returns a user based on a single ID, if the user does not have access to the pet",
			struct {
				ID int `path:"id" description:"ID of pet to fetch"`
			}{},
			Pet{},
			Error{},
		), GetPet,
	}, {
		sashay.NewOperation(
			"DELETE",
			"/pets/:id",
			"deletes a single pet based on the ID supplied",
			struct {
				ID int `path:"id" description:"ID of pet to delete"`
			}{},
			nil, // 204 response
			Error{},
		), DeletePet,
	},
}

func main() {
	sw := PetStoreSwagger()
	RegisterRoutes(&FrameworkRouter{}, sw)
	fmt.Println(sw.BuildYAML())
}
Output:
openapi: 3.0.0
info:
  title: Swagger Petstore
  description: A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification
  termsOfService: http://swagger.io/terms/
  contact:
    email: apiteam@swagger.io
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.0
tags:
  - name: pet
    description: Everything about your Pets
  - name: store
    description: Access to Petstore orders
  - name: user
    description: Operations about user
servers:
  - url: http://petstore.swagger.io/api
    description: Public API server
paths:
  /pets:
    get:
      operationId: getPets
      summary: Returns all pets from the system that the user has access to
      parameters:
        - name: tags
          in: query
          description: tags to filter by
          schema:
            type: array
            items:
              type: string
        - name: limit
          in: query
          description: maximum number of results to return
          schema:
            type: integer
            format: int32
      responses:
        '200':
          description: ok response
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Pet'
        'default':
          description: error response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    post:
      operationId: postPets
      summary: Creates a new pet in the store.  Duplicates are allowed
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                tag:
                  type: string
      responses:
        '200':
          description: pet response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
        'default':
          description: error response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /pets/{id}:
    get:
      operationId: getPetsId
      summary: Returns a user based on a single ID, if the user does not have access to the pet
      parameters:
        - name: id
          in: path
          required: true
          description: ID of pet to fetch
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: ok response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
        'default':
          description: error response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    delete:
      operationId: deletePetsId
      summary: deletes a single pet based on the ID supplied
      parameters:
        - name: id
          in: path
          required: true
          description: ID of pet to delete
          schema:
            type: integer
            format: int64
      responses:
        '204':
          description: The operation completed successfully.
        'default':
          description: error response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    Error:
      type: object
      properties:
        code:
          type: integer
          format: int32
        message:
          type: string
    Pet:
      type: object
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        tag:
          type: string
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: api_key
security:
  - apiKeyAuth: []
Example (Readme)
package main

import (
	"fmt"
	"github.com/rgalanakis/sashay"
)

func main() {
	type Pet struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
		Tag  string `json:"tag"`
	}
	type Error struct {
		Code    int32  `json:"code"`
		Message string `json:"message"`
	}

	sw := sashay.New("PetStore API", "Manage your pet store with our API", "1.0.0")
	sw.Add(sashay.NewOperation(
		"GET",
		"/pets",
		"Return all pets.",
		struct {
			Status string `query:"status"`
		}{},
		[]Pet{},
		Error{},
	))
	sw.Add(sashay.NewOperation(
		"POST",
		"/pets",
		"Create a pet.",
		struct {
			Pretty bool   `query:"pretty" default:"true" description:"If true, return pretty-printed JSON."`
			Name   string `json:"name"`
		}{},
		Pet{},
		Error{},
	))
	sw.Add(sashay.NewOperation(
		"GET",
		"/pets/:id",
		"Fetch info about a pet.",
		struct {
			ID   int    `path:"id"`
			Name string `json:"name"`
		}{},
		Pet{},
		Error{},
	))
	fmt.Println(sw.BuildYAML())
}
Output:
openapi: 3.0.0
info:
  title: PetStore API
  description: Manage your pet store with our API
  version: 1.0.0
paths:
  /pets:
    get:
      operationId: getPets
      summary: Return all pets.
      parameters:
        - name: status
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ok response
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Pet'
        'default':
          description: error response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    post:
      operationId: postPets
      summary: Create a pet.
      parameters:
        - name: pretty
          in: query
          description: If true, return pretty-printed JSON.
          schema:
            type: boolean
            default: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
      responses:
        '201':
          description: ok response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
        'default':
          description: error response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /pets/{id}:
    get:
      operationId: getPetsId
      summary: Fetch info about a pet.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: ok response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
        'default':
          description: error response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    Error:
      type: object
      properties:
        code:
          type: integer
          format: int32
        message:
          type: string
    Pet:
      type: object
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        tag:
          type: string

Index

Examples

Constants

This section is empty.

Variables

View Source
var BuiltinDataTypeValues = []interface{}{
	int(0),
	int64(0),
	int32(0),
	"",
	false,
	float64(0),
	float32(0),
	time.Time{},
	make(map[string]interface{}, 0),
	make([]map[string]interface{}, 0),
	make([]interface{}, 0),
}

BuiltinDataTypeValues is a slice of values of all supported data types. Use it for when you want to define custom DataTypers for the builtin types, like if you are parsing validations.

Functions

This section is empty.

Types

type DataTyper

type DataTyper func(f Field, of ObjectFields)

DataTyper modifies the ObjectFields for the passed field Field. The ObjectFields are written into the schema for a data type.

func BuiltinDataTyperFor

func BuiltinDataTyperFor(value interface{}, chained ...DataTyper) DataTyper

BuiltinDataTyperFor returns the default/builtin DataTyper for type of value. The default data typers are always SimpleDataTyper with the right type and format fields. If value is an unsupported type, return only the DefaultDataTyper.

func ChainDataTyper

func ChainDataTyper(typers ...DataTyper) DataTyper

ChainDataTyper returns a DataTyper that will call each function in typers in order, merging all the returned ObjectFields. In conflict, later typers will take priority.

Example
dt := sashay.ChainDataTyper(
	sashay.SimpleDataTyper("string", "format1"),
	func(_ sashay.Field, of sashay.ObjectFields) {
		of["format"] = "format2"
	})
fields := sashay.ObjectFields{}
dt(sashay.NewField("abc"), fields)
fmt.Println("Type:", fields["type"], "Format:", fields["format"])
Output:
Type: string Format: format2

func DefaultDataTyper

func DefaultDataTyper() DataTyper

DefaultDataTyper returns a DataTyper that sets the "default" field of the data type to the "default" value of the struct tag on the Field passed to it.

func SimpleDataTyper

func SimpleDataTyper(swaggerType, format string) DataTyper

SimpleDataTyper returns a DataTyper that will specify a "type" field of type, and a "format" field of format, if not empty.

Example
dt := sashay.SimpleDataTyper("string", "date-time")
fields := sashay.ObjectFields{}
dt(sashay.NewField("abc"), fields)
fmt.Println("Type:", fields["type"], "Format:", fields["format"])
Output:
Type: string Format: date-time

type Field

type Field struct {
	// Interface is the original passed-in field value.
	Interface interface{}
	// Type is reflect.TypeOf(field.Interface)
	Type reflect.Type
	// Kind is reflect.TypeOf(field.Interface).Kind()
	Kind reflect.Kind
	// Value is reflect.ValueOf(field.Interface)
	Value reflect.Value
	// DataTyper is the mapping function for this value and type.
	// It may be nil.
	// In general it is only set for a single Field instance for a type,
	// when defined through Sashay#DefineDataType.
	DataTyper DataTyper
	// StructField is the StructField the Field was created from.
	// If it was not created from a field, FromStructField will be false.
	StructField     reflect.StructField
	FromStructField bool
}

Field is a container for reflection information about a value. Since we need this repeatedly, we parse it once and pass it around.

func NewField

func NewField(v interface{}, fields ...reflect.StructField) Field

NewField returns a Field initialized from v. If fields is provided, its first item indicates the Field was parsed from a StructField.

func ZeroSliceValueField

func ZeroSliceValueField(t reflect.Type) Field

For a reflect.Type for a slice, return a Field representing an item of the slice's underlying type. So ZeroSliceValueField(reflect.TypeOf([]MyType{}) would be the same as NewField(MyType{}).

Example
package main

import (
	"fmt"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"github.com/rgalanakis/sashay"
	"reflect"
)

func main() {
	type User struct{}

	slice := make([]User, 0)
	sliceField := sashay.NewField(slice)
	userField := sashay.NewField(User{})
	zeroSliceField := sashay.ZeroSliceValueField(reflect.TypeOf(slice))
	fmt.Println("sliceField type name:", sliceField.Type)
	fmt.Println("userField type name:", userField.Type)
	fmt.Println("zeroSliceField type name:", zeroSliceField.Type)
}

var _ = Describe("Field", func() {
	It("can render itself as a string", func() {
		f := sashay.NewField(5)
		Expect(f.String()).To(Equal("Field{kind: int, type:int}"))
	})
})

var _ = Describe("Fields", func() {
	Describe("FlattenSliceTypes", func() {
		It("replaces Fields with slice types with their underlying value", func() {
			intField := sashay.NewField(5)
			strSliceField := sashay.NewField([]string{})
			flattened := sashay.Fields{intField, strSliceField}.FlattenSliceTypes()
			Expect(flattened[0].Kind.String()).To(Equal(reflect.Int.String()))
			Expect(flattened[1].Kind.String()).To(Equal(reflect.String.String()))
		})
	})
})
Output:
sliceField type name: []sashay_test.User
userField type name: sashay_test.User
zeroSliceField type name: sashay_test.User

func (Field) Nil

func (f Field) Nil() bool

Return true if f was created from nil.

func (Field) String

func (f Field) String() string

type Fields

type Fields []Field

Fields is a slice of Field instances.

func (Fields) Compact

func (fs Fields) Compact() Fields

Compact returns a new Fields with Nil values removed.

func (Fields) Distinct

func (fs Fields) Distinct() Fields

Distinct eliminates Fields with the same Type.

func (Fields) FlattenSliceTypes

func (fs Fields) FlattenSliceTypes() Fields

FlattenSliceTypes replaces Fields with slice types with their underlying value (see ZeroSliceTypeField).

func (Fields) Len

func (fs Fields) Len() int

func (Fields) Less

func (fs Fields) Less(i, j int) bool

func (Fields) RemoveAnonymousTypes

func (fs Fields) RemoveAnonymousTypes() Fields

RemoveAnonymousTypes removes Fields that have no PkgPath, such as anonymous types.

func (Fields) Swap

func (fs Fields) Swap(i, j int)

type Method

type Method string

Method represents an HTTP method string ("get", "post", etc.).

func NewMethod

func NewMethod(s string) Method

NewMethod returns a Method with the right casing/form from an HTTP verb string. "get" => "get" "POST" => "post"

Example
package main

import (
	"fmt"
	"github.com/rgalanakis/sashay"
)

func main() {
	fmt.Println(sashay.NewMethod("GET"))
	fmt.Println(sashay.NewMethod("get"))
}
Output:
get
get

type ObjectFields

type ObjectFields map[string]string

ObjectFields is a mapping of the fields for some object, such as a data type (https://swagger.io/specification/#dataTypes) or security object (https://swagger.io/specification/#securitySchemeObject). In general, this always includes a "type" key, and other fields are based on the object being represented (data type fields often have a "format", security objects a "scheme").

func (ObjectFields) Sorted

func (dtf ObjectFields) Sorted() [][]string

Sorted returns a slice of string tuples suitable for writing to YAML. "type" should always be first, otherwise sort keys alphabetically.

type Operation

type Operation struct {
	// Method is a string like GET, POST, etc.
	Method string
	// Path is the path for the endpoint. Parameters should have a leading colon, like /users/:id.
	Path string
	// Summary is the summary for the endpoint. Please provide it.
	Summary string
	// Description is an optional longer endpoint description with Markdown support.
	Description string
	// Params is a zero'd instance of parameters for the endpoint.
	// If there are no params, use nil.
	Params interface{}
	// ReturnOk is a zero'ed instance of the struct used for successful responses from the endpoint.
	// If nil, assume a 204 success and use no body.
	ReturnOk interface{}
	// ReturnOk is a zero'ed instance of the struct used for an error response from the endpoint.
	// Since all endpoints should return the same error response shape,
	// we use thue 'default' Swagger response field. We can add custom error code mapping in the future.
	ReturnErr interface{}
	// Tags is a slice of string tags for the operation.
	// Tags can be used for logical grouping of operations by resources or any other qualifier.
	Tags []string
}

Operation is the definition for an endpoint (method and path). See https://swagger.io/specification/#operationObject

func NewOperation

func NewOperation(method, path, summary string, params, returnOK, returnErr interface{}) Operation

NewOperation returns a new Operation instance with the given parameters.

func (Operation) AddTags

func (op Operation) AddTags(tags ...string) Operation

AddTags appends new tags to the receiver and returns a modified instance.

func (Operation) WithDescription

func (op Operation) WithDescription(desc string) Operation

WithDescription sets the description on the receiver and returns a modified instance.

type OperationID

type OperationID string

OperationID represents a Swagger operationId string.

func NewOperationID

func NewOperationID(op Operation) OperationID

NewOperationID returns an OperationID that is unique for the method and endpoint.

Example
package main

import (
	"fmt"
	"github.com/rgalanakis/sashay"
)

func main() {
	op := sashay.NewOperation("GET", "/users/:id", "", nil, nil, nil)
	fmt.Println(sashay.NewOperationID(op))
}
Output:
getUsersId

type Path

type Path string

Path represents a Swagger path, like "/users/{id}/pets".

func NewPath

func NewPath(s string) Path

NewPath returns a Path with the right form from a Go-style route. "/users/:id" => "/users/{id}"

Example
package main

import (
	"fmt"
	"github.com/rgalanakis/sashay"
)

func main() {
	fmt.Println(sashay.NewPath("/users/:id"))
	fmt.Println(sashay.NewPath("/users/:id/pets"))
	fmt.Println(sashay.NewPath("/users/{id}"))
	fmt.Println(sashay.NewPath("/users/{id}/pets"))
}
Output:
/users/{id}
/users/{id}/pets
/users/{id}
/users/{id}/pets

type Response

type Response struct {
	Code        string
	Description string
	Field       Field
}

Response defines a Swagger response, roughly corresponding to https://swagger.io/specification/#responseObject (see also https://swagger.io/docs/specification/describing-responses/) Clients can use Responses and Response when they need to override the default behavior.

func NewResponse

func NewResponse(code int, description string, shape interface{}) Response

NewResponse returns a new Response initialized with the given code and description. code is an HTTP status code, or -1 for "default". shape should be the return object, like what is passed as NewOperation's returnOK or returnErr argument (something like User{} or ErrorResponse{}).

type Responses

type Responses []Response

Responses is a slice of Response objects.

type Sashay

type Sashay struct {
	// The default content type for all request bodies and responses.
	// Defaults to application/json. This can only be set document-wide,
	// and cannot vary per-endpoint right now.
	DefaultContentType string
	// contains filtered or unexported fields
}

Sashay describes an OpenAPI document, including meta info, servers, security, schemas, and all Operations. See https://swagger.io/specification/

Example (AdvancedResponses)
package main

import (
	"fmt"

	"github.com/rgalanakis/sashay"
)

func main() {
	sw := sashay.New("t", "d", "v")

	type TeapotResponse struct {
		Probability float64 `json:"prob"`
	}
	type TeapotError struct {
		Strength float64 `json:"strength"`
	}
	op := sashay.NewOperation(
		"GET",
		"/is_teapot",
		"Error if the server is a teapot.",
		nil,
		sashay.Responses{
			sashay.NewResponse(200, "Not a teapot.", TeapotResponse{}),
			sashay.NewResponse(203, "I may not be a teapot", TeapotResponse{}),
		},
		sashay.NewResponse(418, "Yes, I am sure a teapot!", TeapotError{}),
	)

	sw.Add(op)
	fmt.Println(sw.BuildYAML())
}
Output:
openapi: 3.0.0
info:
  title: t
  description: d
  version: v
paths:
  /is_teapot:
    get:
      operationId: getIsTeapot
      summary: Error if the server is a teapot.
      responses:
        '200':
          description: Not a teapot.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TeapotResponse'
        '203':
          description: I may not be a teapot
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TeapotResponse'
        '418':
          description: Yes, I am sure a teapot!
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TeapotError'
components:
  schemas:
    TeapotError:
      type: object
      properties:
        strength:
          type: number
          format: double
    TeapotResponse:
      type: object
      properties:
        prob:
          type: number
          format: double
Example (BasicParameters)
package main

import (
	"fmt"

	"github.com/rgalanakis/sashay"
)

func main() {
	sw := sashay.New("t", "d", "v")
	op := sashay.NewOperation(
		"POST",
		"/users/:id",
		"Update the user.",
		struct {
			ID         int    `path:"id" validate:"min=1"`
			Pretty     bool   `query:"pretty" description:"If true, return pretty-printed JSON." default:"true"`
			NoResponse bool   `header:"X-NO-RESPONSE" description:"If true, return a 204 rather than the updated User."`
			Name       string `json:"name"`
		}{},
		nil,
		nil,
	)
	sw.Add(op)
	fmt.Println(sw.BuildYAML())
}
Output:
openapi: 3.0.0
info:
  title: t
  description: d
  version: v
paths:
  /users/{id}:
    post:
      operationId: postUsersId
      summary: Update the user.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            format: int64
        - name: pretty
          in: query
          description: If true, return pretty-printed JSON.
          schema:
            type: boolean
            default: true
        - name: X-NO-RESPONSE
          in: header
          description: If true, return a 204 rather than the updated User.
          schema:
            type: boolean
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
      responses:
        '204':
          description: The operation completed successfully.
        'default':
          description: error response
Example (BasicResponse)
sw := sashay.New("t", "d", "v")
op := sashay.NewOperation(
	"GET",
	"/users",
	"",
	nil,
	[]User{},
	ErrorModel{},
)
sw.Add(op)
fmt.Println(sw.BuildYAML())
Output:
openapi: 3.0.0
info:
  title: t
  description: d
  version: v
paths:
  /users:
    get:
      operationId: getUsers
      responses:
        '200':
          description: ok response
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'
        'default':
          description: error response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorModel'
components:
  schemas:
    ErrorModel:
      type: object
      properties:
        error:
          type: object
          properties:
            message:
              type: string
            code:
              type: integer
              format: int64
    User:
      type: object
      properties:
        result:
          type: object
          properties:
            id:
              type: integer
              format: int64
            name:
              type: string
Example (CustomDataType)
package main

import (
	"fmt"
	"strings"
	"time"

	"github.com/rgalanakis/sashay"
)

func main() {
	sw := sashay.New("t", "d", "v")

	type UnitOfTime struct {
		time time.Time
		unit string
	}

	sw.DefineDataType(UnitOfTime{}, func(f sashay.Field, of sashay.ObjectFields) {
		of["type"] = "string"
		if timeunit := f.StructField.Tag.Get("timeunit"); timeunit != "" {
			switch timeunit {
			case "date":
				of["format"] = "date"
			case "month":
				of["format"] = "YYYY-MM"
			}
		}
	})

	extractEnum := func(f sashay.Field, of sashay.ObjectFields) {
		of["type"] = "string"
		if enum := f.StructField.Tag.Get("enum"); enum != "" {
			values := strings.Split(enum, "|")
			of["enum"] = fmt.Sprintf("['%s']", strings.Join(values, "', '"))
		}
	}
	sw.DefineDataType("", sashay.BuiltinDataTyperFor("", extractEnum))

	sw.Add(sashay.NewOperation(
		"POST",
		"/stuff",
		"Update stuff.",
		struct {
			StartMonth UnitOfTime `json:"startMonth" timeunit:"month"`
			EndDay     UnitOfTime `json:"endDay" timeunit:"date"`
			Status     string     `json:"status" enum:"on|off"`
		}{},
		nil,
		nil,
	))
	fmt.Println(sw.BuildYAML())
}
Output:
openapi: 3.0.0
info:
  title: t
  description: d
  version: v
paths:
  /stuff:
    post:
      operationId: postStuff
      summary: Update stuff.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                startMonth:
                  type: string
                  format: YYYY-MM
                endDay:
                  type: string
                  format: date
                status:
                  type: string
                  enum: ['on', 'off']
      responses:
        '204':
          description: The operation completed successfully.
        'default':
          description: error response
Example (NestedParams)
package main

import (
	"fmt"

	"github.com/rgalanakis/sashay"
)

func main() {
	sw := sashay.New("t", "d", "v")
	op := sashay.NewOperation(
		"POST",
		"/users",
		"Create a user.",
		struct {
			Name struct {
				First string `json:"first"`
				Last  string `json:"last"`
			} `json:"name"`
		}{},
		nil,
		nil,
	)
	sw.Add(op)
	fmt.Println(sw.BuildYAML())
}
Output:
openapi: 3.0.0
info:
  title: t
  description: d
  version: v
paths:
  /users:
    post:
      operationId: postUsers
      summary: Create a user.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: object
                  properties:
                    first:
                      type: string
                    last:
                      type: string
      responses:
        '204':
          description: The operation completed successfully.
        'default':
          description: error response
Example (UsableParams)
sw := sashay.New("t", "d", "v")

type getUsersParams struct {
	Status string `query:"status" validate:"eq=active|eq=deleted"`
}
getUsersOp := sashay.NewOperation(
	"GET",
	"/users",
	"Get users",
	getUsersParams{},
	[]User{},
	ErrorModel{},
)
getUsersHandler := func(w http.ResponseWriter, r *http.Request) {
	params := getUsersParams{Status: r.URL.Query().Get("status")}
	if err := validate(params); err != nil {
		w.WriteHeader(500)
	} else {
		var users []User
		// Logic to get users
		bytes, _ := json.Marshal(users)
		w.Write(bytes)
		w.WriteHeader(200)
	}
}

sw.Add(getUsersOp)
http.HandleFunc(getUsersOp.Path, getUsersHandler)

func New

func New(title, description, version string) *Sashay

New returns a pointer to a new Sashay instance, initialized with the provided values. See https://swagger.io/specification/#infoObject

func SelectMap

func SelectMap(source *Sashay, fn func(op Operation) *Operation) *Sashay

SelectMap is used to process a source Sashay registry into an alternative version, like for removing Operations/endpoints matching a certain criteria. A new registry is returned with all the values copied from source; the source registry is not modified.

fn is a function which takes the Operation being considered, and returns nil if the Operation should be excluded, or a pointer to the Operation if it should remain in the registry. Note that fn can modify the input Operation and those changes will be reflected into the resulting Sashay instance.

Example
package main

import (
	"fmt"
	"strings"

	"github.com/rgalanakis/sashay"
)

func main() {
	sw := sashay.New("t", "d", "v")
	// We can remove "/internal" routes
	sw.Add(sashay.NewOperation("GET", "/internal/users", "", nil, nil, nil))
	// And lowercase all paths
	sw.Add(sashay.NewOperation("GET", "/USeRS", "", nil, nil, nil))
	result := sashay.SelectMap(sw, func(op sashay.Operation) *sashay.Operation {
		if strings.Contains(op.Path, "/internal") {
			return nil
		}
		op.Path = strings.ToLower(op.Path)
		return &op
	})
	fmt.Println(result.BuildYAML())
}
Output:
openapi: 3.0.0
info:
  title: t
  description: d
  version: v
paths:
  /users:
    get:
      operationId: getUsers
      responses:
        '204':
          description: The operation completed successfully.
        'default':
          description: error response

func (*Sashay) Add

func (sa *Sashay) Add(op Operation) Operation

Add registers a Swagger operations and all the associated types.

func (*Sashay) AddAPIKeySecurity

func (sa *Sashay) AddAPIKeySecurity(in, name string) *Sashay

AddAPIKeySecurity adds type:apiKey security schema and global scope. See https://swagger.io/specification/#securitySchemeObject https://swagger.io/docs/specification/authentication/api-keys/

func (*Sashay) AddBasicAuthSecurity

func (sa *Sashay) AddBasicAuthSecurity() *Sashay

AddBasicAuthSecurity adds type:http scheme:basic security schema and global scope. See https://swagger.io/specification/#securitySchemeObject https://swagger.io/docs/specification/authentication/basic-authentication/

func (*Sashay) AddJWTSecurity

func (sa *Sashay) AddJWTSecurity() *Sashay

AddJWTSecurity adds type:http scheme:bearer security schema and global scope. See https://swagger.io/specification/#securitySchemeObject https://swagger.io/docs/specification/authentication/bearer-authentication/

func (*Sashay) AddServer

func (sa *Sashay) AddServer(url, description string) *Sashay

AddServer adds a server to the swagger file. See https://swagger.io/specification/#serverObject

func (*Sashay) AddTag

func (sa *Sashay) AddTag(name, desc string) *Sashay

func (*Sashay) BuildYAML

func (sa *Sashay) BuildYAML() string

BuildYAML returns the YAML Swagger string for the receiver.

func (*Sashay) DefineDataType

func (sa *Sashay) DefineDataType(i interface{}, dt DataTyper)

DefineDataType defines the DataTyper to use for values with the same type as i.

For example, DefineDataType(int(0), SimpleDataTyper("integer", "int64")) means that whenever the options for an integer field (or something of reflect.TypeOf(int(0)).Kind()) are written out, it will get the properties {type: "integer", format: "int64"}.

Normally Go structs are not data types- they are either walked (parameter objects) or receive schemas (response objects). However, some structs, like time.Time, should be represented as data types. To achieve this, the DataTyper for time.Time is defined as:

sw.DefineDataType(time.Time{}, SimpleDataTyper("string", "date-time"))

So whenever a time.Time value is seen, the fields {type: "string", format:"date-time"} are used.

Callers can use DefineDataType(myStruct{}, provide define their own DataTyper for structs that they. They can use SimpleDataTyper, or provide a function with dynamic logic for what fields to add:

sw.DefineDataType(FormattableString{}, func(f Field, of ObjectFields) {
  of["type"] = "string"
  if val, ok := f.StructField.Tag.Lookup("format"); ok {
    of["format"] = val
  }
})

The DataTyper above will be called for any struct field with a type of FormattableString, and use a value for the "format" field based on the struct field's tag.

The Sashay package documentation has more extensive details.

See https://swagger.io/specification/#dataTypes

func (*Sashay) SetContact

func (sa *Sashay) SetContact(name, url, email string) *Sashay

SetContact sets the contact fields in the swagger file info. See https://swagger.io/specification/#contactObject

func (*Sashay) SetLicense

func (sa *Sashay) SetLicense(name, url string) *Sashay

SetLicense sets the license fields in the swagger file info. See https://swagger.io/specification/#licenseObject

func (*Sashay) SetTermsOfService

func (sa *Sashay) SetTermsOfService(url string) *Sashay

SetTermsOfService sets the termsOfService in the swagger file info. See https://swagger.io/specification/#infoObject

func (*Sashay) WriteYAML

func (sa *Sashay) WriteYAML(buf io.Writer) error

func (*Sashay) WriteYAMLFile

func (sa *Sashay) WriteYAMLFile(filename string) error

WriteYAMLFile writes the YAML Swagger string to the file at filename. File-writing behavior works like ioutil.WriteFile.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL