Documentation
¶
Overview ¶
Package jsonrpc implements transport-neutral JSON-RPC 2.0 clients and servers. It provides explicit protocol envelopes, request dispatch, middleware, structured errors, strict client response validation, and thin net/http adapters without imposing a router, logger, tracer, or validator.
Requests and responses can be processed directly as bytes through Dispatcher and Transport, allowing custom transports to share the same protocol rules. The protocol is defined by the JSON-RPC 2.0 specification: https://www.jsonrpc.org/specification.
See the repository documentation for compatibility guarantees and complete server, client, notification, and batch examples.
Example ¶
package main
import (
"context"
"encoding/json"
"fmt"
"net/http/httptest"
jsonrpc "github.com/faustbrian/go-jsonrpc"
)
func main() {
registry := jsonrpc.NewRegistry()
_ = registry.Register("add", func(_ context.Context, raw json.RawMessage) (any, error) {
values, rpcErr := jsonrpc.DecodeParams[[]int](raw)
if rpcErr != nil || len(values) != 2 {
return nil, jsonrpc.InvalidParams()
}
return values[0] + values[1], nil
})
server := httptest.NewServer(jsonrpc.NewHTTPHandler(jsonrpc.NewDispatcher(registry)))
defer server.Close()
transport, _ := jsonrpc.NewHTTPTransport(server.URL)
result, _ := jsonrpc.Call[int](context.Background(), jsonrpc.NewClient(transport), "add", []int{2, 3})
fmt.Println(result)
}
Output: 5
Index ¶
- Constants
- Variables
- func Call[T any](ctx context.Context, client *Client, method string, params any) (T, error)
- func IsJSONContentType(value string) bool
- type AtomicIDGenerator
- type BatchCall
- type Client
- type ClientOption
- type Dispatcher
- type DispatcherOption
- func WithErrorMapper(mapper ErrorMapper) DispatcherOption
- func WithHooks(hooks Hooks) DispatcherOption
- func WithMaxBatchItems(limit int) DispatcherOption
- func WithMaxDispatchBytes(limit int64) DispatcherOption
- func WithMaxNestingDepth(limit int) DispatcherOption
- func WithMiddleware(middleware ...Middleware) DispatcherOption
- type Error
- type ErrorMapper
- type HTTPHandler
- type HTTPHandlerOption
- type HTTPStatusError
- type HTTPTransport
- type HTTPTransportOption
- type Handler
- type Hooks
- type ID
- type IDGenerator
- type IDKind
- type Middleware
- type Registry
- type Request
- type Response
- type Transport
- type TransportFunc
Examples ¶
Constants ¶
const ( // CodeRequestLimitExceeded is the implementation-defined server error used // when a dispatcher payload or batch exceeds its configured bound. CodeRequestLimitExceeded = -32000 // CodeParseError indicates invalid JSON. CodeParseError = -32700 // CodeInvalidRequest indicates a structurally invalid request. CodeInvalidRequest = -32600 // CodeMethodNotFound indicates that no handler is registered for a method. CodeMethodNotFound = -32601 // CodeInvalidParams indicates invalid method parameters. CodeInvalidParams = -32602 // CodeInternalError indicates an internal JSON-RPC failure. CodeInternalError = -32603 )
const Version = "2.0"
Version is the JSON-RPC protocol version implemented by this package.
Variables ¶
var ( ErrTransport = errors.New("jsonrpc: transport error") ErrInvalidResponse = errors.New("jsonrpc: invalid response") ErrMismatchedID = errors.New("jsonrpc: mismatched response id") ErrUnexpectedResponse = errors.New("jsonrpc: unexpected response") ErrMissingResponse = errors.New("jsonrpc: missing batch response") ErrDuplicateResponse = errors.New("jsonrpc: duplicate batch response") ErrDuplicateRequestID = errors.New("jsonrpc: duplicate batch request id") ErrEmptyBatch = errors.New("jsonrpc: empty client batch") ErrClientResponseTooLarge = errors.New("jsonrpc: client response too large") )
Client errors identify transport, envelope, correlation, batch, and size failures and support errors.Is through direct return or wrapping.
var ( ErrHTTPStatus = errors.New("jsonrpc: unexpected HTTP status") ErrHTTPContentType = errors.New("jsonrpc: invalid HTTP response content type") ErrResponseTooLarge = errors.New("jsonrpc: HTTP response too large") )
HTTP transport errors distinguish status, content-type, and body-limit failures and support errors.Is through direct return or wrapping.
var ( ErrInvalidMethodName = errors.New("jsonrpc: invalid method name") ErrMethodAlreadyRegistered = errors.New("jsonrpc: method already registered") ErrNilHandler = errors.New("jsonrpc: nil handler") )
Registration errors identify reserved method names, duplicate methods, and nil handlers.
Functions ¶
func IsJSONContentType ¶
IsJSONContentType reports whether value is application/json, application/json-rpc, or an application subtype ending in +json.
Types ¶
type AtomicIDGenerator ¶
type AtomicIDGenerator struct {
// contains filtered or unexported fields
}
AtomicIDGenerator generates concurrency-safe, monotonically increasing numeric IDs.
func NewAtomicIDGenerator ¶
func NewAtomicIDGenerator(start int64) *AtomicIDGenerator
NewAtomicIDGenerator creates a generator whose first ID is start+1.
func (*AtomicIDGenerator) NextID ¶
func (generator *AtomicIDGenerator) NextID() ID
NextID atomically increments the generator and returns the new numeric ID.
type BatchCall ¶
type BatchCall struct {
// Method is the JSON-RPC method name.
Method string
// Params must encode as an object or array when non-nil.
Params any
// Result receives a successful response when non-nil.
Result any
// Notification omits the request ID and expects no response member.
Notification bool
// Error receives a valid JSON-RPC failure response.
Error *Error
// contains filtered or unexported fields
}
BatchCall describes one member of a client batch. Batch writes a decoded success into Result and assigns a protocol failure to Error.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client validates requests and correlates responses over a Transport. Its default AtomicIDGenerator is safe for concurrent calls; custom transports, generators, and BatchCall values retain their own concurrency contracts.
func NewClient ¶
func NewClient(transport Transport, options ...ClientOption) *Client
NewClient constructs a client. A nil transport is reported as ErrTransport when an operation is attempted. Nil options are ignored.
func (*Client) Batch ¶
Batch sends calls together and correlates every non-notification response by ID. It rejects empty batches, nil calls, duplicate generated IDs, and malformed response membership.
Example ¶
package main
import (
"context"
"fmt"
jsonrpc "github.com/faustbrian/go-jsonrpc"
)
func main() {
transport := jsonrpc.TransportFunc(func(context.Context, []byte) ([]byte, error) {
return []byte(`[
{"jsonrpc":"2.0","result":4,"id":2},
{"jsonrpc":"2.0","result":2,"id":1}
]`), nil
})
client := jsonrpc.NewClient(transport)
var first, second int
_ = client.Batch(context.Background(),
&jsonrpc.BatchCall{Method: "double", Params: []int{1}, Result: &first},
&jsonrpc.BatchCall{Method: "double", Params: []int{2}, Result: &second},
)
fmt.Println(first, second)
}
Output: 2 4
type ClientOption ¶
type ClientOption func(*Client)
ClientOption configures a Client during construction.
func WithIDGenerator ¶
func WithIDGenerator(generator IDGenerator) ClientOption
WithIDGenerator installs a non-nil request ID generator.
func WithMaxClientResponseBytes ¶
func WithMaxClientResponseBytes(limit int64) ClientOption
WithMaxClientResponseBytes changes the client's four-MiB reply parsing limit.
type Dispatcher ¶
type Dispatcher struct {
// contains filtered or unexported fields
}
Dispatcher validates and executes JSON-RPC requests and batches. Its configuration is immutable after construction; its Registry may be updated concurrently.
func NewDispatcher ¶
func NewDispatcher(registry *Registry, options ...DispatcherOption) *Dispatcher
NewDispatcher constructs a bounded dispatcher. A nil registry is replaced by an empty registry, and nil options are ignored.
func (*Dispatcher) Dispatch ¶
Dispatch processes one JSON-RPC message. The boolean reports whether the caller must send the returned response; notifications intentionally return no response.
Notification and batch behavior follow:
func (*Dispatcher) DispatchSingle ¶
DispatchSingle processes one non-batch JSON-RPC message and returns its typed response before wire encoding. The boolean is false for notifications.
type DispatcherOption ¶
type DispatcherOption func(*Dispatcher)
DispatcherOption configures a Dispatcher during construction.
func WithErrorMapper ¶
func WithErrorMapper(mapper ErrorMapper) DispatcherOption
WithErrorMapper replaces the default internal-error mapper. Returning nil or an invalid error is contained as an internal error.
func WithHooks ¶
func WithHooks(hooks Hooks) DispatcherOption
WithHooks installs lifecycle observers. Hook panics are contained and hook mutations cannot change protocol output.
func WithMaxBatchItems ¶
func WithMaxBatchItems(limit int) DispatcherOption
WithMaxBatchItems changes the dispatcher's default limit of 1,024 members.
func WithMaxDispatchBytes ¶
func WithMaxDispatchBytes(limit int64) DispatcherOption
WithMaxDispatchBytes changes the dispatcher's four-MiB payload limit.
func WithMaxNestingDepth ¶
func WithMaxNestingDepth(limit int) DispatcherOption
WithMaxNestingDepth changes the maximum number of nested JSON arrays and objects accepted before dispatch. The default matches encoding/json's built-in maximum depth of 10,000.
func WithMiddleware ¶
func WithMiddleware(middleware ...Middleware) DispatcherOption
WithMiddleware appends middleware in outermost-to-innermost order.
Example ¶
package main
import (
"context"
"encoding/json"
"fmt"
jsonrpc "github.com/faustbrian/go-jsonrpc"
)
func main() {
registry := jsonrpc.NewRegistry()
_ = registry.Register("ping", func(context.Context, json.RawMessage) (any, error) {
return "pong", nil
})
logging := func(next jsonrpc.Handler) jsonrpc.Handler {
return func(ctx context.Context, params json.RawMessage) (any, error) {
request, _ := jsonrpc.RequestFromContext(ctx)
fmt.Println(request.Method)
return next(ctx, params)
}
}
dispatcher := jsonrpc.NewDispatcher(registry, jsonrpc.WithMiddleware(logging))
dispatcher.Dispatch(context.Background(), []byte(`{"jsonrpc":"2.0","method":"ping","id":1}`))
}
Output: ping
type Error ¶
type Error struct {
// Code is the integer JSON-RPC error code.
Code int `json:"code"`
// Message is the public error description.
Message string `json:"message"`
// Data contains optional public JSON details.
Data json.RawMessage `json:"data,omitempty"`
// contains filtered or unexported fields
}
Error is a JSON-RPC error object. Cause is retained locally and is never serialized, allowing callers to preserve diagnostic context safely. See https://www.jsonrpc.org/specification#error_object.
func DecodeParams ¶
func DecodeParams[T any](params json.RawMessage) (T, *Error)
DecodeParams strictly decodes params as T. Duplicate or unknown named members, malformed JSON, and trailing data return InvalidParams.
func InternalError ¶
func InternalError() *Error
InternalError constructs the standard internal error.
func InvalidParams ¶
func InvalidParams() *Error
InvalidParams constructs the standard invalid-params error.
func InvalidRequest ¶
func InvalidRequest() *Error
InvalidRequest constructs the standard invalid-request error.
func MethodNotFound ¶
func MethodNotFound() *Error
MethodNotFound constructs the standard method-not-found error.
func NewError ¶
NewError constructs a JSON-RPC error with code and public message. Application codes should avoid the protocol-reserved range -32768 through -32000.
func RequestLimitExceeded ¶
func RequestLimitExceeded() *Error
RequestLimitExceeded constructs the dispatcher resource-limit error.
func (*Error) Error ¶
Error returns a textual representation containing the public code and message.
func (*Error) UnmarshalJSON ¶
UnmarshalJSON decodes a strict JSON-RPC error object.
type ErrorMapper ¶
ErrorMapper converts an application error into safe public JSON-RPC data.
type HTTPHandler ¶
type HTTPHandler struct {
// contains filtered or unexported fields
}
HTTPHandler adapts a Dispatcher to net/http with strict POST, content-type, and request-body handling.
func NewHTTPHandler ¶
func NewHTTPHandler(dispatcher *Dispatcher, options ...HTTPHandlerOption) *HTTPHandler
NewHTTPHandler constructs an HTTP handler. A nil dispatcher is replaced by an empty dispatcher, and nil options are ignored.
func (*HTTPHandler) ServeHTTP ¶
func (h *HTTPHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request)
ServeHTTP validates and dispatches one HTTP JSON-RPC request.
type HTTPHandlerOption ¶
type HTTPHandlerOption func(*HTTPHandler)
HTTPHandlerOption configures an HTTPHandler during construction.
func WithMaxRequestBytes ¶
func WithMaxRequestBytes(limit int64) HTTPHandlerOption
WithMaxRequestBytes changes the default four-MiB HTTP request-body limit.
type HTTPStatusError ¶
type HTTPStatusError struct {
// StatusCode is the peer's HTTP response status.
StatusCode int
// Body is the trimmed, bounded response body.
Body string
}
HTTPStatusError reports a non-200 HTTP response and its bounded body.
func (*HTTPStatusError) Error ¶
func (err *HTTPStatusError) Error() string
Error returns the status code and, when present, response body.
func (*HTTPStatusError) Unwrap ¶
func (err *HTTPStatusError) Unwrap() error
Unwrap returns ErrHTTPStatus.
type HTTPTransport ¶
type HTTPTransport struct {
// contains filtered or unexported fields
}
HTTPTransport exchanges JSON-RPC payloads over HTTP POST.
func NewHTTPTransport ¶
func NewHTTPTransport(endpoint string, options ...HTTPTransportOption) (*HTTPTransport, error)
NewHTTPTransport validates an HTTP(S) endpoint and constructs a transport. The default client does not follow redirects. Nil options are ignored.
type HTTPTransportOption ¶
type HTTPTransportOption func(*HTTPTransport)
HTTPTransportOption configures an HTTPTransport during construction.
func WithHTTPClient ¶
func WithHTTPClient(client *http.Client) HTTPTransportOption
WithHTTPClient installs a non-nil HTTP client. Its timeout and redirect policy remain caller-owned.
func WithHTTPHeader ¶
func WithHTTPHeader(name, value string) HTTPTransportOption
WithHTTPHeader adds a header to each request. Content-Type and Accept are always overwritten with JSON values during RoundTrip.
func WithMaxResponseBytes ¶
func WithMaxResponseBytes(limit int64) HTTPTransportOption
WithMaxResponseBytes changes the default four-MiB HTTP response-body limit.
type Hooks ¶
type Hooks struct {
// OnRequest observes a copied validated request or nil for an invalid
// envelope and may return a derived context for subsequent processing.
OnRequest func(context.Context, *Request) context.Context
// OnResponse observes a copied internal outcome, including notifications.
OnResponse func(context.Context, *Request, *Response)
}
Hooks observe the complete dispatcher lifecycle, including protocol errors that occur before a Handler or Middleware can run. A nil Request represents an unparseable or invalid request. Notifications provide an internal outcome to OnResponse even though that response is never placed on the wire.
type ID ¶
type ID struct {
// contains filtered or unexported fields
}
ID preserves the exact JSON representation of a string, number, or null ID. See https://www.jsonrpc.org/specification#request_object.
func StringID ¶
StringID constructs a string ID. Invalid UTF-8 is replaced according to encoding/json rules so correlation matches the transmitted value.
func (ID) Equal ¶
Equal reports whether IDs have the same kind and value. Mathematically equivalent numeric spellings compare equal.
func (ID) MarshalJSON ¶
MarshalJSON preserves the ID's original JSON spelling. A missing ID marshals as null when encoded outside a Request.
func (*ID) UnmarshalJSON ¶
UnmarshalJSON decodes a string, number, or null ID without numeric precision loss and rejects invalid UTF-8.
type IDGenerator ¶
type IDGenerator interface {
NextID() ID
}
IDGenerator supplies request IDs to a Client.
type Middleware ¶
Middleware wraps a Handler with cross-cutting behavior.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is a concurrency-safe method registry. Its zero value is ready for use. A Registry must not be copied after first use.
type Request ¶
type Request struct {
// JSONRPC must equal Version.
JSONRPC string `json:"jsonrpc"`
// Method is the case-sensitive method name.
Method string `json:"method"`
// Params contains an optional JSON object or array.
Params json.RawMessage `json:"params,omitempty"`
// ID identifies a request; it is missing for notifications.
ID ID `json:"-"`
// contains filtered or unexported fields
}
Request represents the protocol's request object. Params, when present, must follow https://www.jsonrpc.org/specification#parameter_structures. See https://www.jsonrpc.org/specification#request_object.
func NewNotification ¶
NewNotification constructs a validated request without an ID.
func NewRequest ¶
NewRequest constructs a validated request with an explicit non-missing ID.
func RequestFromContext ¶
RequestFromContext returns the validated request made available to the active middleware or handler.
func (Request) IsNotification ¶
IsNotification reports whether the request omitted its ID member as defined by https://www.jsonrpc.org/specification#notification.
func (Request) MarshalJSON ¶
MarshalJSON encodes a request while preserving notification ID omission.
func (*Request) UnmarshalJSON ¶
UnmarshalJSON decodes a request while preserving whether ID and method were present and rejecting ambiguous protocol members.
type Response ¶
type Response struct {
// JSONRPC must equal Version.
JSONRPC string `json:"jsonrpc"`
// Result contains the success value.
Result json.RawMessage `json:"result,omitempty"`
// Error contains the failure object.
Error *Error `json:"error,omitempty"`
// ID correlates the response with its request.
ID ID `json:"id"`
// contains filtered or unexported fields
}
Response represents the object defined by https://www.jsonrpc.org/specification#response_object.
func (Response) MarshalJSON ¶
MarshalJSON encodes exactly one result or error member.
func (*Response) UnmarshalJSON ¶
UnmarshalJSON decodes a response and records the presence of result, error, and ID members for later validation.
type TransportFunc ¶
TransportFunc adapts a function to Transport.