rpclens

package module
v0.0.0-...-d91a1f5 Latest Latest
Warning

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

Go to latest
Published: Jan 1, 2026 License: MIT Imports: 15 Imported by: 0

README

go-rpclens

An experimental framework for creating HTTP API endpoints using the lens pattern.

NOTE: This curently (as of go 1.26) requires building with GOEXPERIMENT=jsonv2 as it requires the eociding/json/v2 library (which addresses several major issues including correctly serializing empty lists).

A lens is a function mappign operations on a part to operations on the whole. If the operations are total (with no error pathway), such a function acts like a bidirectional, logical property accessor, and is a common pattern in functional programming. If we generalize the concept to allow operations to fail, instead of providing access to a field, the lens provides access to a specific case, with the lens itself handling the others.

In this library, we provide genrealized lenses to handle some of the common cases of HTTP API calls. In all of these, the outer operation is a http.Handler (which can be used by ServeMux or any compatible framework), accepting a request and writing out a response. The inner operation takes (optionally, depending on the lens used) a parsed response body and returns either a structured response (such as NoContent or JSONBody) or a structured error (implementing Problem). If a body is required but cannot be parsed, an error will be returned immediately and the inner operation will not be called. This allows the operation to be decoupled from the HTTP protocol logic and use standard go control flow, taking parameters and returning values and errors.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var AllowBlankContentType = false

Incorrect Content-Type will always be rejected (required to prevent XSRF which can send JSON as text/plain bypassing CORS), but the header can be made optional.

Options to use when processing and emitting JSON bodies

Functions

func HandleEmptyRequest

func HandleEmptyRequest[T HTTPResponse](endpoint func(r *http.Request, log *slog.Logger) (T, Problem)) http.Handler

func HandleJSONRequest

func HandleJSONRequest[S any, T HTTPResponse](endpoint func(r *http.Request, body S, log *slog.Logger) (T, Problem)) http.Handler

func ListenAndServeUntilSignal

func ListenAndServeUntilSignal(server *http.Server) error

func LogProblem

func LogProblem(ctx context.Context, log *slog.Logger, p Problem)

Types

type BasicProblem

type BasicProblem struct {
	PType           ProblemType
	Instance        string
	Detail          string
	InternalMessage string
	WrappedError    error
}

func (*BasicProblem) Error

func (p *BasicProblem) Error() string

func (*BasicProblem) ErrorData

func (p *BasicProblem) ErrorData() map[string]any

func (*BasicProblem) ProblemData

func (p *BasicProblem) ProblemData() map[string]any

func (*BasicProblem) ProblemDetail

func (p *BasicProblem) ProblemDetail() string

func (*BasicProblem) ProblemInstance

func (p *BasicProblem) ProblemInstance() string

func (*BasicProblem) ProblemType

func (p *BasicProblem) ProblemType() ProblemType

func (*BasicProblem) SetProblemHeaders

func (p *BasicProblem) SetProblemHeaders(h http.Header)

func (*BasicProblem) Unwrap

func (p *BasicProblem) Unwrap() error

type EmptyBodyHandler

type EmptyBodyHandler[T HTTPResponse] struct {
	Name         string
	EndpointFunc func(r *http.Request, log *slog.Logger) (T, Problem)
}

func (*EmptyBodyHandler[T]) ServeHTTP

func (h *EmptyBodyHandler[T]) ServeHTTP(w http.ResponseWriter, r *http.Request)

type HTTPResponse

type HTTPResponse interface {
	WriteHTTPResponse(w http.ResponseWriter) error // the error should be ignorable if you do not care about dropped connections
}

type HeadersOnly

type HeadersOnly struct {
	Status  int
	Headers map[string]string
}

func (*HeadersOnly) WriteHTTPResponse

func (r *HeadersOnly) WriteHTTPResponse(w http.ResponseWriter) error

nil is a valid value here, as NoContent has no content

type JSONBody

type JSONBody[T any] struct {
	Status       int
	ExtraHeaders map[string]string
	Body         T
}

func AsJSONBody

func AsJSONBody[T any](body T) *JSONBody[T]

func (*JSONBody[T]) WriteHTTPResponse

func (r *JSONBody[T]) WriteHTTPResponse(w http.ResponseWriter) error

type JSONBodyHandler

type JSONBodyHandler[S any, T HTTPResponse] struct {
	Name         string
	EndpointFunc func(r *http.Request, body S, log *slog.Logger) (T, Problem)
}

func (*JSONBodyHandler[S, T]) ServeHTTP

func (h *JSONBodyHandler[S, T]) ServeHTTP(w http.ResponseWriter, r *http.Request)

type NoContent

type NoContent struct{}

func (*NoContent) WriteHTTPResponse

func (r *NoContent) WriteHTTPResponse(w http.ResponseWriter) error

nil is a valid value here, as NoContent has no content

type Problem

type Problem interface {
	error                                      // allows this to be returned as an error as well as providing a log message through the Error() method
	ProblemType() ProblemType                  // type of error including the status code
	ProblemInstance() string                   // Optional URI pointing to a persistent record of this occurrence of the problem.
	ProblemDetail() string                     // Human-readable error message to be returned to the client
	SetProblemHeaders(headers_out http.Header) // set any headers required for the response
	ProblemData() map[string]any               // additional fields to be sent to the response, MUST be json/v2 serializable
	ErrorData() map[string]any                 // additional data to be included in log
}

Am error message that can be returned from an HTTP API. Follows RFC 9457 structure.

func GetJSONBody

func GetJSONBody[T any](r *http.Request) (T, Problem)

func InstancedProblemf

func InstancedProblemf(ptype ProblemType, instance string, format string, args ...any) Problem

func ProblemOrFallback

func ProblemOrFallback(err error, fallbackType ProblemType, fallbackInstance string, fallbackDetail string) Problem

func Problemf

func Problemf(ptype ProblemType, format string, args ...any) Problem

func StatusProblemf

func StatusProblemf(status int, format string, args ...any) Problem

type ProblemJSON

type ProblemJSON struct {
	Problem
}

func (*ProblemJSON) RawBody

func (p *ProblemJSON) RawBody() []byte

func (*ProblemJSON) WriteHTTPResponse

func (p *ProblemJSON) WriteHTTPResponse(w http.ResponseWriter) error

Writes a Problem as an HTTP response. Returns the rrror from the Write call itself, which can be ignored if you don't care about dropped sockets. Panics if ProblemData returns fields with non-serializable types.

type ProblemType

type ProblemType struct {
	Title    string     `json:"title"`
	Status   int        `json:"status"`
	URI      string     `json:"type,omitempty"`
	LogLevel slog.Level `json:"-"`
}

Error type from RFC 9457. This extends an HTTP status code with an optional URI for further specificity. Title is descriptive text but should be consistent for a given problem type.

func ProblemStatus

func ProblemStatus(status int) ProblemType

ProbemType for an HTTP status code with no further specificity.

type UnsupportedMediaTypeError

type UnsupportedMediaTypeError struct {
	Accepted []string
	Received string
	WantUTF8 bool
}

func (*UnsupportedMediaTypeError) Error

func (e *UnsupportedMediaTypeError) Error() string

func (*UnsupportedMediaTypeError) ErrorData

func (e *UnsupportedMediaTypeError) ErrorData() map[string]any

func (*UnsupportedMediaTypeError) ProblemData

func (e *UnsupportedMediaTypeError) ProblemData() map[string]any

func (*UnsupportedMediaTypeError) ProblemDetail

func (e *UnsupportedMediaTypeError) ProblemDetail() string

func (*UnsupportedMediaTypeError) ProblemInstance

func (e *UnsupportedMediaTypeError) ProblemInstance() string

func (*UnsupportedMediaTypeError) ProblemType

func (e *UnsupportedMediaTypeError) ProblemType() ProblemType

func (*UnsupportedMediaTypeError) SetProblemHeaders

func (e *UnsupportedMediaTypeError) SetProblemHeaders(headers_out http.Header)

Jump to

Keyboard shortcuts

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