openapirouter

package module
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Aug 11, 2022 License: MIT Imports: 10 Imported by: 0

README

Go CodeQL Go Report Card GoDoc

OpenAPI-Router

The OpenAPI-Router is a "Contract-First" http-Router, specifically designed for JSON-based REST-Services. It takes an OpenAPI schema as input to create the router. The router validates requests and maps them to their handler method using kin-openapi. Additionally, the router simplifies request writing and error handling for JSON-based REST-Services.

Features

  • HTTP-Router with automatic OpenAPI validation
  • Implementation http.Handler to be compatible with existing HTTP libraries
  • Automatic response writing of JSON or plain-text responses
  • ErrorMapper to write helpful responses based on the type of error

How to use

Installation
go get github.com/huk-coburg/openapirouter
Creating the router

In order to create the router, a file with the OpenAPI specification is needed. The file can be in JSON or YAML format. The router is created using the following NewRouter function with the path of the OpenAPI file.

Handler function

To enable the automatic response writing and error mapping, a custom handler function different from the standard http.HandlerFunc is used for the implementation of endpoints. The following function signature is used:

func(request *http.Request, pathParamerters map[string]string) (*openapirouter.Response, error)

The parameters are:

  • request: The pointer to the http.Request as in the standard http.HandlerFunc. It can be used to extract the request body, the headers or query parameters.
  • pathParameters: A map of the path parameters which are extracted for validation and are populated to the request, so they don't need to be extracted manually for the URL.
  • Response: A struct to depict the response to be returned. It is used to set the response body, the status and the response headers.
  • error: Standard Go error to indicate that an error occurred.

The AddRequestHandler function of the router is used to add a function for a specific path and method to the router.

If an endpoint defines a security requirement, AddRequestHandlerWithAuthFunc must be used in order to enable the router to check if the user is authorized to access the endpoint. Using the openapi3filter.NoopAuthenticationFunc as authFunc will grant access for any request without further checks.

Error handling

If the handler function returns an error, it will be mapped to a corresponding response. Therefore, a custom HTTPError is used and returned as a JSON response. It contains the following fields:

type HTTPError struct {
	// HTTP status code to return
	StatusCode int
	// URL to describe the response code
	Type       string
	// name of the response
	Title      string
	// additional details for the error, e.g. what went wrong
	Details    []string  
}

If an HTTPError is returned by the handler function, it will be mapped to a corresponding response by default. The NewError function is used to create such an error for a status code with any number of details.

It is also possible to map any other error produced by the handler function to a response. Unknown errors are mapped to an Internal Server Error by default. In order to create a different response, the error needs to be added to the routers' error mapper by using the AddErrorMapping function to define the HTTPError it should be mapped to.

Full Example
package main

import (
	"github.com/huk-coburg/openapirouter"
	"net/http"
)

// Struct for the data to be returned
type MyOutputData struct {
	Data string `json:"data"`
}

// Custom error to be mapped by error mapper
type MyCustomError struct {
}

func (e *MyCustomError) Error() string {
	return "oops"
}

// Example function which can produce an error
func GetDataForClient(client string) (*MyOutputData, error) {
	var data *MyOutputData
	var success bool
	// ...
	if success {
		return data, nil
	} else {
		return nil, &MyCustomError{}
	}
}

// HandlerFunction to assign to an endpoint
func HandleRequest(_ *http.Request, pathParams map[string]string) (*openapirouter.Response, error) {
	// Extract path parameter
	client := pathParams["client"]
	if client == "unknown" {
		// Return HttpError which is mapped automatically
		return nil, openapirouter.NewHTTPError(http.StatusForbidden, "unknown client must not receive data")
	}
	data, err := GetDataForClient(client)
	if err != nil {
		// Return custom error to be mapped
		return nil, err
	}
	// Return response to be written
	return &openapirouter.Response{
		StatusCode: http.StatusOK,
		Body:       data,
	}, nil

}

func main() {
	router, err := openapirouter.NewRouter("./test-api.yaml")
	if err != nil {
		// Could not read file
		panic(err)
	}
	// Add implementation for endpoint specified in OpenAPI specification
	// Use router.AddRequestHandlerWithAuthFunc if the endpoint defines security requirements
	router.AddRequestHandler(http.MethodGet, "/test/{client}", HandleRequest)
	// Add error mapping for MyCustomError
	router.AddErrorMapping(&MyCustomError{}, http.StatusBadGateway, "could not load data")
	// use router
	_ = http.ListenAndServe(":8080", router)
}

Contribute

Contributions are welcome. You can:

  • Submit bugs and feature requests as issues
  • Review code and let us know what we can do better
  • Submit PullRequests for code or documentation changes or additions

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type HTTPError

type HTTPError struct {
	// http status code to return
	StatusCode int
	// URL to describe the response code
	Type string `json:"type"`
	// name of the response
	Title string `json:"title"`
	// additional details for the error, e.g. what went wrong
	Details []string `json:"details"`
}

HTTPError implements error and is used to return 4xx/5xx HTTP responses. It can be converted to Response with the StatusCode of the HTTPError and HTTPError itself as the Body of the response.

func NewHTTPError

func NewHTTPError(statusCode int, details ...string) *HTTPError

NewHTTPError creates a new error with a specified statusCode and any number of details. The Title and Type of the error is set to correspond with the status code.

func (*HTTPError) Error

func (er *HTTPError) Error() string

implementation of error

func (*HTTPError) ToResponse

func (er *HTTPError) ToResponse() *Response

ToResponse converts the HTTPError to a response to be written.

type HandleRequestFunction

type HandleRequestFunction = func(*http.Request, map[string]string) (*Response, error)

HandleRequestFunction is a custom function to specify the implementation of an HTTP endpoint. It does not receive the http.ResponseWriter for the request since it is written by the requestHandler. The content of this response is specified by the returned Response or the error. If an error is returned, it is mapped to a response by the errorMapper of the Router the function was added to. The Function does receive the path parameters, because they are already extracted for validation purposes.

type Response

type Response struct {
	// http StatusCode to return
	StatusCode int
	// Body of the http request to return. If it is set to string, a plain text response is return. If it is anything
	// else, the response is returned in JSON format.
	Body interface{}
	// http Headers to add to the response
	Headers map[string]string
}

Response is a data struct that depicts the http response to be returned.

type Router

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

The Router which implements the described features. It implements http.Handler to be compatible with existing HTTP libraries.

func NewRouter

func NewRouter(swaggerPath string) (*Router, error)

NewRouter creates a new Router with the path of a OpenAPI specification file in YAML or JSON format.

func (*Router) AddErrorMapping

func (router *Router) AddErrorMapping(err error, responseCode int, details ...string)

AddErrorMapping adds a custom error that should be mapped to an error response. It uses the HTTPError to create the response. It takes an error and the response code this error should be mapped to. Additionally, any number of details can be specified.

func (*Router) AddRequestHandler

func (router *Router) AddRequestHandler(method string, path string, handleFunc HandleRequestFunction)

AddRequestHandler creates a new requestHandler for a specified method and path. It is used to set an implementation for an endpoint. The function panics, if the endpoint is not specified in the OpenAPI specification

func (*Router) AddRequestHandlerWithAuthFunc added in v0.2.0

func (router *Router) AddRequestHandlerWithAuthFunc(method string, path string, handleFunc HandleRequestFunction,
	authFunc openapi3filter.AuthenticationFunc)

AddRequestHandlerWithAuthFunc creates a new requestHandler for a specified method and path. It is used to set an implementation for an endpoint. The function panics, if the endpoint is not specified in the OpenAPI specification. In Addition to AddRequestHandler adds an openapi3filter.AuthenticationFunc which is necessary to validate a request with specified SecurityRequirements. If SecurityRequirements are specified for a resource without openapi3filter.AuthenticationFunc, the router will respond with http.StatusInternalServerError.

func (*Router) ServeHTTP

func (router *Router) ServeHTTP(writer http.ResponseWriter, request *http.Request)

Implementation of http.Handler that finds the requestHandler for an incoming request and validates the requests. It also adds the pathParameters to the requests Context so they can be extracted by the requestHandler

Jump to

Keyboard shortcuts

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