jayson

package module
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Mar 23, 2025 License: MIT Imports: 14 Imported by: 0

README

jayson

Simple json response/error writer on steroids.

Design

Jayson defines Extension as interface that does 2 things - it can extend http response writer with additional headers, status codes, etc. - it can extend object with additional fields

Errors

Jayson provides way to register errors and provide extensions to them. When error is written to response writer, jayson looks up error and all its ancestors in registry and applies all extensions to it. This gives ability to define error once and use it in multiple places with different extensions. You can build error hierarchy and add extensions to each error in hierarchy.

Responses

Responses are similar to errors, but they are not errors. You can register responses and provide extensions to them. However when response is passed as object, all extensions that alter "object" can not do anything with it. It will just run extensions that alter response writer (status, headers). There is a way to provide extensions to object, but you need to wrap it in jayson.ExtObjectKeyValue which marks it as key of reponse object. This makes it easier to provide multiple extensions to single object.

Global state

Jayson provides global instance of jayson that can be used to register errors, responses and extensions. You can swap global instance with your own instance if you need to maintain separate jayson instance. You can also instantiate your own jayson instance and use it in your code.

Usage

Simple response

Jayson can directly marshal values provided to Response and write them to response writer. In this case jayson does not do any additional processing of object.

type User struct {
    ID string `json:"id"`
}

func Handler(rw http.ResponseWriter, r *http.Request) {
    // Write response with default status code
    jayson.G().Response(r.Context(), w, User{})
}

func HandlerCustomStatus(rw http.ResponseWriter, r *http.Request) {
    // Write response with custom status code
    jayson.G().Response(r.Context(), w, User{}, jayson.ExtStatus(http.StatusCreated))
}

Simple Error

When you only want to write error with default status code it's as easy as:

func HandlerSimpleError(rw http.ResponseWriter, r *http.Request) {
    // Write response with http.StatusInternalServerError status code
    jayson.G().Error(r.Context(), w, errors.New("boom internal error"))
}

Advanced Error usage

You can register errors with status codes and other extensions.

var (
    ErrNotFound = errors.New("not found")
)

func init() {
    // register error with status code
    jayson.Must(
        jayson.G().RegisterError(ErrNotFound, jayson.ExtStatus(http.StatusNotFound))
    )   
}

func HandlerAdvancedError(rw http.ResponseWriter, r *http.Request) {
    // Write error with http.StatusNotFound status code
    jayson.G().Error(r.Context(), w, ErrNotFound)
}

You can also add additional extensions to error

var (
    // ErrNotFound is generic not found error
    ErrNotFound = errors.New("not found")
    // ErrUserNotFound is returned when user is not found, it wraps ErrNotFound
    ErrUserNotFound = fmt.Errorf("%w: user", ErrNotFound)
)

func init() {
    jayson.Must(
        jayson.G().RegisterError(ErrNotFound, jayson.ExtStatus(http.StatusNotFound)),
    )
}


func HandlerUnwrap(rw http.ResponseWriter, r *http.Request) {
    // Write error with http.StatusNotFound status code (inherits from ErrNotFound) and additional error detail
    jayson.G().Error(r.Context(), w, ErrUserNotFound, jayson.ExtErrorDetail("user not found"))
}

Error can also be wrapped with additional extensions, and they inherit all extensions from parent error

var (
    Error1 = errors.New("error1")
    Error2 = fmt.Errorf("%w: error2", Error1)
)

func init() {
    jayson.Must(
        jayson.G().RegisterError(Error1, jayson.ExtStatus(http.StatusNotFound)),
        jayson.G().RegisterError(Error2, jayson.ExtErrorDetail("error2 detail")),
    )
}

func Handler(rw http.ResponseWriter, r *http.Request) {
    // Write error with http.StatusNotFound status code and error detail "error2 detail"
    jayson.G().Error(r.Context(), w, Error2)
}

Advanced Response usage


// CreatedUser is returned when user is created
type CreatedUser struct {
    ID string `json:"id"`
}

// Register all errors, structures with status and other extensions
func init() {
    // RegisterResponse registers response with status code (also non pointer type if not registered already)
    jayson.G().RegisterResponse(&CreatedUser{}, Status(http.StatusCreated))
}

// CreateUser http handler
func CreateUser(w http.ResponseWriter, r *http.Request) {
    // Write response
    jayson.G().Response(r.Context(), w, CreatedUser{}, jayson.Header("X-Request-ID", "123"))
}

Custom jayson instance

For special cases where you want to maintain separate jayson instance you can create your own instance.

type CreatedUser struct {
    ID string `json:"id"`
}

var (
	// jay is custom jayson instance
    jay = jayson.New(jayson.DefaultSettings())
)

func init() {
    jay.RegisterResponse(&CreatedUser{}, jayson.ExtStatus(http.StatusCreated))
}

func Handler(w http.ResponseWriter, r *http.Request) {
    jay.Response(r.Context(), w, CreatedUser{})
}

TODO:

  • ExtObjectUnwrap should not use json marshal/unmarshal but read struct/map fields directly

Author

Peter Vrba phonkee@phonkee.eu

Documentation

Index

Constants

View Source
const (
	// DebugMaxCallerDepth is the maximum depth of debug caller
	DebugMaxCallerDepth = 255
)

Variables

View Source
var (
	Warning = errors.New("jayson: warning")
	// ErrImproperlyConfigured is error returned when Jayson is improperly configured.
	ErrImproperlyConfigured = errors.New("jayson: improperly configured")
	WarnAlreadyRegistered   = fmt.Errorf("%w: already registered", Warning)
)
View Source
var (
	// Any is used to RegisterResponse and RegisterError for any response/error object,
	// It is used as base type for all response/error objects
	// regardless if it's registered or not.
	Any = anyTarget(0)
)

Functions

func ContextErrorValue

func ContextErrorValue(ctx context.Context) (error, bool)

ContextErrorValue returns the error value stored in the context.

func ContextObjectValue

func ContextObjectValue[T any](ctx context.Context) (T, bool)

ContextObjectValue returns the object value stored in the context.

func Must

func Must(err ...error)

Must panics if err is not nil, otherwise it returns the value.

func ReplaceGlobal added in v0.2.0

func ReplaceGlobal(j Jayson)

ReplaceGlobal replaces global instance of Jayson with given instance

func WrapError added in v0.4.0

func WrapError(err error, ext ...Extension) error

WrapError wraps error and adds extensions to it

Types

type Extended added in v0.4.0

type Extended interface {
	// Extensions returns list of extensions for the error.
	Extensions() []Extension
}

Extended is interface for errors that can be extended.

type Extension

type Extension interface {
	// ExtendResponseWriter extends the response
	ExtendResponseWriter(context.Context, http.ResponseWriter) bool
	// ExtendResponseObject extends the object
	ExtendResponseObject(context.Context, map[string]any) bool
}

Extension is the interface for Jayson ext.

It is used to alter the response or the object before it is sent to the client.

func ExtChain

func ExtChain(extensions ...Extension) Extension

ExtChain returns an extFunc that chains multiple ext together.

func ExtConditional

func ExtConditional(condition Extension, ext ...Extension) Extension

ExtConditional calls first extFunc, and if it returns true, it calls all the ext. This is useful for conditional ext based on context (such as debug mode or any context values).

func ExtFirst

func ExtFirst(ext ...Extension) Extension

ExtFirst returns an extFunc that returns the first extFunc that extends the response.

func ExtFunc

func ExtFunc(
	extResponseWriter ExtensionApplyResponseWriterFunc,
	extResponseObject ExtensionApplyResponseObjectFunc,
) Extension

ExtFunc is an extFunc that calls the given function to extend the response or the response object.

func ExtHeader

func ExtHeader(h http.Header) Extension

ExtHeader extends the response with the given HTTP headers.

func ExtHeaderValue

func ExtHeaderValue(k string, v string) Extension

ExtHeaderValue is an extFunc that adds a single HTTP header to the response.

func ExtNoop

func ExtNoop() Extension

ExtNoop does not extend the response or the response object.

func ExtObjectKeyValue

func ExtObjectKeyValue(key string, value any) Extension

ExtObjectKeyValue is an extFunc that adds a single key-value pair to the response object.

func ExtObjectKeyValuef

func ExtObjectKeyValuef(key string, format string, args ...any) Extension

ExtObjectKeyValuef is an extFunc that adds a single key-value pair to the response object based on a format string.

func ExtObjectUnwrap added in v0.2.0

func ExtObjectUnwrap(obj any) Extension

ExtObjectUnwrap is an Extension that converts the given object to the response object. It is useful if you want to add key/values to the response object (by altering it via Extensions). If the object is a struct, it will be converted to a map[string]any.

func ExtOmitObjectKey

func ExtOmitObjectKey(keys ...string) Extension

ExtOmitObjectKey is an extFunc that removes the given keys from the response object. This extFunc needs to be called at the end, so it removes the keys after all other ext have added their keys.

func ExtOmitSettingsKey added in v0.2.0

func ExtOmitSettingsKey(fn func(settings Settings) []string) Extension

ExtOmitSettingsKey is an extFunc that removes the given keys from the response object based on the settings.

func ExtStatus

func ExtStatus(status int) Extension

ExtStatus is an extension that sets the HTTP status code of the response.

type ExtensionApplyResponseObjectFunc

type ExtensionApplyResponseObjectFunc func(context.Context, map[string]any) bool

ExtensionApplyResponseObjectFunc is a function that extends the response object.

type ExtensionApplyResponseWriterFunc

type ExtensionApplyResponseWriterFunc func(context.Context, http.ResponseWriter) bool

ExtensionApplyResponseWriterFunc is a function that extends the response writer.

type Jayson

type Jayson interface {
	// Debug enables debug mode via zap logger.
	Debug(*zap.Logger)
	// Error writes error to the client.
	Error(context.Context, http.ResponseWriter, error, ...Extension)
	// RegisterError registers extFunc for given error.
	RegisterError(error, ...Extension) error
	// RegisterResponse registers extFunc for given response object.
	RegisterResponse(any, ...Extension) error
	// Response writes given object/error to the client.
	Response(context.Context, http.ResponseWriter, any, ...Extension)
}

Jayson is the interface that provides methods for writing responses to the client.

By default, there is a global instance to be used, but for some special purposes you can create your own (multiple servers, different settings, etc.)

func G added in v0.2.0

func G() Jayson

G returns global instance of Jayson

func New

func New(settings Settings) Jayson

New instantiates custom jayson instance. Usually you don't need to use it, since there is a _g instance.

type Settings

type Settings struct {
	DefaultErrorStatus        int
	DefaultErrorMessageKey    string
	DefaultErrorStatusCodeKey string
	DefaultErrorStatusTextKey string
	DefaultResponseStatus     int
	DefaultUnwrapObjectKey    string // if unwrap fails, object will be placed under this key
}

Settings for jayson instance

func ContextSettingsValue

func ContextSettingsValue(ctx context.Context) Settings

ContextSettingsValue returns the settings value stored in the context.

func DefaultSettings

func DefaultSettings() Settings

DefaultSettings returns default settings for jayson instance

func (*Settings) Validate

func (s *Settings) Validate()

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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