Documentation ¶
Overview ¶
Example ¶
Example shows an injection chain handling a single endpoint using nject, npoint, and nvelope.
package main import ( "errors" "fmt" "io" "log" "net/http" "net/http/httptest" "strings" "github.com/gorilla/mux" "github.com/muir/nject/npoint" "github.com/muir/nject/nvelope" ) // 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 := npoint.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, nvelope.DecodeJSON, HandleExampleEndpoint, ).Methods("POST") } // Example shows an injection chain handling a single endpoint using nject, // npoint, 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 := io.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 ¶
- Variables
- func BadRequest(err error) error
- func Forbidden(err error) error
- func GenerateDecoder(genOpts ...DecodeInputsGeneratorOpt) interface{}
- func GetReturnCode(err error) int
- func LoggerFromStd(log StdLogger) func() BasicLogger
- func MakeResponseEncoder(name string, encoderFuncArgs ...ResponseEncoderFuncArg) nject.Provider
- func MiddlewareBaseWriter(m ...func(http.HandlerFunc) http.HandlerFunc) nject.Provider
- func MiddlewareDeferredWriter(m ...func(http.HandlerFunc) http.HandlerFunc) nject.Provider
- func NotFound(err error) error
- func RecoverInterface(err error) interface{}
- func RecoverStack(err error) string
- func ReturnCode(err error, code int) error
- func SetErrorOnPanic(ep *error, log BasicLogger)
- func Unauthorized(err error) error
- type BasicLogger
- type Body
- type CanModel
- type DecodeInputsGeneratorOpt
- type Decoder
- type DeferredWriter
- func (w *DeferredWriter) Done() bool
- func (w *DeferredWriter) Flush() error
- func (w *DeferredWriter) FlushIfNotFlushed() error
- func (w *DeferredWriter) Header() http.Header
- func (w *DeferredWriter) PreserveHeader()
- func (w *DeferredWriter) Reset() error
- func (w *DeferredWriter) UnderlyingWriter() http.ResponseWriter
- func (w *DeferredWriter) Write(b []byte) (int, error)
- func (w *DeferredWriter) WriteHeader(statusCode int)
- type EncoderSpecificFuncArg
- type ErrorTranformer
- type LogFlusher
- type Response
- type ResponseEncoderFuncArg
- type StdLogger
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var AutoFlushWriter = nject.Provide("autoflush-writer", func(inner func(), w *DeferredWriter) {
inner()
if !w.Done() {
_ = w.Flush()
}
})
AutoFlushWriter calls Flush on the deferred writer if it hasn't already been done
var CatchPanic = nject.Provide("catch-panic", catchPanicInjector)
CatchPanic is a wrapp that catches downstream panics and returns an error a downsteam provider panic's.
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.
var DecodeJSON = GenerateDecoder( WithDecoder("application/json", json.Unmarshal), WithDefaultContentType("application/json"), )
DecodeJSON is is a pre-defined special nject.Provider created with GenerateDecoder for decoding JSON requests.
var DecodeXML = GenerateDecoder( WithDecoder("application/xml", xml.Unmarshal), WithDefaultContentType("application/xml"), )
DecodeXML is is a pre-defined special nject.Provider created with GenerateDecoder for decoding XML requests.
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.
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.
var InjectWriter = nject.Provide("writer", NewDeferredWriter)
InjectWriter injects a DeferredWriter
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.
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 ¶
BadRequest annotates an error has giving 400 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, the 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.
GenerateDecoder depends upon and uses Gorilla mux.
func GetReturnCode ¶
GetReturnCode turns an error into an HTTP response code.
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.
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.
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 ¶
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/nject/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 ¶
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 ¶
Unauthorized annotates an error has giving 401 HTTP return code
Types ¶
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.
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 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.
func WithDefaultContentType ¶
func WithDefaultContentType(contentType string) DecodeInputsGeneratorOpt
WithDefaultContentType specifies which model decoder to use when no "Content-Type" header was sent.
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 ¶
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
NewDeferredWriter returns a DeferredWriter based on a base ResponseWriter
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 ¶ added in v0.0.5
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.
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(encoding string, apiEnforcer func(httpCode int, enc []byte, r *http.Request) error) 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.
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 ¶
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().