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.
Construct dispatchers, clients and HTTP adapters with their New functions; Registry and AtomicIDGenerator also have usable zero values. Dispatch is synchronous and propagates context but cannot preempt callbacks or infer retry safety. Concurrent use requires caller-owned callbacks, transports and result targets to satisfy their own synchronization and lifetime contracts.
Standalone envelope decoding and validation are separate steps. Dispatcher and Client provide byte/item bounds; direct decoders need caller-owned input bounds. Raw messages and copied IDs can share mutable storage. Public error messages/data cross the wire; retained local causes are not a redaction service. Specification decisions and pinned example provenance live under specification/ and docs/specification-decisions.md; no HTTP binding is implied by JSON-RPC 2.0.
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()
if err := 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
}); err != nil {
fmt.Println(err)
return
}
server := httptest.NewServer(jsonrpc.NewHTTPHandler(jsonrpc.NewDispatcher(registry)))
defer server.Close()
transport, err := jsonrpc.NewHTTPTransport(server.URL)
if err != nil {
fmt.Println(err)
return
}
result, err := jsonrpc.Call[int](context.Background(), jsonrpc.NewClient(transport), "add", []int{2, 3})
if err != nil {
fmt.Println(err)
return
}
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 WithMaxDispatchResponseBytes(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 identifies a missing transport or a failure returned by it. ErrTransport = errors.New("jsonrpc: transport error") // ErrInvalidResponse identifies malformed envelopes or failed result decoding. ErrInvalidResponse = errors.New("jsonrpc: invalid response") // ErrMismatchedID identifies a response ID not matching a pending request. ErrMismatchedID = errors.New("jsonrpc: mismatched response id") // ErrUnexpectedResponse identifies notification replies or excess batch reply members. ErrUnexpectedResponse = errors.New("jsonrpc: unexpected response") // ErrMissingResponse identifies an incomplete set of batch responses. ErrMissingResponse = errors.New("jsonrpc: missing batch response") // ErrDuplicateResponse identifies repeated response IDs within a batch. ErrDuplicateResponse = errors.New("jsonrpc: duplicate batch response") // ErrDuplicateRequestID identifies ambiguous generated request IDs before batch transport. ErrDuplicateRequestID = errors.New("jsonrpc: duplicate batch request id") // ErrEmptyBatch identifies a client batch with no calls. ErrEmptyBatch = errors.New("jsonrpc: empty client batch") // ErrClientBatchTooLarge identifies a call count above the client batch bound. ErrClientBatchTooLarge = errors.New("jsonrpc: client batch too large") // ErrClientResponseTooLarge identifies a reply above the client parsing byte bound. 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 identifies a non-200, non-204 response after bounded body acquisition. ErrHTTPStatus = errors.New("jsonrpc: unexpected HTTP status") // ErrHTTPContentType identifies an unsupported content type on an HTTP 200 reply. ErrHTTPContentType = errors.New("jsonrpc: invalid HTTP response content type") // ErrResponseTooLarge identifies an HTTP reply body above the acquisition limit. 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 identifies rpc. application registration or non-rpc. system registration. ErrInvalidMethodName = errors.New("jsonrpc: invalid method name") // ErrMethodAlreadyRegistered identifies an attempt to replace an existing registry name. ErrMethodAlreadyRegistered = errors.New("jsonrpc: method already registered") // ErrNilHandler identifies registration without a callable handler. ErrNilHandler = errors.New("jsonrpc: nil handler") )
Registration errors identify reserved method names, duplicate methods, and nil handlers.
Functions ¶
func Call ¶
Call sends one request through a constructed, non-nil client and decodes T. On failure the returned value may contain partial decoding; consult the error.
func IsJSONContentType ¶
IsJSONContentType reports whether value is application/json, application/json-rpc, or any successfully parsed media type ending in +json. Parameters are accepted; the suffix branch does not restrict the top-level type to application. JSONRPC-DEC-010 records the current binding policy.
Types ¶
type AtomicIDGenerator ¶
type AtomicIDGenerator struct {
// contains filtered or unexported fields
}
AtomicIDGenerator generates concurrency-safe numeric IDs by int64 increments. Its zero value starts at 1. It must not be copied after use; arithmetic wraps at int64 overflow, so it is not an unlimited monotonic or unique-ID guarantee.
func NewAtomicIDGenerator ¶
func NewAtomicIDGenerator(start int64) *AtomicIDGenerator
NewAtomicIDGenerator creates a generator whose first ID is start+1 using wrapping int64 arithmetic.
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 is a caller-owned json.Unmarshal destination for success, when non-nil.
Result any
// Notification omits the request ID and expects no response member.
Notification bool
// Error receives a per-call protocol failure and is reset as the batch is built.
// A nil Batch error does not mean every call succeeded; inspect each Error.
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. These fields and internal correlation state are mutated; do not share calls or result targets concurrently. Earlier members may be updated even if Batch later returns an 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. Construct it with NewClient; the zero value is not ready for calls. The client borrows collaborators, performs no automatic retries and does not close them.
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. Replies may arrive in any order. Valid per-call protocol failures populate BatchCall.Error without making Batch itself fail; envelope/transport/decoding failures return an error. Result mutation is not transactional and is not rolled back when a later reply fails validation.
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
calls := []*jsonrpc.BatchCall{
{Method: "double", Params: []int{1}, Result: &first},
{Method: "double", Params: []int{2}, Result: &second},
}
if err := client.Batch(context.Background(), calls...); err != nil {
fmt.Println(err)
return
}
for _, call := range calls {
if call.Error != nil {
fmt.Println(call.Error)
return
}
}
fmt.Println(first, second)
}
Output: 2 4
func (*Client) Call ¶
Call sends one request, validates and correlates its response, and decodes a successful result into result when result is non-nil. The target must be a suitable json.Unmarshal destination and is caller-owned; decoding errors may leave it partially updated. A valid peer failure is returned as *Error; other failures preserve their category/causes. The transport enforces cancellation; Client does not preflight ctx.Err or impose a separate deadline.
type ClientOption ¶
type ClientOption func(*Client)
ClientOption configures a Client during construction.
func WithIDGenerator ¶
func WithIDGenerator(generator IDGenerator) ClientOption
WithIDGenerator borrows a non-nil request ID generator; nil leaves the existing generator unchanged. Typed-nil implementations are not filtered and must not be supplied. It does not validate future IDs in advance.
func WithMaxClientBatchItems ¶ added in v1.1.0
func WithMaxClientBatchItems(limit int) ClientOption
WithMaxClientBatchItems changes the default limit of 1,024 calls sent and response members inspected for one client batch. Non-positive values leave the existing bound unchanged.
func WithMaxClientResponseBytes ¶
func WithMaxClientResponseBytes(limit int64) ClientOption
WithMaxClientResponseBytes changes the four-MiB parsing limit for positive values. Non-positive values leave it unchanged. Transport acquisition occurs before this check and needs its own bounds.
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. Use NewDispatcher; the zero value is not ready for dispatch. It borrows the registry and callback state, which must be concurrency-safe. Options are construction-only; do not mutate configuration after publication.
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 for valid notifications, even on handler failure. Invalid id-less envelopes still return null-ID errors (JSONRPC-DEC-001); explicit null IDs are requests (JSONRPC-DEC-002). ctx must be nonnil; cancellation is propagated to callbacks, not preflight admission or forced preemption.
Payload is borrowed for this synchronous call; returned bytes are caller-owned. Batches currently execute sequentially, but ordering is not a public guarantee (JSONRPC-DEC-006). Aggregate response overflow still runs remaining handlers and returns one null-ID internal-error member, so side effects may complete without correlated outcomes (JSONRPC-DEC-011); do not blindly retry.
Notification and batch behavior follow:
func (*Dispatcher) DispatchSingle ¶
DispatchSingle processes one non-batch JSON-RPC message and returns its typed response before final wire encoding. The boolean is false for valid notifications, which return a zero Response; batches return Invalid Request. ctx/payload ownership and cancellation match Dispatch. Result shaping still encodes to enforce the response-size bound; this is not a zero-encoding API. The returned Response can retain a handler-owned Error/cause, not a deep copy.
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 response fields are copied, but OnResponse's request/cause are borrowed and must be treated as read-only to avoid changing shared state or wire IDs.
func WithMaxBatchItems ¶
func WithMaxBatchItems(limit int) DispatcherOption
WithMaxBatchItems changes the dispatcher's default limit of 1,024 members. Non-positive values leave the existing bound unchanged.
func WithMaxDispatchBytes ¶
func WithMaxDispatchBytes(limit int64) DispatcherOption
WithMaxDispatchBytes changes the dispatcher's four-MiB payload limit. Non-positive values leave the existing bound unchanged.
func WithMaxDispatchResponseBytes ¶ added in v1.1.0
func WithMaxDispatchResponseBytes(limit int64) DispatcherOption
WithMaxDispatchResponseBytes changes the dispatcher's four-MiB encoded response limit. Values smaller than the minimal protocol error use that minimal size so a valid bounded failure can still be returned. Non-positive values leave the limit unchanged. Encoding can allocate before this check; callers must bound handler values and custom marshaling (JSONRPC-DEC-011).
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. Non-positive values leave the bound unchanged; raising it does not raise the standard JSON parser's own ceiling.
func WithMiddleware ¶
func WithMiddleware(middleware ...Middleware) DispatcherOption
WithMiddleware appends function values in outermost-to-innermost order when the option runs. Captured state is not cloned; nil entries are skipped at execution.
Example ¶
package main
import (
"context"
"encoding/json"
"fmt"
jsonrpc "github.com/faustbrian/go-jsonrpc"
)
func main() {
registry := jsonrpc.NewRegistry()
if err := registry.Register("ping", func(context.Context, json.RawMessage) (any, error) {
return "pong", nil
}); err != nil {
fmt.Println(err)
return
}
logging := func(next jsonrpc.Handler) jsonrpc.Handler {
return func(ctx context.Context, params json.RawMessage) (any, error) {
request, ok := jsonrpc.RequestFromContext(ctx)
if !ok {
return nil, jsonrpc.InternalError()
}
fmt.Println(request.Method)
return next(ctx, params)
}
}
dispatcher := jsonrpc.NewDispatcher(registry, jsonrpc.WithMiddleware(logging))
response, reply := dispatcher.DispatchSingle(context.Background(), []byte(`{"jsonrpc":"2.0","method":"ping","id":1}`))
if reply && response.Error != nil {
fmt.Println(response.Error)
}
}
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. Direct assignments borrow the
// supplied slice; use WithData to encode a new owned buffer.
Data json.RawMessage `json:"data,omitempty"`
// contains filtered or unexported fields
}
Error is a mutable JSON-RPC error object. Message and Data are public wire content, not sanitized secrets. WithCause retains an unredacted local error exposed by Unwrap but excluded from JSON serialization. Do not share mutable errors across concurrent calls; even copies share Data and the cause. 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, invalid UTF-8, malformed JSON and trailing data return InvalidParams. Exact top-level field names apply when T is a struct (including pointer forms); nested field case matching and custom unmarshaling retain encoding/json rules. Duplicate object names are rejected at every depth (JSONRPC-DEC-008). Direct callers must bound params before parsing. On error T may be partially decoded; use it only after checking the returned protocol error.
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. This constructor preserves code/message without enforcing that range or redacting them.
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 error fields, rejecting invalid UTF-8, duplicate envelope names and case variants of reserved names. Presence of code/message is checked later by Response.Validate; successful decoding alone is not validation. It clears old fields after preliminary decoding, but errors may leave partial receiver state. Use a separate value when rollback/alias isolation is needed.
type ErrorMapper ¶
ErrorMapper converts an ordinary application error into public JSON-RPC data. It runs synchronously, may be invoked concurrently and owns the public message/ data disclosure policy. Dispatcher contains its panic or invalid/nil result; a directly returned or wrapped *Error bypasses this mapper.
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. Construct it with NewHTTPHandler; the zero value is not usable. It borrows the dispatcher and owns no listener or server shutdown.
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 admits POST with an accepted JSON content type, then closes its bounded request body on every read/dispatch path. Earlier method/media rejection leaves body ownership to net/http or a direct caller. JSON-RPC replies use 200; valid notification-only calls use empty 204. Method, media, size and read failures use 405, 415, 413 and 400 (JSONRPC-DEC-009/010). Authentication, server deadlines and callback concurrency remain caller-owned; write/close errors are not returned.
type HTTPHandlerOption ¶
type HTTPHandlerOption func(*HTTPHandler)
HTTPHandlerOption configures an HTTPHandler during construction.
func WithMaxRequestBytes ¶
func WithMaxRequestBytes(limit int64) HTTPHandlerOption
WithMaxRequestBytes changes the four-MiB body limit for positive values; non-positive values leave it unchanged. Dispatcher bounds are independent.
type HTTPStatusError ¶
type HTTPStatusError struct {
// StatusCode is the peer's HTTP response status.
StatusCode int
// Body is an untrusted, control-character-sanitized preview bounded to four
// KiB. It is empty by default, and Error deliberately omits it.
Body string
}
HTTPStatusError reports a non-200 HTTP response. Body is empty by default and populated only when the transport enables a bounded diagnostic preview.
func (*HTTPStatusError) Error ¶
func (err *HTTPStatusError) Error() string
Error returns only the status code. Body is excluded because it is controlled by the remote peer and may contain secrets.
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. Construct it with NewHTTPTransport; the zero value is not usable. Concurrent RoundTrip calls are supported with a concurrency-safe borrowed client and construction-only options. It starts no owned background worker and does not close client idle connections.
func NewHTTPTransport ¶
func NewHTTPTransport(endpoint string, options ...HTTPTransportOption) (*HTTPTransport, error)
NewHTTPTransport validates an HTTP(S) endpoint without URL user information and constructs a transport. The default client has a 30-second timeout and does not follow redirects or use environment proxies. Nil options are ignored. HTTP(S), loopback/private destinations and query strings are allowed; endpoint validation is not SSRF protection. Callers own origin/DNS/TLS/egress policy.
func (*HTTPTransport) RoundTrip ¶
RoundTrip posts payload and returns a bounded JSON response. A 204 response returns nil without reading/validating a JSON body (JSONRPC-DEC-009); other non-200 statuses return *HTTPStatusError after bounded body acquisition. Size or read failures can precede status classification. A 200 response needs an accepted content type, but envelope validation belongs to Client. Payload is borrowed until return and returned body bytes are caller-owned. Every received body is closed. Context cancellation and the configured HTTP client bound network work; no retry or extra deadline is imposed here.
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, redirect, proxy, DNS, and dial policies remain caller-owned. The client is borrowed, not cloned or closed; nil leaves the existing client unchanged.
func WithHTTPDiagnosticPreviewBytes ¶ added in v1.1.0
func WithHTTPDiagnosticPreviewBytes(limit int64) HTTPTransportOption
WithHTTPDiagnosticPreviewBytes opts into retaining up to limit bytes of a non-success response body in HTTPStatusError.Body. Values above four KiB are clamped to four KiB. Non-positive values leave an existing preview setting unchanged. Control sanitation is not secret redaction; Error never prints Body.
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 four-MiB HTTP acquisition limit for positive values; non-positive values leave it unchanged. Client parsing has its own limit.
type Handler ¶
Handler implements one JSON-RPC method synchronously. Params is borrowed for the invocation; copy bytes before retaining or mutating them. Handlers own cooperative cancellation, authorization, replay safety and bounded returned values/custom marshaling. Concurrent dispatch may invoke a handler concurrently.
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.
// Params/ID raw bytes are copied for this callback. Nil or panic preserves ctx;
// any other returned context is used without enforcing the original deadline.
// Derive it from ctx to preserve cancellation and caller context values.
OnRequest func(context.Context, *Request) context.Context
// OnResponse observes copied response fields/raw buffers, including notification
// outcomes, before final wire encoding. Request is borrowed, not copied: treat
// it and its ID/Params storage as read-only. Mutating/re-decoding Request.ID can
// alter shared response ID bytes. A local Error cause also remains borrowed
// and may carry sensitive error or panic-stack data. Copy before retention.
// Observed outcomes may differ from final aggregate/encoding-limit fallbacks.
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. Callbacks run synchronously without registry locks and may run concurrently across dispatches. Panics are contained, not blocking or unbounded work.
type ID ¶
type ID struct {
// contains filtered or unexported fields
}
ID preserves string, number or null wire spelling separately from correlation identity. The zero value is missing, not explicit null. Copies share raw storage: re-decoding a copied ID can change an alias's wire spelling, so treat IDs borrowed from requests/context/hooks as read-only and decode into a fresh value when independent ownership is needed. See https://www.jsonrpc.org/specification#request_object.
func NullID ¶
func NullID() ID
NullID constructs explicit null, distinct from the missing zero-value ID. JSONRPC-DEC-002 treats a present null request ID as requiring a response.
func NumberID ¶
NumberID preserves value's wire spelling and exact-decimal correlation key. It does not validate that value is valid JSON numeric syntax. NewRequest rejects invalid IDs; direct users must supply valid numbers. JSONRPC-DEC-003 records equivalence without binary floating-point conversion or exponent expansion.
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 valid numeric spellings compare equal; strings and numbers never coerce to one another. Equal does not validate IDs.
func (ID) MarshalJSON ¶
MarshalJSON preserves the ID's original JSON spelling. A missing ID marshals as null when encoded outside a Request. Returned bytes are an independent copy; this method does not validate raw numeric syntax.
func (*ID) UnmarshalJSON ¶
UnmarshalJSON decodes a string, number, or null ID without numeric precision loss and rejects invalid UTF-8, non-ID types and trailing values. It copies input bytes but may reuse receiver storage shared by prior ID value copies. Standalone callers must bound input size; no dispatcher byte limit applies here.
type IDGenerator ¶
type IDGenerator interface {
// NextID returns a valid non-missing ID for correlation. Client calls may invoke
// it concurrently; a custom implementation must synchronize its own state.
// Duplicate generated IDs within a batch are rejected before transport.
NextID() ID
}
IDGenerator supplies request IDs to a Client.
type Middleware ¶
Middleware wraps a Handler with cross-cutting behavior. Its constructor runs for each method execution, outside registry locks, in reverse wrapping order. Both it and its returned handler must support the caller's concurrency; neither gets automatic timeouts or bounded recursion from Dispatcher.
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.
func (*Registry) Lookup ¶
Lookup returns the borrowed handler under name after releasing the registry read lock. The caller owns its invocation and any captured mutable state.
func (*Registry) Register ¶
Register adds handler under name. Names beginning with rpc. are reserved, and an existing name cannot be replaced. Empty application names are valid. The registry borrows the function/captured state; it does not run the handler under its lock or provide synchronization for that state.
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; literal assignments borrow
// the slice, while the JSON decoder and builders allocate their own bytes.
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. UnmarshalJSON records presence; Validate separately checks the envelope. Value copies share Params and ID raw storage; mutation requires caller ownership. See https://www.jsonrpc.org/specification#request_object.
func NewNotification ¶
NewNotification validates method/params as NewRequest does, omits the ID member and therefore cannot receive a correlated protocol response.
func NewRequest ¶
NewRequest validates a non-missing ID and rejects reserved rpc. application method names (empty names are permitted). Nil params omit the member; nonnil params must JSON-encode as an object or array. Encoding allocates owned Params; the supplied ID is a value copy sharing its raw storage. Explicit NullID is accepted as a request per JSONRPC-DEC-002, not treated as a notification.
func RequestFromContext ¶
RequestFromContext returns the validated request made available to the active middleware or handler. ctx must be nonnil. The returned value is a shallow copy sharing Params and ID storage; treat it as read-only and create independent copies before retention or mutation.
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 fields without calling Validate, preserving notification ID omission. Invalid raw Params/ID can produce an encoding error.
func (*Request) UnmarshalJSON ¶
UnmarshalJSON decodes a request while preserving whether ID and method were present and rejecting invalid UTF-8, duplicate envelope names and case variants of reserved names. Unknown extension names are allowed. Nested application data keeps ordinary JSON behavior unless DecodeParams is used. Call Validate after success; an error may leave the receiver partially changed.
func (Request) Validate ¶
Validate returns an InvalidRequest error when the request envelope violates JSON-RPC 2.0. This checks version, method presence and params shape, not reserved-method registration policy or validity of a programmatically supplied ID. NewRequest and UnmarshalJSON validate IDs at their respective boundaries.
type Response ¶
type Response struct {
// JSONRPC must equal Version.
JSONRPC string `json:"jsonrpc"`
// Result contains raw success JSON, including an explicit null. Literal
// assignments borrow the slice; mutating it requires caller ownership.
Result json.RawMessage `json:"result,omitempty"`
// Error contains the failure object; pointer copies retain that mutable 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. Result, Error.Data and ID raw buffers can be shared by copies. Hidden presence state is set by decoding/dispatch; exported fields in a literal alone do not satisfy Validate.
func (Response) MarshalJSON ¶
MarshalJSON selects error when Error is nonnil or decoded error presence is set; otherwise it encodes result, using null when Result is nil. It does not call Validate or preserve an invalid result/error conflict.
func (*Response) UnmarshalJSON ¶
UnmarshalJSON decodes a response and records the presence of result, error, and ID members for later validation. It rejects invalid UTF-8, duplicate/case variants of reserved envelope names and malformed field representations, but successful decoding does not guarantee a valid envelope. Errors may leave partial receiver state; Validate checks completeness and exclusivity.
type Transport ¶
type Transport interface {
// RoundTrip synchronously exchanges payload using the supplied context.
// Implementations own cancellation and acquisition bounds; Client checks reply
// size only after return. Treat payload as borrowed until return, and return
// reply bytes safe for the caller to inspect without concurrent mutation.
RoundTrip(context.Context, []byte) ([]byte, error)
}
Transport exchanges one complete JSON-RPC payload with a peer.
type TransportFunc ¶
TransportFunc adapts a function to Transport.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
semvercheck
command
Package main provides the semvercheck release-tag validation and bump command.
|
Package main provides the semvercheck release-tag validation and bump command. |
|
examples
|
|
|
client
command
Package main demonstrates a JSON-RPC call to the separately run example server.
|
Package main demonstrates a JSON-RPC call to the separately run example server. |
|
e2e
command
Package main demonstrates an owned in-process HTTP server and JSON-RPC client.
|
Package main demonstrates an owned in-process HTTP server and JSON-RPC client. |
|
server
command
Package main demonstrates a JSON-RPC HTTP server with caller-owned timeouts.
|
Package main demonstrates a JSON-RPC HTTP server with caller-owned timeouts. |
|
internal
|
|
|
semver
Package semver implements repository-private release-tag syntax and bump helpers.
|
Package semver implements repository-private release-tag syntax and bump helpers. |