nvelope

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Dec 20, 2022 License: MIT Imports: 18 Imported by: 3

README

nvelope - http endpoint helpers in an nject world

GoDoc unit tests report card codecov

Install:

go get github.com/muir/nvelope

Prior to nject version 0.2.0, this was part of that repo. As of release 0.2.0, GenerateDecoder no longer depends upon gorilla/mux. Some additional effort is now required to extract data from path/route variables and the pre-defined decoders DecodeJSON and DecodeXML have been removed.


Nvelope provides pre-defined handlers for basic endpoint tasks. When used in combination with npoint or nape, all that's left is the business logic. It is based upon nject.

An Example
type ExampleRequestBundle struct {
	Request     PostBodyModel `nvelope:"model"`
	With        string        `nvelope:"path,name=with"`
	Parameters  int64         `nvelope:"path,name=parameters"`
	Friends     []int         `nvelope:"query,name=friends"`
	ContentType string        `nvelope:"header,name=Content-Type"`
}

func Service(router *mux.Router) {
	service := nape.RegisterServiceWithMux("example", router)
	service.RegisterEndpoint("/some/path",
		nvelope.LoggerFromStd(log.Default()),
		nvelope.InjectWriter,
		nvelope.EncodeJSON,
		nvelope.CatchPanic,
		nvelope.Nil204,
		nvelope.ReadBody,
		DecodeJSON,
		func (req ExampleRequestBundle) (nvelope.Response, error) {
			....
		},
	).Methods("POST")
}

Typical chain

A typical endpoint wrapping chan contains some or all of the following.

Create a logger

This is an option step that is recommended if you're using request-specific loggers. The encoding provider can uses a logger that implements the nvelope.BasicLogger interface. nvelope.LoggerFromStd can create an nvelope.BasicLogger from the "log" logger. nvelope.NoLogger provides a nvelope.BasicLogger that does nothing.

Deferred Writer

Use nvelope.InjectWriter to create a *DeferredWriter. A *DeferredWriter is a useful enchancement to http.ResponseWriter that allows the output to be reset and allows headers to be set at any time. The cost of a *DeferredWriter is that the output is buffered and copied.

Marshal response

We need the request encoder this early in the framework so that it can marshal error responses.

A JSON marshaller is provided: nvelope.EncodeJSON. Other response encoders can be created with nvelope.MakeResponseEncoder.

Catch panics

Have the endpoint return a 500 when there is a panic.
nvelope.SetErrorOnPanic() is a function that can be deferred to notice a panic and create a useful error. In an injection chain, use nvelope.CatchPanic.

Return 204 for nil responses

Use an extra injector to trigger a 204 response for nil content instead of having the encoder handle nil specially. nvelope.Nil204 does this.

Grab the request body

The request body is more convieniently handled as a []byte . This is also one place where API enforcement can be done. The type nvelope.Body is provided by nvelope.ReadBody via injection to any provider that wants it.

Validate response

This is a user-provided optional step that can be used to double-check that what is being sent matches the API defintion.

The nvalid package provides a function to generate a response validator from Swagger.

Decode the request body

The request body needs to be unpacked with an unmarshaller of some kind. nvelope.GenerateDecoder creates decoders that examine the injection chain looking for models that are consumed but not provided. If it finds any, it examines those models for struct tags that indicate that nvelope should create and fill the model.

If so, it generates a provider that fills the model from the request. This includes filling fields for the main decoded request body and also includes filling fields from URL path elements, URL query parameters, and HTTP headers.

nvelope.DecodeJSON and nvelope.DecodeXML are pre-defined for convience.

Validate the request

This is an optional step, provided by the user of nvelope, that should return nject.TerminalError if the request is not valid. Other validation can happen later, but this is good place to enforce API compliance. The nvalid package provides a function to generate an input validator from Swagger.

Actually handle the request

At this point the request model has been decoded. The other input parameters (from headers, path, and query parameters) have been decoded. The input model may have been validated.

The response will automatically be encoded. The endpoint handler returns the response and and error. If there is an error, it will trigger an appropriate return. Use nvelope.ReturnCode to set the return code if returning an error.

Documentation

Overview

Package nvelope provides injection handlers that make building HTTP endpoints simple. In combination with npoint and nject it provides a API endpoint framework.

The main things it provides are a request decoder and a response encoder.

The request decoder will fill in a struct to capture all the parts of the request: path parameters, query parameters, headers, and the body. The decoding is driven by struct tags that are interpreted at program startup.

The response encoder is comparatively simpler: given a model and an error, it encodes the error or the model appropriately.

Deferred writer allows output to be buffered and then abandoned.

NotFound, Forbidden, and BadRequest provide easy ways to annotate an error return to cause a specific HTTP error code to be sent.

CatchPanic makes it easy to turn panics into error returns.

The provided example puts it all together.

Example

Example shows an injection chain handling a single endpoint using nject, nape, and nvelope.

package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/muir/nape"
	"github.com/muir/nvelope"

	"github.com/gorilla/mux"
)

// nolint:deadcode,unused
func Main() {
	r := mux.NewRouter()
	srv := &http.Server{
		Addr:    "0.0.0.0:8080",
		Handler: r,
	}
	Service(r)
	log.Fatal(srv.ListenAndServe())
}

type PostBodyModel struct {
	Use      string `json:"use"`
	Exported string `json:"exported"`
	Names    string `json:"names"`
}

type ExampleRequestBundle struct {
	Request     PostBodyModel `nvelope:"model"`
	With        *string       `nvelope:"path,name=with"`
	Parameters  int64         `nvelope:"path,name=parameters"`
	Friends     []int         `nvelope:"query,name=friends"`
	ContentType string        `nvelope:"header,name=Content-Type"`
}

type ExampleResponse struct {
	Stuff string `json:"stuff,omitempty"`
	Here  string `json:"here,omitempty"`
}

func HandleExampleEndpoint(req ExampleRequestBundle) (nvelope.Response, error) {
	if req.ContentType != "application/json" {
		return nil, errors.New("content type must be application/json")
	}
	switch req.Parameters {
	case 666:
		panic("something is not right")
	case 100:
		return nil, nil
	default:
		return ExampleResponse{
			Stuff: *req.With,
		}, nil
	}
}

func Service(router *mux.Router) {
	service := nape.RegisterServiceWithMux("example", router)
	service.RegisterEndpoint("/a/path/{with}/{parameters}",
		// order matters and this is a correct order
		nvelope.NoLogger,
		nvelope.InjectWriter,
		nvelope.EncodeJSON,
		nvelope.CatchPanic,
		nvelope.Nil204,
		nvelope.ReadBody,
		nape.DecodeJSON,
		HandleExampleEndpoint,
	).Methods("POST")
}

// Example shows an injection chain handling a single endpoint using nject,
// nape, and nvelope.
func main() {
	r := mux.NewRouter()
	Service(r)
	ts := httptest.NewServer(r)
	client := ts.Client()
	doPost := func(url string, body string) {
		// nolint:noctx
		res, err := client.Post(ts.URL+url, "application/json",
			strings.NewReader(body))
		if err != nil {
			fmt.Println("response error:", err)
			return
		}
		b, err := ioutil.ReadAll(res.Body)
		if err != nil {
			fmt.Println("read error:", err)
			return
		}
		res.Body.Close()
		fmt.Println(res.StatusCode, "->"+string(b))
	}
	doPost("/a/path/joe/37", `{"Use":"yeah","Exported":"uh hu"}`)
	doPost("/a/path/joe/100", `{"Use":"yeah","Exported":"uh hu"}`)
	doPost("/a/path/joe/38", `invalid json`)
	doPost("/a/path/joe/666", `{"Use":"yeah","Exported":"uh hu"}`)

}
Output:

200 ->{"stuff":"joe"}
204 ->
400 ->nvelope_test.ExampleRequestBundle model: Could not decode application/json into nvelope_test.PostBodyModel: invalid character 'i' looking for beginning of value
500 ->panic: something is not right

Index

Examples

Constants

This section is empty.

Variables

View Source
var AutoFlushWriter = nject.Provide("autoflush-writer", func(inner func(), w *DeferredWriter) {
	inner()
	_ = w.FlushIfNotFlushed()
})

AutoFlushWriter calls Flush on the deferred writer if it hasn't already been done

View Source
var CatchPanic = nject.Provide("catch-panic", catchPanicInjector)

CatchPanic is a wrapper that catches downstream panics and returns an error a downsteam provider panic's.

View Source
var DebugIncludeExclude = nject.Required(nject.Provide("debug-include/exclude",
	func(log BasicLogger, d *nject.Debugging) {
		log.Debug(strings.Join(d.IncludeExclude, "\n"))
	}))

DebugIncludeExclude is a tiny wrapper around nject.Debugging. It logs the IncludeExclude strings.

View Source
var EncodeJSON = MakeResponseEncoder("JSON",
	WithEncoder("application/json", json.Marshal,
		WithEncoderErrorTransform(func(err error) (interface{}, bool) {
			var jm json.Marshaler
			if errors.As(err, &jm) {
				return jm, true
			}
			return nil, false
		}),
	))

EncodeJSON is a JSON encoder manufactured by MakeResponseEncoder with default options.

View Source
var EncodeXML = MakeResponseEncoder("XML",
	WithEncoder("application/xml", xml.Marshal,
		WithEncoderErrorTransform(func(err error) (interface{}, bool) {
			var me xml.Marshaler
			if errors.As(err, &me) {
				return me, true
			}
			return nil, false
		}),
	))

EncodeXML is a XML encoder manufactured by MakeResponseEncoder with default options.

View Source
var InjectWriter = nject.Provide("writer", NewDeferredWriter)

InjectWriter injects a DeferredWriter

View Source
var MinimalErrorHandler = nject.Provide("minimal-error-handler", minimalErrorHandler)

MinimalErrorHandler provides a way to catch returned error values from the many functions that return them if MakeResponseEncoder is not used. http.ResponseWriter is used instead of a DeferredWriter. That means that MinimalErrorHandler cannot know if a response has already been made. The assumption is that if the returned error is nil, a respons has been made and if the returned error is not nil, then a response has not yet been made and the MinimalErrorHandler should make one. GetReturnCode is used to determine the return code.

View Source
var Nil204 = nject.Desired(nject.Provide("nil-204", nil204))

Nil204 is a wrapper that causes looks for return values of Response and error and if both are nil, writes a 204 header and no data. It is mean to be used downstream from a response encocder.

View Source
var ReadBody = nject.Provide("read-body", readBody)

ReadBody is a provider that reads the input body from an http.Request and provides it in the Body type.

Functions

func BadRequest

func BadRequest(err error) error

BadRequest annotates an error has giving 400 HTTP return code

func Forbidden

func Forbidden(err error) error

Forbidden annotates an error has giving 403 HTTP return code

func GenerateDecoder

func GenerateDecoder(
	genOpts ...DecodeInputsGeneratorOpt,
) interface{}

GenerateDecoder injects a special provider that uses nject.GenerateFromInjectionChain to examine the injection chain to see if there are any models that are used but never provided. If so, it looks at the struct tags in the models to see if they are tagged for filling with the decoder. If so, a provider is created that injects the missing model into the dependency chain. The intended use for this is to have an endpoint handler receive the deocded request body.

Major warning: the endpoint handler must receive the request model as a field inside a model, not as a standalone model.

The following tags are recognized:

`nvelope:"model"` causes the POST or PUT body to be decoded using a decoder like json.Unmarshal.

`nvelope:"path,name=xxx"` causes part of the URL path to be extracted and written to the tagged field.

`nvelope:"query,name=xxx"` causes the named URL query parameters to be extracted and written to the tagged field.

`nvelope:"header,name=xxx"` causes the named HTTP header to be extracted and written to the tagged field.

`nvelope:"cookie,name=xxx"` cause the named HTTP cookie to be extracted and writted to the tagged field.

Path, query, header, and cookie support options described in https://swagger.io/docs/specification/serialization/ for controlling how to serialize. The following are supported as appropriate.

explode=true			# default for query, header
explode=false			# default for path
delimiter=comma			# default
delimiter=space			# query parameters only
delimiter=pipe			# query parameters only
allowReserved=false		# default
allowReserved=true		# query parameters only
form=false			# default
form=true			# cookies only
content=application/json	# specifies that the value should be decoded with JSON
content=application/xml		# specifies that the value should be decoded with XML
content=application/yaml	# specifies that the value should be decoded with YAML
content=text/yaml		# specifies that the value should be decoded with YAML
deepObject=false		# default
deepObject=true			# required for query object

"style=label" and "style=matrix" are NOT yet supported for path parameters.

For query parameters filling maps and structs, the only the following combinations are supported:

deepObject=true
deepObject=false,explode=false

When filling embedded structs from query, or header, parameters, using explode=false or deepObject=true, tagging struct members is optional. Tag them with their name or with "-" if you do not want them filled.

type Fillme struct {
	Embedded struct {
		IntValue    int                     // will get filled by key "IntValue"
		FloatValue  float64 `nvelope:"-"`   // will not get filled
		StringValue string  `nvelope:"bob"` // will get filled by key "bob"
	} `nvelope:"query,name=embedded,explode=false"`
}

"deepObject=true" is only supported for maps and structs and only for query parameters.

Use "explode=true" combined with setting a "content" when you have a map to a struct or a slice of structs and each value will be encoded in JSON/XML independently. If the entire map is encoded, then use "explode=false".

GenerateDecoder uses https://pkg.go.dev/github.com/muir/reflectutils#MakeStringSetter to unpack strings into struct fields. That provides support for time.Duration and anything that implements encoding.TextUnmarshaler or flag.Value. Additional custom decoders can be registered with https://pkg.go.dev/github.com/muir/reflectutils#RegisterStringSetter .

There are a couple of example decoders defined in https://github.com/muir/nape and also https://github.com/muir/nchi .

func GetReturnCode

func GetReturnCode(err error) int

GetReturnCode turns an error into an HTTP response code.

func LoggerFromStd

func LoggerFromStd(log StdLogger) func() BasicLogger

LoggerFromStd creates a

func MakeResponseEncoder

func MakeResponseEncoder(
	name string,
	encoderFuncArgs ...ResponseEncoderFuncArg,
) nject.Provider

MakeResponseEncoder generates an nject Provider to encode API responses.

The generated provider is a wrapper that invokes the rest of the handler injection chain and expect to receive as return values an Response and and error. If the error is not nil, then the response becomes the error.

If more than one encoder is configurured, then MakeResponseEncoder will default to the first one specified in its functional arguments.

func MiddlewareBaseWriter

func MiddlewareBaseWriter(m ...func(http.HandlerFunc) http.HandlerFunc) nject.Provider

MiddlewareBaseWriter acts as a translator. In the Go world, there are a bunch of packages that expect to use the wrapping

func(http.HandlerFunc) http.HandlerFunc

pattern. The func(http.HandlerFunc) http.HandlerFunc pattern is harder to use and not as expressive as the patterns supported by npoint and nvelope, but there may be code written with the func(http.HandlerFunc) http.HandlerFunc pattern that you want to use with npoint and nvelope.

MiddlewareBaseWriter converts existing func(http.HandlerFunc) http.HandlerFunc functions so that they're compatible with nject. Because Middleware may wrap http.ResponseWriter, it should be used earlier in the injection chain than InjectWriter so that InjectWriter gets the already-wrapped http.ResponseWriter. Use MiddlewareBaseWriter if you suspect that the middleware you're wrapping replaces the writer.

func MiddlewareDeferredWriter

func MiddlewareDeferredWriter(m ...func(http.HandlerFunc) http.HandlerFunc) nject.Provider

MiddlewareDeferredWriter acts as a translator. In the Go world, there are a bunch of packages that expect to use the wrapping

func(http.HandlerFunc) http.HandlerFunc

pattern. The func(http.HandlerFunc) http.HandlerFunc pattern is harder to use and not as expressive as the patterns supported by npoint and nvelope, but there may be code written with the func(http.HandlerFunc) http.HandlerFunc pattern that you want to use with npoint and nvelope.

MiddlewareDeferredWriter converts existing func(http.HandlerFunc) http.HandlerFunc functions so that they're compatible with nject. MiddlewareDeferredWriter injects a DeferredWriter into the the func(http.HandlerFunc) http.HandlerFunc handler chain. If the chain replaces the writer, there will be two writers in play at once and results may be inconsistent. MiddlewareDeferredWriter must be used after InjectWriter. Use MiddlewareDeferredWriter if you know that the middleware you're wrapping does not replace the writer.

func MiddlewareHandlerBaseWriter

func MiddlewareHandlerBaseWriter(m ...func(http.Handler) http.Handler) nject.Provider

MiddlewareHandlerBaseWriter acts as a translator. In the Go world, there are a bunch of packages that expect to use the wrapping

func(http.Handler) http.Handler

pattern. The func(http.HandlerFunc) http.HandlerFunc pattern is harder to use and not as expressive as the patterns supported by npoint and nvelope, but there may be code written with the func(http.HandlerFunc) http.HandlerFunc pattern that you want to use with npoint and nvelope.

MiddlewareHandlerBaseWriter converts existing func(http.Handler) http.Handler functions so that they're compatible with nject. Because Middleware may wrap http.ResponseWriter, it should be used earlier in the injection chain than InjectWriter so that InjectWriter gets the already-wrapped http.ResponseWriter. Use MiddlewareBaseWriter if you suspect that the middleware you're wrapping replaces the writer.

func MiddlewareHandlerDeferredWriter

func MiddlewareHandlerDeferredWriter(m ...func(http.Handler) http.Handler) nject.Provider

MiddlewareHandlerDeferredWriter acts as a translator. In the Go world, there are a bunch of packages that expect to use the wrapping

func(http.Handler) http.Handler

pattern. The func(http.Handler) http.Handler pattern is harder to use and not as expressive as the patterns supported by npoint and nvelope, but there may be code written with the func(http.Handler) http.Handler pattern that you want to use with npoint and nvelope.

MiddlewareHandlerDeferredWriter converts existing func(http.Handler) http.Handler functions so that they're compatible with nject. MiddlewareHandlerDeferredWriter injects a DeferredWriter into the the func(http.Handler) http.Handler handler chain. If the chain replaces the writer, there will be two writers in play at once and results may be inconsistent. MiddlewareHandlerDeferredWriter must be used after InjectWriter. Use MiddlewareHandlerDeferredWriter if you know that the middleware you're wrapping does not replace the writer.

func NotFound

func NotFound(err error) error

NotFound annotates an error has giving 404 HTTP return code

func RecoverInterface

func RecoverInterface(err error) interface{}

RecoverInterface returns the interface{} that recover() originally provided. Or it returns nil if the error isn't a from a panic recovery. This works only in conjunction with SetErrorOnPanic() and CatchPanic.

func RecoverStack

func RecoverStack(err error) string

RecoverStack returns the stack from when recover() originally caught the panic. Or it returns "" if the error isn't a from a panic recovery. This works only in conjunction with SetErrorOnPanic() and CatchPanic.

Example
package main

import (
	"fmt"

	"github.com/muir/nvelope"
)

func main() {
	f := func(i int) (err error) {
		defer nvelope.SetErrorOnPanic(&err, nvelope.NoLogger())
		return func() error {
			switch i {
			case 0:
				panic("zero")
			case 1:
				return fmt.Errorf("a one")
			default:
				return nil
			}
		}()
	}
	err := f(0)
	fmt.Println(err)
	stack := nvelope.RecoverStack(err)
	fmt.Println(len(stack) > 1000)
}
Output:

panic: zero
true

func ReturnCode

func ReturnCode(err error, code int) error

ReturnCode associates an HTTP return code with a error. if err is nil, then nil is returned.

func SetErrorOnPanic

func SetErrorOnPanic(ep *error, log BasicLogger)

SetErrorOnPanic should be called as a defer. It sets an error value if there is a panic.

func Unauthorized

func Unauthorized(err error) error

Unauthorized annotates an error has giving 401 HTTP return code

Types

type APIEnforcerFunc

type APIEnforcerFunc func(httpCode int, enc []byte, header http.Header, r *http.Request) error

type BasicLogger

type BasicLogger interface {
	Debug(msg string, fields ...map[string]interface{})
	Error(msg string, fields ...map[string]interface{})
	Warn(msg string, fields ...map[string]interface{})
}

BasicLogger is just the start of what a logger might support. It exists mostly as a placeholder. Future versions of nvelope will prefer more capabile loggers but will use type assertions so that the BasicLogger will remain acceptable to the APIs.

func NoLogger

func NoLogger() BasicLogger

NoLogger injects a BasicLogger that discards all inputs

type Body

type Body []byte

Body is a type provideded by ReadBody: it is a []byte with the request body pre-read.

type CanModel

type CanModel interface {
	error
	Model() encoding.TextUnmarshaler
}

CanModel represents errors that can transform themselves into a model for logging.

type DecodeInputsGeneratorOpt

type DecodeInputsGeneratorOpt func(*eigo)

DecodeInputsGeneratorOpt are functional arguments for GenerateDecoder

func RejectUnknownQueryParameters

func RejectUnknownQueryParameters(b bool) DecodeInputsGeneratorOpt

RejectUnknownQueryParameters true indicates that if there are any query parameters supplied that were not expected, the request should be rejected with a 400 response code. This parameter also controls what happens if there an embedded object is filled and there is no object key corresponding to the request parameter.

This does not apply to query parameters with content=application/json decodings. If you want to disallow unknown tags for content= decodings, define a custom decoder.

func WithDecoder

func WithDecoder(contentType string, decoder Decoder) DecodeInputsGeneratorOpt

WithDecoder maps conent types (eg "application/json") to decode functions (eg json.Unmarshal). If a Content-Type header is used in the requet, then the value of that header will be used to pick a decoder.

When using a decoder, the body must be provided as an nvelope.Body parameter. Use nvelope.ReadBody to do that.

func WithDefaultContentType

func WithDefaultContentType(contentType string) DecodeInputsGeneratorOpt

WithDefaultContentType specifies which model decoder to use when no "Content-Type" header was sent.

func WithPathVarsFunction

func WithPathVarsFunction(pathVarFunction interface{}) DecodeInputsGeneratorOpt

WithPathVarsFunction is required if there are any variables from the path/route that need to be extracted. What's required is a function that returns a function to lookup path/route variables. The first function can take whatever arguments it needs and they'll be supplied as part of the injection chain.

For gorilla/mux:

WithPathVarsFunction(func(r *http.Request) RouteVarLookup {
	vars := mux.Vars(r)
	return func(v string) string {
		return vars[v]
	}
})

For httprouter:

WithPathVarsFunction(func(params httprouter.Params) RouteVarLookup {
	return params.ByName
})

func WithTag

func WithTag(tag string) DecodeInputsGeneratorOpt

WithTag overrides the tag for specifying fields to be filled from the http request. The default is "nvelope"

type Decoder

type Decoder func([]byte, interface{}) error

Decoder is the signature for decoders: take bytes and a pointer to something and deserialize it.

type DeferredWriter

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

DeferredWriter that wraps an underlying http.ResponseWriter. DeferredWriter buffers writes and headers. The buffer can be reset. When it's time to actually write, use Flush().

func NewDeferredWriter

func NewDeferredWriter(w http.ResponseWriter) (*DeferredWriter, http.ResponseWriter)

NewDeferredWriter returns a DeferredWriter based on a base ResponseWriter. It re-injects the base writer so that in effect, there is only one writer present.

func (*DeferredWriter) Body added in v0.4.0

func (w *DeferredWriter) Body() ([]byte, int, error)

Body returns the internal buffer used by DeferredWriter. Do not modify it. It also returns the status code (if set). If UnderlyingWriter() has been called, then Body() will return an error since the underlying buffer does not represent what has been written.

func (*DeferredWriter) Done

func (w *DeferredWriter) Done() bool

Done returns true if the DeferredWriter is in passthrough mode.

func (*DeferredWriter) Flush

func (w *DeferredWriter) Flush() error

Flush pushes the buffered write content through to the base writer. You can only flush once. After a flush, all further calls are passed through to be base writer. WriteHeader() will be called on the base writer even if there is no buffered data.

func (*DeferredWriter) FlushIfNotFlushed

func (w *DeferredWriter) FlushIfNotFlushed() error

FlushIfNotFlushed calls Flush if the DeferredWriter is not in passthrough mode.

func (*DeferredWriter) Header

func (w *DeferredWriter) Header() http.Header

Header is the same as http.ResponseWriter.Header

func (*DeferredWriter) PreserveHeader

func (w *DeferredWriter) PreserveHeader()

PreserveHeader saves the current Header so that a Reset will revert back to the header just saved.

func (*DeferredWriter) Reset

func (w *DeferredWriter) Reset() error

Reset empties the DeferredWriter's buffers and resets its Header back to its original state. Reset returns error if UnderlyingWriter() or Flush() have been called.

func (*DeferredWriter) UnderlyingWriter

func (w *DeferredWriter) UnderlyingWriter() http.ResponseWriter

UnderlyingWriter returns the underlying writer. Any header modifications made with the DeferredWriter are copied to the base writer. After a call to UnderlyingWriter, the DeferredWriter switches to passthrough mode: all future calls to Write(), Header(), etc are passed through to the http.ResponseWriter that was used to initialize the DeferredWrited.

Any writes made before the call to UnderlyingWriter are discarded. Call Flush() first to preserve writes.

func (*DeferredWriter) Write

func (w *DeferredWriter) Write(b []byte) (int, error)

Write is the same as http.ResponseWriter.Write except that the action is delayed until Flush() is called.

func (*DeferredWriter) WriteHeader

func (w *DeferredWriter) WriteHeader(statusCode int)

WriteHeader is the same as http.ResponseWriter.WriteHeader except that the action is delayed until Flush() is called.

type EncoderSpecificFuncArg

type EncoderSpecificFuncArg func(*specificEncoder)

EncoderSpecificFuncArg is a functional arguemnt for WithEncoder

func WithAPIEnforcer

func WithAPIEnforcer(apiEnforcer APIEnforcerFunc) EncoderSpecificFuncArg

WithAPIEnforcer specifies a function that can check if the encoded API response is valid for the endpoint that is generating the response. This is where swagger enforcement could be added. The default is not not verify API conformance.

https://github.com/muir/nvalid provides a function to generate an APIEnforcerFunc from swagger.

func WithEncoderErrorTransform

func WithEncoderErrorTransform(errorTransformer ErrorTranformer) EncoderSpecificFuncArg

WithEncoderErrorTransform provides an encoder-specific function to transform errors before encoding them using the normal encoder. The return values are the model to use instead of the error and a boolean to indicate that the replacement should be used. If the boolean is false, then a plain text error message will be generated using err.Error().

type ErrorTranformer

type ErrorTranformer func(error) (replacementModel interface{}, useReplacement bool)

ErrorTranformer transforms an error into a model that can be logged.

type LogFlusher

type LogFlusher interface {
	Flush()
}

LogFlusher is used to check if a logger implements Flush(). This is useful as part of a panic handler.

type Response

type Response interface{}

Response is an empty interface that is the expected return value from endpoints.

type ResponseEncoderFuncArg

type ResponseEncoderFuncArg func(*encoderOptions)

ResponseEncoderFuncArg is a function argument for MakeResponseEncoder

func WithEncoder

func WithEncoder(contentType string, encode func(interface{}) ([]byte, error), encoderOpts ...EncoderSpecificFuncArg) ResponseEncoderFuncArg

WithEncoder adds an model encoder to what MakeResponseEncoder will support. The first encoder added becomes the default encoder that is used if there is no match between the client's Accept header and the encoders that MakeResponseEncoder knows about.

func WithErrorModel

func WithErrorModel(errorTransformer ErrorTranformer) ResponseEncoderFuncArg

WithErrorModel provides a function to transform errors before encoding them using the normal encoder. The return values are the model to use instead of the error and a boolean to indicate that the replacement should be used. If the boolean is false, then a plain text error message will be generated using err.Error().

type RouteVarLookup

type RouteVarLookup func(string) string

type StdLogger

type StdLogger interface {
	Print(v ...interface{})
}

StdLogger is implmented by the base library log.Logger

Jump to

Keyboard shortcuts

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