arrest

package module
v0.0.0-...-911ca8e Latest Latest
Warning

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

Go to latest
Published: Jan 28, 2025 License: MIT Imports: 16 Imported by: 1

README

AR! Rest!

The pirate-y OpenAPI 3.0 spec generator for Go. This is built on the very excellent libopenapi library from pb33f. Five out of five stars. Highly recommend. It can handle just about any kind of weird, fancy, or crazy OpenAPI doc you want to write, but it's also like programming your VCR. That's a dated references, but the retro styling of his website suggests.

Anyway, this provides a DSL for generating OpenAPI 3.0 specs in Go. That, by itself, is probably not very interesting, but it does help infer your schemas from Go functions and Go types, which can greatly simplify things. Consider the ubiquitous Petstore example, here using the ARRest DSL:

package main

import (
	"fmt"
	"reflect"

	"github.com/zostay/arrest-go"
)

type Pet struct {
	ID   int64  `json:"id"`
	Name string `json:"name"`
	Tag  string `json:"tag"`
}

type Pets []Pet

type Error struct {
	Code    int32  `json:"code"`
	Message string `json:"message"`
}

func ListPets(limit int32) (Pets, error) {
	// This is where you would put your implementation of ListPets
	return nil, nil
}

func CreatePets(pet Pet) error {
	// This is where you would put your implementation of CreatePets
	return nil
}

func ShowByPetID(petID string) (*Pet, error) {
	// This is where you would put your implementation of ShowByPetID
	return nil, nil
}

func main() {
	doc, err := arrest.NewDocument("Swagger Petstore")
	if err != nil {
		panic(err)
	}
            
    doc.AddServer("http://petstore.swagger.io/v1")

	listPets := arrest.ParametersFromReflect(reflect.TypeOf(ListPets)).
		P(0, func(p *arrest.Parameter) {
			p.Name("limit").In("query").Required()
		})

	doc.Get("/pets").
		Summary("List all pets").
		OperationID("listPets").
		Tags("pets").
		Parameters(listPets).
		Response("200", func(r *arrest.Response) {
			r.Description("A list of pets").
				Header("x-next", arrest.ModelFrom[string](), func(h *arrest.Header) {
					h.Description("A link to the next page of responses")
				}).
				Content("application/json", arrest.ModelFrom[Pets]())
		}).
		Response("default", func(r *arrest.Response) {
			r.Description("unexpected error").
				Content("application/json", arrest.ModelFrom[Error]())
		})

	doc.Post("/pets").
		Summary("Create a pet").
		OperationID("createPets").
		Tags("pets").
		RequestBody("application/json", arrest.ModelFrom[Pet]()).
		Response("201", func(r *arrest.Response) { r.Description("Null response") }).
		Response("default", func(r *arrest.Response) {
			r.Description("unexpected error").
				Content("application/json", arrest.ModelFrom[Error]())
		})

	showByPetId := arrest.ParametersFromReflect(reflect.TypeOf(ShowByPetID)).
		P(0, func(p *arrest.Parameter) {
			p.Name("petId").In("path").Required().
				Description("The ID of the pet to retrieve")
		})

	doc.Get("/pets/{petId}").
		Summary("Info for a specific pet").
		OperationID("showByPetId").
		Tags("pets").
		Parameters(showByPetId).
		Response("200", func(r *arrest.Response) {
			r.Description("Expected response to a valid request").
				Content("application/json", arrest.ModelFrom[Pet]())
		}).
		Response("default", func(r *arrest.Response) {
			r.Description("unexpected error").
				Content("application/json", arrest.ModelFrom[Error]())
		})


	if doc.Err() != nil {
		panic(doc.Err())
	}
	
	rend, err := doc.OpenAPI.Render()
	if err != nil {
		panic(err)
	}

	fmt.Print(string(rend))
}

Features

The aim is to be able to speed up building your specs while still giving you full flexibility. This tool is aimed at the high level. If it doesn't do something yet and you think it should, PRs welcome. However, in the meantime, you can just use the OpenAPI struct directly to modify it using "hard mode". In face, if you can come up with the standardized method this way first, that will make the PR all the easier.

The DSL

That's a Domain Specific Language for those who may not know. This library is based around building a sort of DSL inside of Go. It makes heavy use of method chaining to help abbreviate the code necessary to build a spec. This takes a little getting used.

The chaining is setup to be used in paragraphs or stanzas around the primary components. We consider the following pieces to be primary components:

  • Document
  • Operation

Therefore, the encouraged style is to build up the Document and then build up each Operation. Each primary component returns the component object and then all the methods of the primary only return that object.

doc := arrest.NewDocument("My API").AddServer("http://example.com")

doc.Get("/path").Summary("Get a thing") // and on

doc.Post("/path").Summary("Post a thing") // and on

Within each primary there are secondary components. To avoid breaking up your primary paragraphs, these do not return the different types for configuration but make use of callbacks or inner constructors.

doc.Get("/path").Summary("Get a thing").
    OperationID("getThing").
    Parameters(
        arrest.ParametersFromReflect(reflect.TypeOf(GetThing)).
            p.P(0, func(p *arrest.Parameter) {
                p.Name("id").In("query").Required()
            })
    ).
    Response("200", func(r *arrest.Response) {
        r.Description("The thing").
            Content("application/json", arrest.ModelFrom[Thing]())
    })

Finally, when you're finished you need to check to see if errors occurred. The system should not panic, but it records errors as it goes. You can check for them at the end, or any time in the middle by calling the Err() method on the document. You can check for errors in operations or other parts as well. Errors all flow upward to the parent component, but you can check them at any level:

if doc.Err() != nil {
    // handle the error here, of course
}

Installation

go get github.com/zostay/arrest-go

Design Note

This library is being deliberately design in such a way as to be replaced with a second version later. Some of the issues are complicated and I want a bad implementation that gets me started so I can explore issues and then work my way toward a better solution.

For example, I would like the ./gin package to perform code generation of the handlers to build REST APIs that resemble regular Go code. But to get there I need to have a good handle on how I want those handlers to look like before I can start building those templates. In the meantime, the package merely provides some helps toward integrating with the Gin framework.

Special Thanks

Thanks to pb33f for the excellent libopenapi library. This library is built on top of that one.

Also thanks to Adrian Hesketh whose github.com/a-h/rest library provided some inspiration for this library.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrUnsupportedModelType = errors.New("unsupported model type")

ErrUnsupportedModelType is returned when the model type is not supported.

View Source
var ErrUnsupportedParameterType = errors.New("unsupported parameter type")

ErrUnsupportedParameterType is returned when a parameter is created from an unsupported type.

Functions

func GoDocForStruct

func GoDocForStruct(t reflect.Type) (string, map[string]string, error)

func MappedName

func MappedName(typName string, pkgMap []PackageMap) string

Types

type Document

type Document struct {
	// OpenAPI is the underlying OpenAPI document.
	OpenAPI libopenapi.Document

	// DataModel is the v3 DataModel from the document.
	DataModel *libopenapi.DocumentModel[v3.Document]

	// PackageMap maps OpenAPI "package names" to Go package names. This is
	// used in SchemaComponentRef.
	PkgMap []PackageMap

	ErrHelper
}

Document providees DSL methods for creating OpenAPI documents.

func NewDocument

func NewDocument(title string) (*Document, error)

NewDocument creates a new Document with the given title.

func NewDocumentFrom

func NewDocumentFrom(doc libopenapi.Document) (*Document, error)

NewDocumentFrom creates a new Document from a v3.Document. This allows you to add to an existing document using the DSL.

func NewDocumentFromBytes

func NewDocumentFromBytes(bs []byte) (*Document, error)

NewDocumentFromBytes creates a new Document from raw YAML bytes.

func (*Document) AddSecurityRequirement

func (d *Document) AddSecurityRequirement(reqs map[string][]string) *Document

AddSecurityRequirement configures the global security scopes. The key in the map is the security scheme name and the value is the list of scopes.

func (*Document) AddServer

func (d *Document) AddServer(url string) *Document

AddServer adds a new server URL to the document.

func (*Document) Delete

func (d *Document) Delete(pattern string) *Operation

Delete creates a new DELETE operation at the given pattern. The Operation is returned to be manipulated further.

func (*Document) Description

func (d *Document) Description(description string) *Document

func (*Document) Get

func (d *Document) Get(pattern string) *Operation

Get creates a new GET operation at the given pattern. The Operation is returned to be manipulated further.

func (*Document) Operations

func (d *Document) Operations(ctx context.Context) []*Operation

Operations lists all the operations in the document.

func (*Document) PackageMap

func (d *Document) PackageMap(pairs ...string) *Document

func (*Document) Post

func (d *Document) Post(pattern string) *Operation

Post creates a new POST operation at the given pattern. The Operation is returned to be manipulated further.

func (*Document) Put

func (d *Document) Put(pattern string) *Operation

Put creates a new PUT operation at the given pattern. The Operation is returned to be manipulated further.

func (*Document) Refresh

func (d *Document) Refresh() error

func (*Document) SchemaComponent

func (d *Document) SchemaComponent(fqn string, m *Model) *Document

SchemaComponent adds a schema component to the document. You can then use

arrest.SchemaRef(fqn)

to reference this schema in other parts of the document.

func (*Document) SchemaComponentRef

func (d *Document) SchemaComponentRef(m *Model) *SchemaComponent

func (*Document) SchemaComponents

func (d *Document) SchemaComponents(ctx context.Context) []*SchemaComponent

SchemaComponents lists all the schema components in the document.

func (*Document) SecuritySchemeComponent

func (d *Document) SecuritySchemeComponent(fqn string, m *SecurityScheme) *Document

SecuritySchemeComponent adds a security scheme component to the document. You can then use the fqn to reference this schema in other parts of the document.

func (*Document) Title

func (d *Document) Title(title string) *Document

func (*Document) Version

func (d *Document) Version(version string) *Document

type ErrHandler

type ErrHandler interface {
	Err() error
	Errs() []error
	AddError(...error)
	AddHandler(...ErrHandler)
}

ErrHandler is the interface that all DSL object implement to allow errors to flow upward to parent components.

type ErrHelper

type ErrHelper struct {
	// contains filtered or unexported fields
}

ErrHelper implements the ErrHandler to make implementation of the interface as easy as embedding ErrHelper into the struct.

func (*ErrHelper) AddError

func (e *ErrHelper) AddError(errs ...error)

AddError adds the given errors to the current component.

func (*ErrHelper) AddHandler

func (e *ErrHelper) AddHandler(handlers ...ErrHandler)

AddHandler is used internally to add child component errors to the parent.

func (*ErrHelper) Err

func (e *ErrHelper) Err() error

Err returns an error if there are any. This uses errors.JOin to join together all of the errors in this component as well as any in any child component.

func (*ErrHelper) Errs

func (e *ErrHelper) Errs() []error

Errs returns all of the errors in this component as well as any in any child component. You probably went Err() instead.

type Header struct {
	Header *v3.Header
}

Header provides DSL methods for creating OpenAPI headers.

func (*Header) Description

func (h *Header) Description(description string) *Header

Description sets the description of the header.

type JSONTag

type JSONTag string

func (JSONTag) HasName

func (tag JSONTag) HasName() bool

func (JSONTag) IsIgnored

func (tag JSONTag) IsIgnored() bool

func (JSONTag) Name

func (tag JSONTag) Name() string

func (JSONTag) Parts

func (tag JSONTag) Parts() []string

type Model

type Model struct {
	Name        string
	SchemaProxy *base.SchemaProxy

	ErrHelper
	// contains filtered or unexported fields
}

Model provides DSL methods for creating OpenAPI schema objects based on Go types.

func ModelFrom

func ModelFrom[T any]() *Model

ModelFrom creates a new Model from a type.

func ModelFromReflect

func ModelFromReflect(t reflect.Type) *Model

ModelFromReflect creates a new Model from a reflect.Type.

func SchemaRef

func SchemaRef(fqn string) *Model

func (*Model) Description

func (m *Model) Description(description string) *Model

func (*Model) ExtractChildRefs

func (m *Model) ExtractChildRefs() map[string]*base.SchemaProxy

func (*Model) MappedName

func (m *Model) MappedName(pkgMap []PackageMap) string

type OpenAPITag

type OpenAPITag string

func (OpenAPITag) HasName

func (tag OpenAPITag) HasName() bool

func (OpenAPITag) IsIgnored

func (tag OpenAPITag) IsIgnored() bool

func (OpenAPITag) Name

func (tag OpenAPITag) Name() string

func (OpenAPITag) Parts

func (tag OpenAPITag) Parts() []string

func (OpenAPITag) Props

func (tag OpenAPITag) Props() map[string]string

type Operation

type Operation struct {
	Operation *v3.Operation

	ErrHelper
}

Operation provides DSL methods for creating OpenAPI operations.

func (*Operation) Description

func (o *Operation) Description(description string) *Operation

Description sets the description for the operation.

func (*Operation) OperationID

func (o *Operation) OperationID(id string) *Operation

OperationID sets the operation ID for the operation.

func (*Operation) Parameters

func (o *Operation) Parameters(ps *Parameters) *Operation

Parameters adds parameters to the operation.

func (*Operation) RequestBody

func (o *Operation) RequestBody(mt string, model *Model) *Operation

RequestBody sets the request body for the operation.

func (*Operation) Response

func (o *Operation) Response(code string, cb func(r *Response)) *Operation

Response adds a response to the operation.

func (*Operation) SecurityRequirement

func (o *Operation) SecurityRequirement(reqs map[string][]string) *Operation

SecurityRequirement configures the security scopes for this operation. The key in the map is the security scheme name and the value is the list of scopes.

func (*Operation) Summary

func (o *Operation) Summary(summary string) *Operation

Summary sets the summary for the operation.

func (*Operation) Tags

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

Tags adds tags to the operation.

type PackageMap

type PackageMap struct {
	OpenAPIName string
	GoName      string
}

type Parameter

type Parameter struct {
	Parameter *v3.Parameter

	ErrHelper
}

Parameter provides DSL methods for creating individual OpenAPI parameters.

func ParameterFrom

func ParameterFrom[T any]() *Parameter

ParameterFrom creates a new Parameter from a type.

func ParameterFromReflect

func ParameterFromReflect(t reflect.Type) *Parameter

ParameterFromReflect creates a new Parameter from a reflect.Type.

func (*Parameter) Description

func (p *Parameter) Description(description string) *Parameter

Description sets the description of the parameter.

func (*Parameter) In

func (p *Parameter) In(in string) *Parameter

In sets the location of the parameter. Usually "query" or "path">

func (*Parameter) Model

func (p *Parameter) Model(m *Model) *Parameter

Model sets the schema of the parameter.

func (*Parameter) Name

func (p *Parameter) Name(name string) *Parameter

Name sets the name of the parameter.

func (*Parameter) Required

func (p *Parameter) Required() *Parameter

Required marks the parameter as required.

type Parameters

type Parameters struct {
	Parameters []*Parameter

	ErrHelper
}

Parameters provides DSL methods for creating multiple OpenAPI parameters.

func NParameters

func NParameters(n int) *Parameters

NParameters creates a new Parameters with the given number of parameters.

func ParametersFrom

func ParametersFrom[T any]() *Parameters

ParametersFrom creates a new Parameters from a type.

func ParametersFromReflect

func ParametersFromReflect(t reflect.Type) *Parameters

ParametersFromReflect creates a new Parameters from a reflect.Type. Given the reflect.Type for a function, it will use the function parameters to create the base list of parameters. You will need to use the P() method to access the parameters and set names in that case.

func (*Parameters) P

func (p *Parameters) P(idx int, cb func(p *Parameter)) *Parameters

P returns the parameter at the given index and calls the callback with it.

type Response

type Response struct {
	Response *v3.Response

	ErrHelper
}

Response provides DSL methods for creating OpenAPI responses.

func (*Response) Content

func (r *Response) Content(code string, m *Model) *Response

Content adds a content type to the response.

func (*Response) Description

func (r *Response) Description(description string) *Response

Description sets the description of the response.

func (*Response) Header

func (r *Response) Header(name string, m *Model, mods ...func(h *Header)) *Response

Header adds a header to the response.

type SchemaComponent

type SchemaComponent struct {
	// contains filtered or unexported fields
}

func NewSchemaComponent

func NewSchemaComponent(schema *Model, ref *Model) *SchemaComponent

func (*SchemaComponent) Description

func (s *SchemaComponent) Description(description string) *SchemaComponent

func (*SchemaComponent) Ref

func (s *SchemaComponent) Ref() *Model

func (*SchemaComponent) Schema

func (s *SchemaComponent) Schema() *Model

type SecurityScheme

type SecurityScheme struct {
	SecurityScheme *highv3.SecurityScheme
}

func SecuritySchemeAPIAuthKey

func SecuritySchemeAPIAuthKey(in string, name string) *SecurityScheme

func SecuritySchemeBasicAuth

func SecuritySchemeBasicAuth() *SecurityScheme

func SecuritySchemeBearerAuth

func SecuritySchemeBearerAuth() *SecurityScheme

func SecuritySchemeBearerAuthWithFormat

func SecuritySchemeBearerAuthWithFormat(format string) *SecurityScheme

func SecuritySchemeCookieAuth

func SecuritySchemeCookieAuth(name string) *SecurityScheme

func SecuritySchemeForType

func SecuritySchemeForType(typ string) *SecurityScheme

func SecuritySchemeOAuth2AuthorizationCode

func SecuritySchemeOAuth2AuthorizationCode(
	authorizationURL string,
	tokenURL string,
	scopes map[string]string,
) *SecurityScheme

func SecuritySchemeOAuth2ClientCredentials

func SecuritySchemeOAuth2ClientCredentials(
	tokenURL string,
	scopes map[string]string,
) *SecurityScheme

func SecuritySchemeOAuth2Implicit

func SecuritySchemeOAuth2Implicit(
	authorizationURL string,
	scopes map[string]string,
) *SecurityScheme

func SecuritySchemeOAuth2Password

func SecuritySchemeOAuth2Password(
	tokenURL string,
	scopes map[string]string,
) *SecurityScheme

func (*SecurityScheme) AllDefinedFlows

func (s *SecurityScheme) AllDefinedFlows() *regardingFlow

func (*SecurityScheme) AllFlows

func (s *SecurityScheme) AllFlows() *regardingFlow

func (*SecurityScheme) AuthorizationCodeFlow

func (s *SecurityScheme) AuthorizationCodeFlow() *regardingFlow

func (*SecurityScheme) BearerFormat

func (s *SecurityScheme) BearerFormat(format string) *SecurityScheme

func (*SecurityScheme) ClientCredentialsFlow

func (s *SecurityScheme) ClientCredentialsFlow() *regardingFlow

func (*SecurityScheme) Description

func (s *SecurityScheme) Description(description string) *SecurityScheme

func (*SecurityScheme) ImplicitFlow

func (s *SecurityScheme) ImplicitFlow() *regardingFlow

func (*SecurityScheme) In

func (*SecurityScheme) Name

func (s *SecurityScheme) Name(name string) *SecurityScheme

func (*SecurityScheme) PasswordFlow

func (s *SecurityScheme) PasswordFlow() *regardingFlow

func (*SecurityScheme) Scheme

func (s *SecurityScheme) Scheme(scheme string) *SecurityScheme

type TagInfo

type TagInfo struct {
	// contains filtered or unexported fields
}

func NewTagInfo

func NewTagInfo(tag reflect.StructTag) *TagInfo

func (*TagInfo) ElemRefName

func (info *TagInfo) ElemRefName() string

func (*TagInfo) HasIn

func (info *TagInfo) HasIn() bool

func (*TagInfo) HasName

func (info *TagInfo) HasName() bool

func (*TagInfo) In

func (into *TagInfo) In() string

func (*TagInfo) IsIgnored

func (info *TagInfo) IsIgnored() bool

func (*TagInfo) Name

func (info *TagInfo) Name() string

func (*TagInfo) Props

func (info *TagInfo) Props() map[string]string

func (*TagInfo) RefName

func (info *TagInfo) RefName() string

func (*TagInfo) ReplacementType

func (info *TagInfo) ReplacementType() string

Directories

Path Synopsis
gin module

Jump to

Keyboard shortcuts

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