openapi

package module
v0.0.0-...-42acbb5 Latest Latest
Warning

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

Go to latest
Published: Jan 6, 2020 License: MIT Imports: 10 Imported by: 0

README

go-openapi

Shabby yet useful OpenAPI 3.0 support for Go

Install

go get -u github.com/tangyanhan/go-openapi

Quick Start

Organize your API routes like a tree:

	o, err := New("3.0.0", sampleInfo)
	if err != nil {
		log.Fatal(err)
	}
	r := NewRouter(o)
	r.Route("/{namespace}/books", func(r Router) {
    r.WithPathParam("namespace", "Namespace of the bookstore")
		r.GET("/", "List books", "List books").
			Returns(200, "Book content", "bookArray", []*Book{}).
			Returns(404, "Book not found", "replyError", &ReplyError{
				Code:    "book_not_found",
				Message: "The request book is not found",
			}).ReturnDefault("internal errors", "replyError", &ReplyError{
			Code:    "internal_error",
			Message: "an unknown error occurred in our end",
		})
		r.POST("/", "Add new book", "Add a new book").
			ReadJSON("JSON of book info", true, "book", &Book{}).
			Returns(200, "Book content", "book", &Book{}).
			Returns(404, "Book not found", "replyError", &ReplyError{
				Code:    "book_not_found",
				Message: "The request book is not found",
			})
	})
  apiYAML, _ := o.YAML()

You don't have to write all arguments again and again, simply put common parameters in upstream branch.

The schemas of data to be read/written will be collected into API document automatically.

Interface Schema

The schema of a given interface can be generated either automatically or via implementating SchemaDoc interface.

Automatic Schema For Interface

Data can be provided either in struct or pointer. The package will collected all it's info and generate a schema.

During this process, field tags will be used to generate propertity schemas::

json tags will be used as the propertity name.

doc tags will be used to generate these values for schema: enum, description, format and pattern.

   {
     kind string `json:"kind" doc="enum=a|b|c,description=blablabla"`
     age int `json:"age" doc="pattern=^abc$"`
   }

validate tags will be used to generate values for schema: minimum, maximum, min, max, required. If required,min,max is set, corresponding values will be set.

Native Schema Generation

A type may implement openapi.SchemaDoc to provide a schema manually. If the implementation is found, the schema it returns will be used, instead of automatic schema generation.

// ReplyError error reply
type ReplyError struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

func (r ReplyError) SchemaDoc() *Schema {
	return &Schema{
		Type: "object",
		Properties: map[string]*Schema{
			"code": &Schema{
				Type:        "integer",
				Description: "Error code of current string, read by program",
			},
			"message": &Schema{
				Type:        "string",
				Description: "Human friendly message that may help with the problem",
			},
		},
	}
}

Known Issues

  • The final document is not likely to be in common order.
  • The schema for a interface will always be put into #/components/schemas
  • Parameters/Responses are not reused with ref in the document generated automatically
  • If a type contain nested type, such like a map with struct as values, the struct schema is not likely to be in the ref style, it will be nested in the definition

This package has not been fully tested and covered. I will keep on updating it on my own needs in production.

Documentation

Overview

Package openapi provide OpenAPI 3.0 support for Go In this package, there are many util functions that looks chained, but some are not. If a method starts with "Add", it will return the instance that has just been added. Otherwise, methods start with "With" returns the instance itself.

Package openapi provide OpenAPI 3.0 support for Go

Index

Examples

Constants

View Source
const (
	MimeJSON = "application/json"
	MimeYAML = "application/yaml"
)

Supported mime types when using shortcuts

Variables

View Source
var (
	ErrNoRoot           = errors.New("root not initialized with NewRoot")
	ErrNoOneOf          = errors.New("oneOf has no alternatives")
	ErrInvalidSchemaDoc = errors.New("invalid SchemaDoc method given")
)

Errors that may be returned or raised

View Source
var OperationGenerator = DefaultOperationID

OperationGenerator provide operationId when no operation id given

Functions

func DefaultOperationID

func DefaultOperationID(method, path string) string

DefaultOperationID provide default operation id generator

Types

type APIExample

type APIExample interface {
	Example()
}

APIExample provider

type Components

type Components struct {
	Schemas       schemaMap        `json:"schemas,omitempty"`
	Responses     respMap          `json:"responses,omitempty"`
	Parameters    paramMap         `json:"parameters,omitempty"`
	Examples      exampleMap       `json:"examples,omitempty"`
	RequestBodies reqBodyMap       `json:"requestBodies,omitempty"`
	Headers       paramMap         `json:"-"`
	Links         map[string]*Link `json:"links,omitempty"`
}

Components object

type Contact

type Contact struct {
	Name  string `json:"name,omitempty"`
	URL   string `json:"url,omitempty"`
	Email string `json:"email,omitempty"`
}

Contact info

type Example

type Example struct {
	Summary     string      `json:"summary,omitempty"`
	Description string      `json:"description,omitempty"`
	Value       interface{} `json:"value"`
}

Example ExampleObj

type Info

type Info struct {
	Title          string   `json:"title" validate:"required"`
	Version        string   `json:"version" validate:"required"`
	TermsOfService string   `json:"termsOfService,omitempty"`
	Contact        *Contact `json:"contact,omitempty"`
	License        *License `json:"license,omitempty"`
}

Info of global document

type License

type License struct {
	Name string `json:"name" validate:"required"`
	URL  string `json:"url,omitempty"`
}

License info

type Link struct {
	OperationRef string            `json:"operationRef,omitempty"`
	OperationID  string            `json:"operationId,omitempty"`
	Parameters   map[string]*Param `json:"parameters,omitempty"`
	RequestBody  interface{}       `json:"requestBody,omitempty"`
	Description  string            `json:"description,omitempty"`
}

Link to a resuable object

type MediaType

type MediaType struct {
	Schema   *Schema             `json:"schema,omitempty"`
	Example  interface{}         `json:"example,omitempty"`
	Examples map[string]*Example `json:"examples,omitempty"`
}

MediaType media type object

type OpenAPI

type OpenAPI struct {
	OpenAPI    string      `json:"openapi"`
	Info       Info        `json:"info"`
	Servers    []Server    `json:"servers,omitempty"`
	Paths      pathMap     `json:"paths"`
	Components *Components `json:"components,omitempty"`
}

OpenAPI document structure

func New

func New(version string, info Info) (*OpenAPI, error)

New create OpenAPI document object and set it as global document root

Example
o, err := New("3.0.0", sampleInfo)
if err != nil {
	log.Fatal(err)
}
r := NewRouter(o)
r.Route("/books", func(r Router) {
	r.GET("/", "List books", "List books").
		Returns(200, "Book content", "bookArray", []*Book{}).
		Returns(404, "Book not found", "replyError", &ReplyError{
			Code:    "book_not_found",
			Message: "The request book is not found",
		}).ReturnDefault("internal errors", "replyError", &ReplyError{
		Code:    "internal_error",
		Message: "an unknown error occurred in our end",
	})
	r.POST("/", "Add new book", "Add a new book").
		ReadJSON("JSON of book info", true, "book", &Book{}).
		Returns(200, "Book content", "book", &Book{}).
		Returns(404, "Book not found", "replyError", &ReplyError{
			Code:    "book_not_found",
			Message: "The request book is not found",
		})
	r.Route("/{id}", func(r Router) {
		r.WithPathParam("id", "ID of the book")
		r.GET("", "Get single book", "Info of a book").
			Returns(200, "Book content", "book", &Book{}).
			Returns(404, "Book not found", "replyError", &ReplyError{
				Code:    "book_not_found",
				Message: "The request book is not found",
			})
	})
})

raw, err := o.JSON()
if err != nil {
	log.Fatal(err)
}
fmt.Print(string(raw))
Output:

{"openapi":"3.0.0","info":{"title":"testing","version":"v1.0","termsOfService":"abcd","contact":{"name":"Ethan Tang","url":"example.com","email":"someone@example.com"},"license":{"name":"MIT License","url":"http://example.com/mit"}},"paths":{"/books":{"description":"","get":{"summary":"List books","description":"List books","responses":{"200":{"description":"Book content","content":{"application/json":{"schema":{"$ref":"#/components/schemas/bookArray"},"example":[]}}},"404":{"description":"Book not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/replyError"},"example":{"code":"book_not_found","message":"The request book is not found"}}}},"default":{"description":"internal errors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/replyError"},"example":{"code":"internal_error","message":"an unknown error occurred in our end"}}}}}},"post":{"summary":"Add new book","description":"Add a new book","requestBody":{"description":"JSON of book info","content":{"application/json":{"schema":{"$ref":"#/components/schemas/book"},"example":{"name":"","author":"","date":""}}},"required":true},"responses":{"200":{"description":"Book content","content":{"application/json":{"schema":{"$ref":"#/components/schemas/book"},"example":{"name":"","author":"","date":""}}}},"404":{"description":"Book not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/replyError"},"example":{"code":"book_not_found","message":"The request book is not found"}}}}}},"summary":""},"/books/{id}":{"description":"","get":{"summary":"Get single book","description":"Info of a book","responses":{"200":{"description":"Book content","content":{"application/json":{"schema":{"$ref":"#/components/schemas/book"},"example":{"name":"","author":"","date":""}}}},"404":{"description":"Book not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/replyError"},"example":{"code":"book_not_found","message":"The request book is not found"}}}}}},"parameters":[{"name":"id","in":"path","description":"ID of the book","required":true,"schema":{"type":"string"}}],"summary":""}},"components":{"schemas":{"book":{"type":"object","properties":{"author":{"type":"string","maxLength":128,"minLength":1},"date":{"type":"string","format":"date"},"name":{"type":"string","maxLength":128,"minLength":1}},"required":["name","author"]},"bookArray":{"type":"array","items":{"type":"object","properties":{"author":{"type":"string","maxLength":128,"minLength":1},"date":{"type":"string","format":"date"},"name":{"type":"string","maxLength":128,"minLength":1}},"required":["name","author"]}},"replyError":{"type":"object","properties":{"code":{"type":"integer","description":"Error code of current string, read by program"},"message":{"type":"string","description":"Human friendly message that may help with the problem"}}}}}}

func (*OpenAPI) AddHeader

func (o *OpenAPI) AddHeader(key string, param *Param) string

AddHeader add param definition to global components

func (*OpenAPI) AddParam

func (o *OpenAPI) AddParam(key string, param *Param) string

AddParam add param definition to global components

func (*OpenAPI) AddPath

func (o *OpenAPI) AddPath(path, summary, description string) *Path

AddPath to OpenAPI paths section

func (*OpenAPI) AddSchema

func (o *OpenAPI) AddSchema(key string, schema *Schema) *Schema

AddSchema add schema to global components, and return a ref

func (*OpenAPI) GetHeader

func (o *OpenAPI) GetHeader(key string) *Param

GetHeader return param

func (*OpenAPI) GetParam

func (o *OpenAPI) GetParam(key string) *Param

GetParam return param

func (*OpenAPI) GetSchema

func (o *OpenAPI) GetSchema(key string) *Schema

GetSchema return schema ref if exists

func (OpenAPI) JSON

func (o OpenAPI) JSON() ([]byte, error)

JSON marshal document as JSON

func (*OpenAPI) MustGetSchema

func (o *OpenAPI) MustGetSchema(key string, v interface{}) *Schema

MustGetSchema create schema for struct when necessary and always return a ref format

func (*OpenAPI) YAML

func (o *OpenAPI) YAML() ([]byte, error)

YAML convert document to yaml. We only have json tags in structs, so special lib must be used

type Operation

type Operation struct {
	Tags        []string     `json:"tags,omitempty"`
	Summary     string       `json:"summary,omitempty"`
	Description string       `json:"description,omitempty"`
	OperationID string       `json:"operationId,omitempty"`
	Parameters  []*Param     `json:"parameters,omitempty"`
	RequestBody *RequestBody `json:"requestBody,omitempty"`
	Responses   Responses    `json:"responses" validate:"required"`
	Deprecated  bool         `json:"deprecated,omitempty"`
	// contains filtered or unexported fields
}

Operation OperationObject

func (*Operation) AddParam

func (o *Operation) AddParam(in ParamType, name, description string) *Param

AddParam with param in operation

func (*Operation) Metadata

func (o *Operation) Metadata(operationID, summary, description string) *Operation

Metadata add metadata to operation

func (*Operation) Read

func (o *Operation) Read(description string, required bool, mimeType string, example interface{}) *Operation

Read read raw body of any kind

func (*Operation) ReadJSON

func (o *Operation) ReadJSON(description string, required bool, key string, v interface{}) *Operation

ReadJSON read object json from request body

func (*Operation) ReturnDefault

func (o *Operation) ReturnDefault(description string, key string, v interface{}) *Operation

ReturnDefault add default response. A default response is the response to be used when none of defined codes match the situation.

func (*Operation) Returns

func (o *Operation) Returns(code int, description string, key string, v interface{}) *Operation

Returns with code

func (*Operation) ReturnsNonJSON

func (o *Operation) ReturnsNonJSON(code int, description string,
	mimeType string, headers map[string]*Param, schema *Schema, example interface{}) *Operation

ReturnsNonJSON return something not json

func (*Operation) Root

func (o *Operation) Root() *OpenAPI

Root returns document root for operation

func (*Operation) WithParam

func (o *Operation) WithParam(param *Param) *Operation

WithParam add param to operation

func (*Operation) WithPathParam

func (o *Operation) WithPathParam(name, description string) *Operation

WithPathParam add path param

func (*Operation) WithQueryParam

func (o *Operation) WithQueryParam(name, description string, example interface{}) *Operation

WithQueryParam add query param. Complex types of query param is not supported here(e.g., a struct or slice)

func (*Operation) WithTags

func (o *Operation) WithTags(tags ...string) *Operation

WithTags add tags

type Param

type Param struct {

	// Fixed fields
	Name            string    `json:"name" validate:"required"`
	In              ParamType `json:"in" validate:"required,oneof=query header path cookie"`
	Description     string    `json:"description,omitempty"`
	Required        bool      `json:"required"`
	Deprecated      bool      `json:"deprecated,omitempty"`
	AllowEmptyValue bool      `json:"allowEmptyValue,omitempty"`
	// Below are optional fields
	Schema   *Schema             `json:"schema,omitempty"`
	Example  interface{}         `json:"example,omitempty"`
	Examples map[string]*Example `json:"examples,omitempty"`
	// contains filtered or unexported fields
}

Param ParameterObject

func NewPathParam

func NewPathParam(name, description string) *Param

NewPathParam create new path param

func NewQueryParam

func NewQueryParam(name, description string, example interface{}) *Param

NewQueryParam create new query param

func (*Param) AllowEmpty

func (p *Param) AllowEmpty() *Param

AllowEmpty make param allow empty

func (*Param) SetDeprecated

func (p *Param) SetDeprecated() *Param

SetDeprecated make param as deprecated

func (*Param) SetRequired

func (p *Param) SetRequired() *Param

SetRequired make param as required

func (*Param) WithSchema

func (p *Param) WithSchema(s *Schema) *Param

WithSchema add schema

func (*Param) WithStruct

func (p *Param) WithStruct(v interface{}) *Param

WithStruct add struct schema for param

type ParamType

type ParamType string

ParamType param "in" request

const (
	PathParam   ParamType = "path"
	QueryParam  ParamType = "query"
	HeaderParam ParamType = "header"
	CookieParam ParamType = "cookie"
)

Valid param types

func (ParamType) IsValid

func (p ParamType) IsValid() bool

IsValid param

type Path

type Path struct {
	Summary     string `json:"summary"`
	Description string `json:"description"`

	Parameters []*Param `json:"parameters,omitempty"`
	// contains filtered or unexported fields
}

Path definition

func (*Path) AddOperation

func (p *Path) AddOperation(method string) *Operation

AddOperation add operation to path

func (Path) MarshalJSON

func (p Path) MarshalJSON() ([]byte, error)

MarshalJSON marshal path

func (*Path) Root

func (p *Path) Root() *OpenAPI

Root document of whole application

type RequestBody

type RequestBody struct {
	Description string `json:"description,omitempty"`
	// MIME-Type -> MediaTypeObject
	Content  mediaTypeMap `json:"content" validate:"required"`
	Required bool         `json:"required,omitempty"`
}

RequestBody request body object

type Response

type Response struct {
	Description string       `json:"description"`
	Headers     paramMap     `json:"headers,omitempty"`
	Content     mediaTypeMap `json:"content,omitempty"`
}

Response response object

type Responses

type Responses map[string]*Response

Responses is actually a map

type Router

type Router interface {
	Root() *OpenAPI
	WithParam(param *Param) Router
	WithPathParam(name, description string) Router
	WithTags(tags ...string) Router
	Route(path string, fn func(r Router)) Router
	// HTTP methods
	GET(path, summary, description string) *Operation
	PUT(path, summary, description string) *Operation
	POST(path, summary, description string) *Operation
	DELETE(path, summary, description string) *Operation
	HEAD(path, summary, description string) *Operation
	PATCH(path, summary, description string) *Operation
}

Router is a extract of api path. router grouped in multiple levels can share parameters(path/query/header/etc)

func NewRouter

func NewRouter(root *OpenAPI) Router

NewRouter create a new router

type Schema

type Schema struct {
	Ref                  string             `json:"-"`
	Type                 string             `json:"type,omitempty"`
	Format               string             `json:"format,omitempty" validate:"oneof=int32 int64 float double byte binary date date-time password"`
	AllOf                []*Schema          `json:"allOf,omitempty"`
	OneOf                []*Schema          `json:"oneOf,omitempty"`
	AnyOf                []*Schema          `json:"anyOf,omitempty"`
	Not                  *Schema            `json:"not,omitempty"`
	Items                *Schema            `json:"items,omitempty"`
	Properties           map[string]*Schema `json:"properties,omitempty"`
	AdditionalProperties *Schema            `json:"additionalProperties,omitempty"`
	Description          string             `json:"description,omitempty"`
	Default              interface{}        `json:"default,omitempty"`
	Maximum              interface{}        `json:"maximum,omitempty"`
	Minimum              interface{}        `json:"minimum,omitempty"`
	MaxLength            *int64             `json:"maxLength,omitempty"`
	MinLength            *int64             `json:"minLength,omitempty"`
	Pattern              string             `json:"pattern,omitempty"`
	Required             *SchemaRequired    `json:"required,omitempty"`
	Enum                 []string           `json:"enum,omitempty"`
	// contains filtered or unexported fields
}

Schema SchemaObject

func Interface

func Interface(v interface{}) (schema *Schema, err error)

Interface parse interface as schema

func NewSchema

func NewSchema(schemaType string) *Schema

NewSchema create new schema

func (Schema) MarshalJSON

func (s Schema) MarshalJSON() ([]byte, error)

MarshalJSON turns

func (*Schema) SetRoot

func (s *Schema) SetRoot(root *OpenAPI)

SetRoot recursively set root for schema and all its related schemas

func (*Schema) WithAnyOf

func (s *Schema) WithAnyOf(args ...interface{}) *Schema

WithAnyOf set as any of

func (*Schema) WithBasicProperty

func (s *Schema) WithBasicProperty(name, propType, description string, required bool) *Schema

WithBasicProperty add a basic propertity

func (*Schema) WithDescription

func (s *Schema) WithDescription(description string) *Schema

WithDescription add description

func (*Schema) WithItems

func (s *Schema) WithItems(schema *Schema) *Schema

WithItems set array items

func (*Schema) WithOneOf

func (s *Schema) WithOneOf(keyAndValues ...interface{}) *Schema

WithOneOf set as one of.

func (*Schema) WithProperty

func (s *Schema) WithProperty(name string, required bool, prop *Schema) *Schema

WithProperty add to a schema

func (*Schema) WithRequired

func (s *Schema) WithRequired(required bool) *Schema

WithRequired make a schema to required: true or false

type SchemaDoc

type SchemaDoc interface {
	SchemaDoc() *Schema
}

SchemaDoc provide schema for a struct

type SchemaRequired

type SchemaRequired struct {
	Required   bool
	Properties []string
}

SchemaRequired is a special representation for schema field "required", which can be either boolean or an array of propertity names

func (SchemaRequired) MarshalJSON

func (s SchemaRequired) MarshalJSON() ([]byte, error)

MarshalJSON for special required field

type Server

type Server struct {
	URL         string                    `json:"url" validate:"required"`
	Description string                    `json:"description,omitempty"`
	Variables   map[string]ServerVariable `json:"variables,omitempty"`
}

Server server object

type ServerVariable

type ServerVariable struct {
	Enum        []string `json:"enum,omitempty"`
	Description string   `json:"description,omitempty"`
	Default     string   `json:"default" validate:"required"`
}

ServerVariable is used to replace some things in url schema

Jump to

Keyboard shortcuts

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