Documentation
¶
Overview ¶
Package jsonrpc2 provides a robust and extensible implementation of the JSON-RPC 2.0 protocol specification.
Overview ¶
This library facilitates communication between clients and servers using JSON-RPC 2.0. It adheres strictly to the specification and is not compatible with JSON-RPC 1.0. The core design focuses on a clear client-server architecture. Bi-directional communication (server initiating requests to the client) is not currently supported.
Features ¶
- Transport agnostic, supports various underlying transports.
- Stream-based connections (net.Conn like TCP, Unix sockets) via Server.Serve and Client.
- Packet-based connections (net.PacketConn like UDP, Unix datagram) via Server.ServePacket and Client.
- HTTP(S) via HTTPHandler for servers and NewHTTPBridge for clients.
- Pluggable JSON Libraries: Replace the standard `encoding/json` by overriding package-level variables (Marshal, Unmarshal, NewJSONEncoder, NewJSONDecoder).
- Custom Encoders/Decoders: Implement the Encoder, Decoder, PacketEncoder, or PacketDecoder interfaces for fine-grained control over message processing and transport interaction.
- Middleware/Hooks: Use Binder for server lifecycle management and Callbacks for event handling (errors, panics).
- Method Routing: Use MethodMux to route incoming requests to specific handler functions based on the method name.
- Concurrency Control: Configure server-side concurrency using RPCServer options ([NoRoutines], [SerialBatch]).
- Context Propagation: Leverages Go's `context` package for cancellation and passing request-scoped values (CtxHTTPRequest, CtxNetConn, etc.).
Basic Usage ¶
Server (TCP Example) ¶
package main
import (
"context"
"errors"
"log"
"net"
"os"
"os/signal"
"syscall"
"github.com/rrb3942/jsonrpc2"
)
// Simple echo handler function
func echoHandler(ctx context.Context, req *jsonrpc2.Request) (any, error) {
// Access parameters via req.Params.Unmarshal(&yourStruct) or req.Params.RawMessage()
log.Printf("Received echo request with params: %s", string(req.Params.RawMessage()))
return req.Params.RawMessage(), nil // Echo back the parameters
}
func main() {
mux := jsonrpc2.NewMethodMux()
mux.RegisterFunc("echo", echoHandler) // Register the "echo" method
server := jsonrpc2.NewServer(mux)
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
log.Println("JSON-RPC server listening on :9090")
// Run the server until context is cancelled
// ListenAndServe handles listener creation and shutdown.
if err := server.ListenAndServe(ctx, "tcp::9090"); err != nil && !errors.Is(err, net.ErrClosed) {
log.Printf("Server error: %v", err)
}
log.Println("Server shut down gracefully")
}
Client (TCP Example) ¶
package main
import (
"context"
"log"
"time"
"github.com/rrb3942/jsonrpc2"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Dial establishes the connection and creates a client.
// It uses a ClientPool internally.
client, err := jsonrpc2.Dial(ctx, "tcp:localhost:9090")
if err != nil {
log.Fatalf("Failed to dial server: %v", err)
}
defer client.Close() // Important to close the client connection pool
// Optional: Set a default timeout for all calls via this client instance.
client.SetDefaultTimeout(5 * time.Second)
params := map[string]string{"message": "hello world"}
var result map[string]string // Variable to hold the result
// Call the "echo" method on the server
// BasicClient handles request ID generation.
// Pass params directly (must be marshalable to JSON object/array or nil).
response, err := client.Call(ctx, "echo", params)
if err != nil {
// This error could be a connection error, timeout, etc.
log.Fatalf("RPC transport/call failed: %v", err)
}
// Check if the response itself indicates a JSON-RPC error
if response.IsError() {
log.Printf("Server returned JSON-RPC error: Code=%d, Message=%s, Data=%v",
response.Error.Code, response.Error.Message, response.Error.Data.RawMessage())
return
}
// Process the successful result
// Use response.Result.Unmarshal(&yourStruct) or response.Result.RawMessage()
if err := response.Result.Unmarshal(&result); err != nil {
log.Fatalf("Failed to unmarshal result: %v", err)
}
log.Printf("Server responded successfully: %v", result)
}
Index ¶
- Constants
- Variables
- func BatchCorrelate(requests Batch[*Request], responses Batch[*Response], ...)
- type Batch
- type BatchBuildable
- type BatchBuilder
- type Batchable
- type Binder
- type Callbacks
- type Client
- func (c *Client) Call(ctx context.Context, method string, params any) (*Response, error)
- func (c *Client) Close()
- func (c *Client) NewNotificationBatch(size int) *BatchBuilder[*Notification]
- func (c *Client) NewRequestBatch(size int) *BatchBuilder[*Request]
- func (c *Client) Notify(ctx context.Context, method string, params any) error
- func (c *Client) SetDefaultTimeout(d time.Duration)
- type ClientPool
- func (cp *ClientPool) Call(ctx context.Context, req *Request) (resp *Response, err error)
- func (cp *ClientPool) CallBatch(ctx context.Context, req Batch[*Request]) (resp Batch[*Response], err error)
- func (cp *ClientPool) CallBatchWithTimeout(ctx context.Context, timeout time.Duration, r Batch[*Request]) (Batch[*Response], error)
- func (cp *ClientPool) CallRaw(ctx context.Context, req RawRequest) (resp *Response, err error)
- func (cp *ClientPool) CallRawWithTimeout(ctx context.Context, timeout time.Duration, r RawRequest) (*Response, error)
- func (cp *ClientPool) CallWithTimeout(ctx context.Context, timeout time.Duration, r *Request) (*Response, error)
- func (cp *ClientPool) Close()
- func (cp *ClientPool) Notify(ctx context.Context, notify *Notification) (err error)
- func (cp *ClientPool) NotifyBatch(ctx context.Context, notify Batch[*Notification]) (err error)
- func (cp *ClientPool) NotifyBatchWithTimeout(ctx context.Context, timeout time.Duration, n Batch[*Notification]) error
- func (cp *ClientPool) NotifyRaw(ctx context.Context, notify RawNotification) (err error)
- func (cp *ClientPool) NotifyRawWithTimeout(ctx context.Context, timeout time.Duration, n RawNotification) error
- func (cp *ClientPool) NotifyWithTimeout(ctx context.Context, timeout time.Duration, n *Notification) error
- func (cp *ClientPool) Reset()
- type ClientPoolConfig
- type ContextKey
- type Data
- type DeadlineReader
- type DeadlineWriter
- type Decoder
- type Encoder
- type Error
- type ErrorData
- type HTTPBridge
- type HTTPHandler
- type Handler
- type ID
- func (id *ID) Equal(t ID) bool
- func (id *ID) Int64() (int64, error)
- func (id *ID) IsNull() bool
- func (id *ID) IsZero() bool
- func (id *ID) MarshalJSON() ([]byte, error)
- func (id *ID) Number() (json.Number, bool)
- func (id *ID) String() (string, bool)
- func (id *ID) UnmarshalJSON(data []byte) error
- func (id *ID) Value() any
- type JSONDecoder
- type JSONEncoder
- type MethodMux
- func (mm *MethodMux) Delete(method string)
- func (mm *MethodMux) Handle(ctx context.Context, req *Request) (any, error)
- func (mm *MethodMux) Methods() []string
- func (mm *MethodMux) Register(method string, handler Handler) error
- func (mm *MethodMux) RegisterFunc(method string, f func(context.Context, *Request) (any, error)) error
- func (mm *MethodMux) Replace(method string, handler Handler)
- func (mm *MethodMux) ReplaceFunc(method string, f func(context.Context, *Request) (any, error))
- func (mm *MethodMux) SetDefault(handler Handler)
- func (mm *MethodMux) SetDefaultFunc(f func(context.Context, *Request) (any, error))
- type NewDecoderFunc
- type NewEncoderFunc
- type NewPacketDecoderFunc
- type NewPacketEncoderFunc
- type Notification
- type PacketConnDecoder
- type PacketConnEncoder
- type PacketDecoder
- type PacketEncoder
- type Params
- type RPCServer
- func NewPacketServer(d PacketDecoder, e PacketEncoder, handler Handler) *RPCServer
- func NewRPCServerFromPacket(rw net.PacketConn, handler Handler) *RPCServer
- func NewStreamServer(d Decoder, e Encoder, handler Handler) *RPCServer
- func NewStreamServerFromIO(rw io.ReadWriter, handler Handler) *RPCServer
- type RawNotification
- type RawRequest
- type RawResponse
- type Request
- type Response
- type Result
- type Server
- type StreamDecoder
- type StreamEncoder
- type TransportClient
- func (c *TransportClient) Call(ctx context.Context, r *Request) (*Response, error)
- func (c *TransportClient) CallBatch(ctx context.Context, r Batch[*Request]) (Batch[*Response], error)
- func (c *TransportClient) CallBatchWithTimeout(ctx context.Context, timeout time.Duration, r Batch[*Request]) (Batch[*Response], error)
- func (c *TransportClient) CallRaw(ctx context.Context, r RawRequest) (*Response, error)
- func (c *TransportClient) CallRawWithTimeout(ctx context.Context, timeout time.Duration, r RawRequest) (*Response, error)
- func (c *TransportClient) CallWithTimeout(ctx context.Context, timeout time.Duration, r *Request) (*Response, error)
- func (c *TransportClient) Close() error
- func (c *TransportClient) Notify(ctx context.Context, n *Notification) error
- func (c *TransportClient) NotifyBatch(ctx context.Context, n Batch[*Notification]) error
- func (c *TransportClient) NotifyBatchWithTimeout(ctx context.Context, timeout time.Duration, n Batch[*Notification]) error
- func (c *TransportClient) NotifyRaw(ctx context.Context, n RawNotification) error
- func (c *TransportClient) NotifyRawWithTimeout(ctx context.Context, timeout time.Duration, n RawNotification) error
- func (c *TransportClient) NotifyWithTimeout(ctx context.Context, timeout time.Duration, n *Notification) error
- type TypeHint
- type Version
Examples ¶
Constants ¶
const ( // DefaultPoolDialTimeout specifies the default timeout (30 seconds) used when establishing // a new client connection within the pool. See [ClientPoolConfig.DialTimeout]. DefaultPoolDialTimeout = 30 // DefaultPoolIdleTimeout specifies the default timeout (300 seconds or 5 minutes) // after which idle client connections in the pool are closed. See [ClientPoolConfig.IdleTimeout]. DefaultPoolIdleTimeout = 300 )
const ( // DefaultHTTPReadTimeout specifies the default [http.Server.ReadHeaderTimeout] (5 seconds). DefaultHTTPReadTimeout = 5 // DefaultHTTPShutdownTimeout specifies the default graceful shutdown timeout (30 seconds) for the HTTP server. DefaultHTTPShutdownTimeout = 30 )
const DefaultPacketBufferSize = 65507
DefaultPacketBufferSize specifies the default read buffer size (65507 bytes) used by PacketConnDecoder.DecodeFrom if no explicit limit is set via PacketConnDecoder.SetLimit. This size is chosen to accommodate the theoretical maximum UDP datagram size, although practical limits may be lower.
const ProtocolVersion = "2.0"
ProtocolVersion defines the required string value ("2.0") for the "jsonrpc" field in JSON-RPC 2.0 requests and responses.
Variables ¶
var ( ErrDecoding = errors.New("jsonrpc2: decoding error") ErrJSONTooLarge = errors.New("jsonrpc2: JSON payload larger than configured read limit") )
var ( ErrParseError = NewError(-32700, "Parse error") // Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text. ErrInvalidRequest = NewError(-32600, "Invalid Request") // The JSON sent is not a valid Request object. ErrMethodNotFound = NewError(-32601, "Method not found") // The method does not exist / is not available. ErrInvalidParams = NewError(-32602, "Invalid params") // Invalid method parameter(s). ErrInternalError = NewError(-32603, "Internal error") // Internal JSON-RPC error. ErrServerOverloaded = NewError(-32000, "Server overloaded") // Reserved for implementation-defined server-errors (-32000 to -32099). // ErrMessageTooLarge is sent best-effort before a stream closes. If excess request // data exceeds socket buffers, the client write may reset before reading the reply; // clients must still treat a plain connection error as a transport failure. An // oversized notification may leave this response for the next call on the // connection; use Call when exact attribution matters. ErrMessageTooLarge = NewError(-32001, "Message too large") )
Predefined JSON-RPC 2.0 errors as defined by the specification. See: https://www.jsonrpc.org/specification#error_object
var ( // ErrHTTPEmptyResponse indicates that the HTTP server returned a successful status code (2xx) // and an "application/json" Content-Type, but the response body was empty. ErrHTTPEmptyResponse = errors.New("jsonrpc2/httpbridge: empty JSON response body") // ErrHTTPResponse indicates a non-successful HTTP status code (not 2xx) was received. // The specific status is included in the error message generated by Decode. ErrHTTPResponse = errors.New("jsonrpc2/httpbridge: HTTP response error") // ErrHTTPNotJSON indicates that the HTTP server returned a successful status code (2xx) // but the Content-Type header was not "application/json". ErrHTTPNotJSON = errors.New("jsonrpc2/httpbridge: response Content-Type was not application/json") )
var ( // ErrResponseIsError is returned when trying to use an error response as a regular response. ErrResponseIsError = errors.New("response is an error") // ErrResponseNotError is returned when trying to use an error response as a regular response. ErrResponseNotError = errors.New("response is a response") )
var DefaultOnHandlerPanic = func(ctx context.Context, req *Request, rec any) { slog.ErrorContext(ctx, "Panic recovered in JSON-RPC handler", "method", req.Method, "id", req.ID.Value(), "params", string(req.Params.RawMessage()), "panic_value", rec) }
DefaultOnHandlerPanic provides a basic panic logging mechanism using the standard `slog` package. It is assigned to Callbacks.OnHandlerPanic by default when an RPCServer is created, ensuring that handler panics are logged even if no custom callbacks are configured.
var ErrEmptyData = errors.New("data is empty")
ErrEmptyData is returned by Data.Unmarshal when attempting to unmarshal from a Data instance that represents JSON `null` or was not populated.
var ErrEncoding = errors.New("jsonrpc2: encoding error")
ErrEncoding indicates a general error during the JSON encoding process. Specific underlying errors might be wrapped.
var ErrIDNotANumber = errors.New("ID is not a number")
ErrIDNotANumber is returned by ID.Int64 when the underlying ID value is not an int64 or a json.Number representing an integer.
var ErrInvalidParameters = errors.New("invalid parameters")
ErrInvalidParameters indicates that the provided parameters are invalid according to the JSON-RPC 2.0 specification (e.g., not an object or array).
var ErrInvalidParamsType = errors.New("jsonrpc2: params must marshal to a JSON object or array")
ErrInvalidParamsType indicates that the provided params argument to Client.Call or Client.Notify did not marshal into a JSON object or array as required by the JSON-RPC 2.0 specification.
var ErrMethodAlreadyExists = errors.New("method already exists in mux")
ErrMethodAlreadyExists is returned by MethodMux.Register and MethodMux.RegisterFunc when attempting to register a handler for a method name that is already registered.
var ErrNotRawMessage = errors.New("value is it not a raw message")
ErrNotRawMessage indicates that an operation expected the internal value to be a json.RawMessage, but it was not.
var ErrRetriesExceeded = errors.New("retries exceeded")
ErrRetriesExceeded is returned by ClientPool methods (Call*, Notify*) when an operation fails repeatedly due to retryable errors (like network issues) and the configured number of retries (ClientPoolConfig.Retries) is exhausted. The original error that caused the final failure is joined with this error.
var ErrUnknownScheme = errors.New("jsonrpc2: unknown scheme in uri")
ErrUnknownScheme indicates that the URI provided to Server.ListenAndServe has a scheme that is not supported (e.g., "ftp").
var ErrWrongProtocolVersion = errors.New("wrong protocol version for jsonrpc2")
ErrWrongProtocolVersion is returned by Version.UnmarshalJSON when the "jsonrpc" field is present but does not contain the exact string "2.0".
var Marshal = json.Marshal
Marshal defines the function used for marshaling Go types into JSON []byte. By default, it uses encoding/json.Marshal. Applications can replace this variable *at startup* with a different marshaling function, for example, from a third-party JSON library like `github.com/bytedance/sonic`.
The replacement function must have the same signature as `json.Marshal`. Ensure the replacement is compatible with the JSON-RPC 2.0 specification and the expectations of the Encoder and PacketEncoder interfaces used within this package (e.g., handling of standard types, `json.Marshaler`).
Example (using sonic):
import "github.com/bytedance/sonic"
func init() {
jsonrpc2.Marshal = sonic.ConfigDefault.Marshal
}
var NewJSONDecoder = func(r io.Reader) JSONDecoder { return json.NewDecoder(r) }
NewJSONDecoder defines the function used to create new JSONDecoder instances. By default, it returns a standard encoding/json.Decoder. Applications can replace this variable *at startup* to use a custom decoder implementation.
The returned JSONDecoder must be compatible with the usage within the default Decoder and PacketDecoder implementations. Ensure it handles JSON-RPC structures correctly (e.g., reading distinct objects/arrays).
Example (using sonic):
import (
"io"
"github.com/bytedance/sonic"
"github.com/rrb3942/jsonrpc2"
)
func init() {
jsonrpc2.NewJSONDecoder = func(r io.Reader) jsonrpc2.JSONDecoder { return sonic.ConfigDefault.NewDecoder(r) }
}
var NewJSONEncoder = func(w io.Writer) JSONEncoder { return json.NewEncoder(w) }
NewJSONEncoder defines the function used to create new JSONEncoder instances. By default, it returns a standard encoding/json.Encoder. Applications can replace this variable *at startup* to use a custom encoder implementation, perhaps one that wraps a different JSON library or adds custom behavior.
The returned JSONEncoder must be compatible with the usage within the default Encoder and PacketEncoder implementations.
Example (using sonic):
import (
"io"
"github.com/bytedance/sonic"
"github.com/rrb3942/jsonrpc2"
)
func init() {
jsonrpc2.NewJSONEncoder = func(w io.Writer) jsonrpc2.JSONEncoder { return sonic.ConfigDefault.NewEncoder(w) }
}
var Unmarshal = json.Unmarshal
Unmarshal defines the function used for unmarshalling JSON []byte into Go types. By default, it uses encoding/json.Unmarshal. Applications can replace this variable *at startup* with a different unmarshalling function.
The replacement function must have the same signature as `json.Unmarshal`. Ensure the replacement is compatible with the JSON-RPC 2.0 specification and the expectations of the Decoder and PacketDecoder interfaces (e.g., handling of standard types, `json.Unmarshaler`, `json.RawMessage`).
Example (using sonic):
import "github.com/bytedance/sonic"
func init() {
jsonrpc2.Unmarshal = sonic.ConfigDefault.Unmarshal
}
Functions ¶
func BatchCorrelate ¶
func BatchCorrelate(requests Batch[*Request], responses Batch[*Response], correlated func(req *Request, res *Response) (cont bool))
BatchCorrelate matches items between a request batch and a response batch based on their IDs. For each request, it attempts to find a corresponding response with the same ID.
The `correlated` function is called for each potential match:
- If a request has a matching response, `correlated(request, response)` is called.
- If a request has no matching response, `correlated(request, nil)` is called.
- If a response has no matching request, `correlated(nil, response)` is called.
The `correlated` function should return `true` to continue processing the batches, or `false` to stop correlation immediately.
Note: This function assumes IDs within each batch are unique. If duplicate IDs exist, the behavior is based on the first match found by the `Get` method. It does not handle one-to-many correlations.
Example:
reqBatch := jsonrpc2.NewBatch[*jsonrpc2.Request](0)
reqBatch.Add(jsonrpc2.NewRequest(int64(1), "add") // ID 1
reqBatch.Add(jsonrpc2.NewRequest(int64(2), "notify") // ID 2 (no response expected)
reqBatch.Add(jsonrpc2.NewNotification("log").AsRequest()) // No ID
resBatch := jsonrpc2.NewBatch[*jsonrpc2.Response](0)
resBatch.Add(jsonrpc2.NewResponseWithResult(int64(1), 3)) // ID 1
resBatch.Add(jsonrpc2.NewResponseWithError(int64(3), jsonrpc2.ErrMethodNotFound)) // ID 3 (unmatched)
jsonrpc2.BatchCorrelate(reqBatch, resBatch, func(req *jsonrpc2.Request, res *jsonrpc2.Response) bool {
if req != nil && res != nil {
fmt.Printf("Matched Request ID %v with Response ID %v\n", req.ID.Value(), res.ID.Value())
} else if req != nil {
fmt.Printf("Unmatched Request ID %v\n", req.ID.Value())
} else if res != nil {
fmt.Printf("Unmatched Response ID %v\n", res.ID.Value())
}
return true // Continue processing
})
// Output (order may vary slightly for unmatched responses):
// Matched Request ID 1 with Response ID 1
// Unmatched Request ID 2
// Unmatched Request ID <nil> (For the notification)
// Unmatched Response ID 3
Types ¶
type Batch ¶
type Batch[B Batchable] []B
Batch represents a collection of JSON-RPC 2.0 messages (*Request, *Notification, or *Response) intended to be processed together as defined by the specification. It provides methods for managing and querying the collection based on message IDs.
See: https://www.jsonrpc.org/specification#batch
func NewBatch ¶
NewBatch creates a new, empty Batch with an initial capacity specified by `size`. Use this constructor to efficiently preallocate space if the approximate number of items is known.
Example:
// Create a batch for requests, expecting around 10 requests. requestBatch := jsonrpc2.NewBatch[*jsonrpc2.Request](10)
func (*Batch[B]) Add ¶
func (b *Batch[B]) Add(v ...B)
Add appends one or more items (`v`) to the Batch `b`.
Example:
batch := jsonrpc2.NewBatch[*jsonrpc2.Request](0)
req1 := jsonrpc2.NewRequest(int64(1), "method1")
req2 := jsonrpc2.NewRequest("abc", "method2")
batch.Add(req1, req2)
fmt.Println(len(batch)) // Output: 2
func (*Batch[B]) Contains ¶
Contains checks if the batch includes an element with the specified ID. It returns `true` if a match is found, `false` otherwise. Zero-value IDs are not searchable and will always return `false`.
Example:
batch := jsonrpc2.NewBatch[*jsonrpc2.Request](0) batch.Add(jsonrpc2.NewRequest(int64(1), "method") idToFind := jsonrpc2.NewID(int64(1)) fmt.Println(batch.Contains(idToFind)) // Output: true fmt.Println(batch.Contains(jsonrpc2.NewID(int64(2)))) // Output: false
func (*Batch[B]) Delete ¶
Delete removes the first element found in the batch that matches the given ID. It returns the deleted element and `true` if an element was deleted, or nil and `false` if no matching element was found, or if the `id` is zero. The remaining elements are shifted to fill the gap, maintaining order.
Example:
batch := jsonrpc2.NewBatch[*jsonrpc2.Request](0) req1 := jsonrpc2.NewRequest(int64(1), "method") req2 := jsonrpc2.NewRequest(int64(2), "method") batch.Add(req1, req2) fmt.Println(len(batch)) // Output: 2 deleted, ok := batch.Delete(jsonrpc2.NewID(int64(1))) fmt.Println(ok, deleted.ID.Value()) // Output: true 1 fmt.Println(len(batch)) // Output: 1 fmt.Println(batch[0].ID.Value()) // Output: 2
func (*Batch[B]) Get ¶
Get retrieves the first element from the batch that matches the given ID. It returns the element and `true` if found, or nil and `false` if not found, or if the `id` is zero.
Example:
batch := jsonrpc2.NewBatch[*jsonrpc2.Request](0)
req1 := jsonrpc2.NewRequest("method", 1, nil)
batch.Add(req1)
foundReq, ok := batch.Get(jsonrpc2.NewID(int64(1)))
fmt.Println(ok, foundReq.Method) // Output: true method
_, ok = batch.Get(jsonrpc2.NewID(int64(2)))
fmt.Println(ok) // Output: false
func (*Batch[B]) Grow ¶
Grow increases the batch's capacity, if necessary, to guarantee space for another `n` elements without further reallocation. See slices.Grow for details.
Example:
batch := jsonrpc2.NewBatch[*jsonrpc2.Response](2) fmt.Println(cap(batch)) // Output: 2 batch.Grow(5) fmt.Println(cap(batch)) // Output: >= 5
func (*Batch[B]) Index ¶
Index returns the index of the first element in the batch that matches the given ID. If no element matches, or if the provided `id` is a zero-value ID, it returns -1.
Example:
batch := jsonrpc2.NewBatch[*jsonrpc2.Request](0)
req1 := jsonrpc2.NewRequest(int64(1), "method")
req2 := jsonrpc2.NewRequest("abc", "method")
batch.Add(req1, req2)
fmt.Println(batch.Index(jsonrpc2.NewID(int64(1)))) // Output: 0
fmt.Println(batch.Index(jsonrpc2.NewID("abc"))) // Output: 1
fmt.Println(batch.Index(jsonrpc2.NewID(int64(2)))) // Output: -1
type BatchBuildable ¶
type BatchBuildable interface {
*Request | *Notification // The batch can hold either requests or notifications, but not both.
// contains filtered or unexported methods
}
BatchBuildable defines the types supported within a BatchBuilder: either *Request or *Notification.
type BatchBuilder ¶
type BatchBuilder[B BatchBuildable] struct { Batch[B] // The embedded batch being built. // contains filtered or unexported fields }
BatchBuilder simplifies the creation of JSON-RPC 2.0 batches (*Request or *Notification). It handles ID generation for requests and parameter preparation.
**Important:** Obtain a BatchBuilder only through [Client.NewBatchRequest] or [Client.NewBatchNotify]. Do not create instances directly.
The builder embeds a Batch of the specified type (*Request or *Notification). This underlying batch can be accessed directly (e.g., `builder.Batch`) if needed, for example, to correlate requests with responses using BatchCorrelate.
A BatchBuilder can be reused after calling BatchBuilder.Call and then calling its `Reset()` method (e.g., `builder.Reset()`).
Example (Request Batch):
// Assume 'client' is an initialized *jsonrpc2.Client
reqBuilder := client.NewBatchRequest()
err := reqBuilder.Add("add", []int{1, 2}) // Adds a request with auto-generated ID
if err != nil { /* handle error */ }
err = reqBuilder.Add("subtract", map[string]int{"minuend": 10, "subtrahend": 5})
if err != nil { /* handle error */ }
// Send the batch
resBatch, err := reqBuilder.Call(context.Background())
if err != nil { /* handle error */ }
// Correlate requests and responses (accessing the embedded Batch)
jsonrpc2.BatchCorrelate(reqBuilder.Batch, resBatch, func(req *jsonrpc2.Request, res *jsonrpc2.Response) bool {
if req != nil && res != nil && res.Error.IsZero() {
fmt.Printf("Request %v (%s) succeeded.\n", req.ID.Value(), req.Method)
} else if req != nil && res != nil && !res.Error.IsZero() {
fmt.Printf("Request %v (%s) failed: %s\n", req.ID.Value(), req.Method, res.Error.Message)
} else if req != nil {
fmt.Printf("Request %v (%s) received no response.\n", req.ID.Value(), req.Method)
}
return true // Continue correlation
})
// Reuse the builder
reqBuilder.Reset()
Example (Notification Batch):
notifyBuilder := client.NewBatchNotify()
err = notifyBuilder.Add("log", "System started")
if err != nil { /* handle error */ }
err = notifyBuilder.Add("ping", nil)
if err != nil { /* handle error */ }
// Send the notifications (no responses expected)
_, err = notifyBuilder.Call(context.Background()) // Response batch is always nil for notifications
if err != nil { /* handle error */ }
// Reuse the builder
notifyBuilder.Reset()
func (*BatchBuilder[B]) Add ¶
func (b *BatchBuilder[B]) Add(method string, params any) error
Add creates a new JSON-RPC message (*Request or *Notification) with the given method and parameters, and adds it to the batch.
For request batches ([Client.NewBatchRequest]), it automatically assigns a unique ID. For notification batches ([Client.NewBatchNotify]), no ID is assigned.
The `params` argument is automatically prepared for transmission. Returns an error if parameter preparation fails (e.g., due to marshalling issues).
func (*BatchBuilder[B]) Call ¶
Call sends the batch using the associated Client.
The behavior depends on the type of batch builder:
- Request Batch ([Client.NewBatchRequest]): Sends the requests and attempts to receive corresponding responses. Returns the [Batch[*Response]] and any transport/processing error.
- Notification Batch ([Client.NewBatchNotify]): Sends the notifications. The returned [Batch[*Response]] will always be `nil`. Returns any transport/processing error.
After calling Call, the builder still contains the sent batch items. Use its `Reset()` method to clear it for reuse.
type Batchable ¶
type Batchable interface {
*Request | *Notification | *Response
// contains filtered or unexported methods
}
Batchable represents types that can be used in a batch.
type Binder ¶
type Binder interface {
// Bind is called before the associated RPCServer starts its Run loop.
// It receives the server's context, the RPCServer instance, and a function
// to cancel the server's startup and run.
Bind(ctx context.Context, rpc *RPCServer, stop context.CancelCauseFunc)
}
Binder provides a mechanism to configure or inspect an RPCServer instance just before its Run loop starts processing requests. This is useful for setting server options, callbacks, or performing context-based setup for individual connections or requests.
When used with a Server, the Bind method is called once for each new network connection accepted by the server. The provided context typically contains the net.Conn via CtxNetConn.
When used with an HTTPHandler, the Bind method is called once per incoming HTTP request. The provided context typically contains the *http.Request via CtxHTTPRequest.
The `stop` function (a context.CancelCauseFunc) can be called within Bind to prematurely terminate the RPCServer instance before it starts processing, for example, based on information derived from the context (like authentication failure).
When used to setup custom Callbacks or Handler the `stop` function may also be used to stop the RPCServer on specific error, or other conditions.
Example Use Case: Setting a custom OnError callback based on request headers.
func myBinderFunc(ctx context.Context, rpc *jsonrpc2.RPCServer, stop context.CancelCauseFunc) {
if req, ok := ctx.Value(jsonrpc2.CtxHTTPRequest).(*http.Request); ok {
if req.Header.Get("X-Debug-Mode") == "true" {
rpc.Callbacks.OnEncodingError = func(ctx context.Context, value any, err error) {
defer stop("DEBUG STOP")
log.Printf("DEBUG: Encoding error for value %v: %v", value, err)
}
}
}
// Optionally, call stop(errors.New("auth failed")) to prevent Run from starting.
}
server := jsonrpc2.NewServer(myHandler)
server.Binder = jsonrpc2.NewFuncBinder(myBinderFunc)
func NewFuncBinder ¶
NewFuncBinder wraps a function `f` to create a Binder. This allows using simple functions as Binders without needing to define a struct type.
Example:
logBinder := jsonrpc2.NewFuncBinder(func(ctx context.Context, rpc *jsonrpc2.RPCServer, stop context.CancelCauseFunc) {
log.Printf("New RPCServer instance starting for context: %v", ctx)
// Example: Add a simple exit logger
rpc.Callbacks.OnExit = func(ctx context.Context, err error) {
log.Printf("RPCServer exited with error: %v", err)
}
})
server := jsonrpc2.NewServer(myHandler)
server.Binder = logBinder // Assign the binder to the server
type Callbacks ¶
type Callbacks struct {
// OnExit is called when the [RPCServer.Run] method is about to return.
// The `err` parameter indicates the reason for exiting.
// On graceful shutdown the error will usually include [context.Canceled] and/or [io.EOF],
// depending on the underlying stream/connection behaviour.
OnExit func(ctx context.Context, err error)
// OnDecodingError is called when the server fails to decode an incoming message
// into a valid JSON-RPC Request or Batch structure.
// The `raw` parameter contains the raw JSON message that failed decoding (it might
// be incomplete or nil if the error occurred before reading any data).
// The `err` parameter provides details about the decoding failure (e.g.,
// JSON syntax error, invalid structure).
OnDecodingError func(ctx context.Context, raw json.RawMessage, err error)
// OnEncodingError is called when the server fails to encode a response object
// before sending it back to the client.
// The `value` parameter is the response object ([*Response] or `[]any` for batches)
// that failed to encode.
// The `err` parameter provides details about the encoding failure. This might
// indicate issues like unsupported types within the response data.
OnEncodingError func(ctx context.Context, value any, err error)
// OnHandlerPanic is called when a panic occurs within the user-provided [Handler]
// during request processing. The panic is recovered by the server.
// The `req` parameter is the [*Request] that caused the panic.
// The `rec` parameter is the value recovered from the panic.
// The default behavior is handled by [DefaultOnHandlerPanic]. Assigning a custom
// function here overrides the default.
OnHandlerPanic func(ctx context.Context, req *Request, rec any)
}
Callbacks defines a set of functions that can be registered with an RPCServer to be executed upon specific events during the server's lifecycle. This allows for custom logging, error handling, or other actions.
Callbacks are typically assigned to an RPCServer instance *before* its RPCServer.Run method is called, often using a Binder. Callbacks should be safe for concurrent use if the server processes requests concurrently (i.e., RPCServer.NoRoutines is false).
Callbacks should *not* modify the RPCServer state directly.
Example (using a Binder to set callbacks):
func setupCallbacks(ctx context.Context, rpc *jsonrpc2.RPCServer, stop context.CancelCauseFunc) {
rpc.Callbacks.OnExit = func(ctx context.Context, err error) {
slog.InfoContext(ctx, "RPCServer exiting", "error", err)
}
rpc.Callbacks.OnHandlerPanic = func(ctx context.Context, req *jsonrpc2.Request, rec any) {
slog.ErrorContext(ctx, "Custom panic handler", "method", req.Method, "panic", rec)
// Maybe trigger monitoring alert here
}
// Assign other callbacks as needed...
}
server := jsonrpc2.NewServer(myHandler) // Assuming myHandler is defined
server.Binder = jsonrpc2.NewFuncBinder(setupCallbacks)
// Now run the server... server.ListenAndServe(...)
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is goroutine-safe.
func Dial ¶
Dial is a convenience function that establishes a connection to the destination URI and returns a *Client ready for making simplified RPC calls.
It internally creates a ClientPool using default settings, but explicitly sets AcquireOnCreate=true to ensure the connection is established and validated immediately during the dial process. The underlying connection management and retries are handled by the pool.
Use DialWithConfig for more control over pool settings like timeouts and size. See DialTransport for details on supported URI schemes.
Example:
bc, err := jsonrpc2.Dial(context.Background(), "tcp:localhost:9090")
if err != nil {
log.Fatal(err)
}
defer bc.Close()
bc.SetDefaultTimeout(5 * time.Second) // Optional: Set default timeout
var result string
response, err = bc.Call(context.Background(), "echo", []string{"hello"}))
// ... handle result/error ...
func DialWithConfig ¶
func DialWithConfig(ctx context.Context, config ClientPoolConfig) (*Client, error)
DialWithConfig establishes a connection using a provided ClientPoolConfig and returns a *Client ready for making simplified RPC calls.
It's similar to Dial but allows customization of the underlying connection pool's behavior (e.g., timeouts, size, retries, AcquireOnCreate) via the `config` parameter. The `URI` field within the provided `config` specifies the destination address.
See DialTransport for details on supported URI schemes specified in `config.URI`.
Example:
// Example: Dial with custom settings, deferring initial connection.
customConfig := jsonrpc2.ClientPoolConfig{
URI: "tcp:localhost:9090",
MaxSize: 5,
IdleTimeout: 2 * time.Minute,
Retries: 3,
DialTimeout: 10 * time.Second,
AcquireOnCreate: false, // Defer connection until first call
}
bc, err := jsonrpc2.DialWithConfig(context.Background(), customConfig)
if err != nil {
log.Fatal(err)
}
defer bc.Close()
var result string
response, err = bc.Call(context.Background(), "echo", []string{"hello"})
// ... handle result/error ...
func (*Client) Call ¶
Call sends a JSON-RPC request for the given method with the provided parameters. It automatically assigns a request ID and waits for the server's response. If a default timeout is set via SetDefaultTimeout, it will be applied to the call. The `params` argument can be any Go type that marshals to a JSON object or array, a prepoulated Params, or nil to omit parameters. If `params` marshals to anything else, an error wrapping ErrInvalidParamsType is returned. Returns the server's *Response or an error if the call fails (including potential retries managed by the underlying pool).
func (*Client) Close ¶
func (c *Client) Close()
Close closes the underlying connection pool. Calls to the Client should not be made after Close has been called.
func (*Client) NewNotificationBatch ¶
func (c *Client) NewNotificationBatch(size int) *BatchBuilder[*Notification]
NewNotificationBatch creates a new BatchBuilder for constructing a batch of JSON-RPC notifications. Notifications added via *BatchBuilder.Add will not have IDs. The `size` parameter provides a hint for the initial capacity of the underlying batch slice. The batch is sent using *BatchBuilder.Call.
func (*Client) NewRequestBatch ¶
func (c *Client) NewRequestBatch(size int) *BatchBuilder[*Request]
NewRequestBatch creates a new BatchBuilder for constructing a batch of JSON-RPC requests. Requests added via *BatchBuilder.Add will automatically receive unique IDs. The `size` parameter provides a hint for the initial capacity of the underlying batch slice. The batch is sent using *BatchBuilder.Call.
func (*Client) Notify ¶
Notify sends a JSON-RPC notification for the given method with the provided parameters. It does not wait for a server response. If a default timeout is set via SetDefaultTimeout, it will be applied to the notification attempt. The `params` argument can be any Go type that marshals to a JSON object or array, a prepoulated Params, or nil to omit parameters. If `params` marshals to anything else, an error wrapping ErrInvalidParamsType is returned. Returns an error only if sending the notification fails (including potential retries).
func (*Client) SetDefaultTimeout ¶
SetDefaultTimeout sets a default timeout duration for all subsequent Call and Notify operations made through this Client. If the duration `d` is greater than zero, a context with this timeout will be derived from the context passed to Call/Notify. If `d` is zero or negative, no default timeout is applied, and the original context's deadline (if any) is used.
type ClientPool ¶
type ClientPool struct {
// contains filtered or unexported fields
}
ClientPool manages a pool of reusable *TransportClient connections to a JSON-RPC server. It allows multiple goroutines to make concurrent RPC calls efficiently by reusing established connections. It also handles automatic retries for certain types of errors and manages the lifecycle (creation, closing) of client connections.
Use NewClientPool or NewClientPoolWithDialer to create instances.
func NewClientPool ¶
func NewClientPool(nctx context.Context, config ClientPoolConfig) (*ClientPool, error)
NewClientPool creates a new ClientPool using the default DialTransport function. It connects to the server specified in ClientPoolConfig.URI.
If ClientPoolConfig.AcquireOnCreate is true, it attempts to establish an initial connection and returns an error if it fails.
Example:
config := jsonrpc2.ClientPoolConfig{
URI: "tcp:localhost:5000",
MaxSize: 10,
IdleTimeout: 5 * time.Minute,
Retries: 2, // Initial attempt + 2 retries = 3 total attempts
}
pool, err := jsonrpc2.NewClientPool(context.Background(), config)
if err != nil {
log.Fatalf("Failed to create client pool: %v", err)
}
defer pool.Close()
// Use pool.Call(), pool.Notify(), etc.
func NewClientPoolWithDialer ¶
func NewClientPoolWithDialer(nctx context.Context, config ClientPoolConfig, dialFunc func(ctx context.Context, uri string) (*TransportClient, error)) (*ClientPool, error)
NewClientPoolWithDialer creates a new ClientPool using a custom dialer function. This allows using non-standard transports or custom connection logic. The `dialFunc` should establish a connection to the server specified by `config.URI` and return a *TransportClient.
If ClientPoolConfig.AcquireOnCreate is true, it attempts to establish an initial connection using the provided `dialFunc` and returns an error if it fails.
Example (using a custom dialer for TLS with specific config):
customTLSConfig := &tls.Config{ /* ... */ }
customDialer := func(ctx context.Context, uri string) (*jsonrpc2.Client, error) {
// Example: Parse URI, dial with custom TLS config
// urlInfo, _ := url.Parse(uri) ... addr := urlInfo.Host ...
addr := "secure.example.com:443" // Simplified for example
dialer := &tls.Dialer{Config: customTLSConfig}
conn, err := dialer.DialContext(ctx, "tcp", addr)
if err != nil {
return nil, err
}
return jsonrpc2.NewClientIO(conn), nil
}
config := jsonrpc2.ClientPoolConfig{ URI: "tls://secure.example.com:443", /* ... */ }
pool, err := jsonrpc2.NewClientPoolWithDialer(context.Background(), config, customDialer)
// ... handle error and use pool ...
func (*ClientPool) Call ¶
Call acquires a client connection from the pool, sends the request using Client.Call, and returns the response. If a retryable error occurs (e.g., network issue), it automatically retries the call with a new connection up to the configured number of retries (ClientPoolConfig.Retries). If retries are exhausted, it returns ErrRetriesExceeded joined with the last error encountered.
Example:
req := jsonrpc2.NewRequest(int64(1), "myMethod")
resp, err := pool.Call(context.Background(), req)
if err != nil {
if errors.Is(err, jsonrpc2.ErrRetriesExceeded) {
log.Printf("Call failed after multiple retries: %v", err)
} else {
log.Printf("Call failed: %v", err)
}
return
}
// Process successful response...
func (*ClientPool) CallBatch ¶
func (cp *ClientPool) CallBatch(ctx context.Context, req Batch[*Request]) (resp Batch[*Response], err error)
CallBatch acquires a client connection from the pool, sends the batch request using [Client.CallBatch], and returns the batch response. Handles retries similarly to ClientPool.Call. Returns ErrRetriesExceeded if retries are exhausted.
func (*ClientPool) CallBatchWithTimeout ¶
func (cp *ClientPool) CallBatchWithTimeout(ctx context.Context, timeout time.Duration, r Batch[*Request]) (Batch[*Response], error)
CallBatchWithTimeout is a convenience method that calls ClientPool.CallBatch with a derived context that includes the specified `timeout`.
func (*ClientPool) CallRaw ¶
func (cp *ClientPool) CallRaw(ctx context.Context, req RawRequest) (resp *Response, err error)
CallRaw acquires a client connection from the pool, sends the raw request using [Client.CallRaw], and returns the response. Handles retries similarly to ClientPool.Call. Returns ErrRetriesExceeded if retries are exhausted.
func (*ClientPool) CallRawWithTimeout ¶
func (cp *ClientPool) CallRawWithTimeout(ctx context.Context, timeout time.Duration, r RawRequest) (*Response, error)
CallRawWithTimeout is a convenience method that calls ClientPool.CallRaw with a derived context that includes the specified `timeout`.
func (*ClientPool) CallWithTimeout ¶
func (cp *ClientPool) CallWithTimeout(ctx context.Context, timeout time.Duration, r *Request) (*Response, error)
CallWithTimeout is a convenience method that calls ClientPool.Call with a derived context that includes the specified `timeout`.
func (*ClientPool) Close ¶
func (cp *ClientPool) Close()
Close gracefully shuts down the client pool. It stops the idle connection cleanup timer, closes the underlying pool, closes all idle connections, and waits for any acquired connections to be released before closing them.
After Close returns, the pool should not be used. Calling methods on a closed pool will likely result in errors. It is safe to call Close multiple times.
func (*ClientPool) Notify ¶
func (cp *ClientPool) Notify(ctx context.Context, notify *Notification) (err error)
Notify acquires a client connection from the pool and sends the notification using Client.Notify. Since notifications don't receive responses, this method only returns errors related to acquiring a connection or sending the notification. Handles retries similarly to ClientPool.Call. Returns ErrRetriesExceeded if retries are exhausted.
Example:
notif := jsonrpc2.NewNotificationWithParams("logEvent", NewParamsObject(map[string]any{"level": "warn", "msg": "Disk space low"}))
err := pool.Notify(context.Background(), notif)
if err != nil {
log.Printf("Notify failed: %v", err)
}
An oversized notification may cause the next call on its connection to receive the server’s message-too-large error; use Call when exact attribution matters.
func (*ClientPool) NotifyBatch ¶
func (cp *ClientPool) NotifyBatch(ctx context.Context, notify Batch[*Notification]) (err error)
NotifyBatch acquires a client connection from the pool and sends the batch notification using [Client.NotifyBatch]. Handles retries similarly to ClientPool.Notify. Returns ErrRetriesExceeded if retries are exhausted. An oversized notification may cause the next call on its connection to receive the server’s message-too-large error; use Call when exact attribution matters.
func (*ClientPool) NotifyBatchWithTimeout ¶
func (cp *ClientPool) NotifyBatchWithTimeout(ctx context.Context, timeout time.Duration, n Batch[*Notification]) error
NotifyBatchWithTimeout is a convenience method that calls ClientPool.NotifyBatch with a derived context that includes the specified `timeout`.
func (*ClientPool) NotifyRaw ¶
func (cp *ClientPool) NotifyRaw(ctx context.Context, notify RawNotification) (err error)
NotifyRaw acquires a client connection from the pool and sends the raw notification using [Client.NotifyRaw]. Handles retries similarly to ClientPool.Notify. Returns ErrRetriesExceeded if retries are exhausted. An oversized notification may cause the next call on its connection to receive the server’s message-too-large error; use Call when exact attribution matters.
func (*ClientPool) NotifyRawWithTimeout ¶
func (cp *ClientPool) NotifyRawWithTimeout(ctx context.Context, timeout time.Duration, n RawNotification) error
NotifyRawWithTimeout is a convenience method that calls ClientPool.NotifyRaw with a derived context that includes the specified `timeout`.
func (*ClientPool) NotifyWithTimeout ¶
func (cp *ClientPool) NotifyWithTimeout(ctx context.Context, timeout time.Duration, n *Notification) error
NotifyWithTimeout is a convenience method that calls ClientPool.Notify with a derived context that includes the specified `timeout`.
func (*ClientPool) Reset ¶
func (cp *ClientPool) Reset()
Reset closes all idle connections in the pool and marks all currently acquired connections to be closed when they are released. Any subsequent Acquire calls will create new connections. This is useful for forcing connections to be re-established, for example, after a network configuration change or suspected server issue.
type ClientPoolConfig ¶
type ClientPoolConfig struct {
// URI specifies the target server address (e.g., "tcp:localhost:9090", "http://api.example.com/rpc").
// This URI is passed to the dialer function when new connections are needed. See [Dial] for supported schemes.
URI string
// IdleTimeout defines the maximum duration a client connection can remain idle in the pool
// before being closed. Defaults to [DefaultPoolIdleTimeout] seconds if zero.
// A negative value disables idle connection closing.
IdleTimeout time.Duration
// DialTimeout specifies the maximum time allowed for establishing a new client connection.
// Defaults to [DefaultPoolDialTimeout] seconds if zero or negative.
DialTimeout time.Duration
// Retries specifies the number of times an operation (Call*, Notify*) should be retried
// if it fails with a potentially transient error (e.g., network error, EOF).
// The minimum effective value is 1 (meaning one initial attempt + one retry).
// Defaults to 1 (one initial attempt + one retry) if zero or negative.
Retries int
// MaxSize defines the maximum number of client connections allowed in the pool (both idle and in-use).
// If zero or negative, it defaults to `min(runtime.NumCPU(), runtime.GOMAXPROCS(-1)) * 2`.
// Attempts to acquire a connection when the pool is full will block until a connection becomes available
// or the context is cancelled.
MaxSize int32
// AcquireOnCreate, if true, attempts to establish one initial connection when the pool is created.
// This verifies the URI and dialer configuration early. If the initial connection fails,
// [NewClientPool] or [NewClientPoolWithDialer] will return an error.
AcquireOnCreate bool
}
ClientPoolConfig holds configuration parameters for creating a ClientPool.
type ContextKey ¶
type ContextKey int
ContextKey defines the type for keys used to store values in a context.Context. These keys are used by the Server and RPCServer to provide access to underlying connection details or request information within Handler functions.
const ( // CtxNetConn is the context key for the underlying [net.Conn] in stream-based servers ([Server.Serve]). CtxNetConn ContextKey = iota // CtxNetPacketConn is the context key for the underlying [net.PacketConn] in packet-based servers ([Server.ServePacket]). CtxNetPacketConn // CtxHTTPRequest is the context key for the [*http.Request] when using [HTTPHandler] or [Server.ListenAndServe] with an "http" scheme. CtxHTTPRequest // CtxFromAddr is the context key for the sender's [net.Addr] in packet-based servers ([RPCServer] used by [Server.ServePacket]). CtxFromAddr // CtxRPCServer is the context key for the parent [*RPCServer] handling the current request or connection. CtxRPCServer )
type Data ¶
type Data struct {
// contains filtered or unexported fields
}
Data acts as a container for arbitrary JSON data, typically used within other structures like ErrorData or Result.
When unmarshalling JSON into a struct containing a Data field, the corresponding JSON value is stored internally as a json.RawMessage. This allows delaying the actual parsing of the data until it's needed.
Use the Data.Unmarshal method to decode the contained data into a specific Go type.
func (*Data) IsZero ¶
IsZero returns true if the Data struct is its zero value, meaning it was likely omitted in the source JSON or not populated. It returns false if the Data struct holds a value, even if that value is JSON `null`.
func (*Data) MarshalJSON ¶
MarshalJSON implements the json.Marshaler interface. It marshals the contained Go value into JSON. If the value is json.RawMessage, it's output directly. If the value is nil (and the field was present), it marshals as JSON `null`.
func (*Data) RawMessage ¶
func (d *Data) RawMessage() json.RawMessage
RawMessage returns the underlying json.RawMessage if the Data container holds one (which is the typical case after unmarshalling JSON). If the container holds a different type or is empty/null, it returns nil.
Example:
var d Data
_ = json.Unmarshal([]byte(`{"key":"value"}`), &d)
raw := d.RawMessage() // raw contains json.RawMessage(`{"key":"value"}`)
func (*Data) TypeHint ¶
TypeHint inspects the contained json.RawMessage (if present) and returns a hint about the top-level JSON type (object, array, string, etc.). See TypeHint for possible values. Returns TypeNotJSON if the container doesn't hold a json.RawMessage.
Example:
var d Data _ = json.Unmarshal([]byte(`[1, 2, 3]`), &d) hint := d.TypeHint() // hint == jsonrpc2.TypeArray
func (*Data) Unmarshal ¶
Unmarshal decodes the JSON data held within the Data container into the value pointed to by v. This is the primary method for accessing the data in a structured way.
It returns:
- nil: If unmarshalling is successful.
- ErrEmptyData: If the Data container is empty.
- ErrNotRawMessage: If the container does not hold a json.RawMessage (less common).
- An error from the underlying JSON decoder if the data is invalid or doesn't match `v`.
Example:
var d Data
_ = json.Unmarshal([]byte(`{"name":"example","value":10}`), &d)
type MyStruct struct {
Name string `json:"name"`
Value int `json:"value"`
}
var target MyStruct
err := d.Unmarshal(&target)
if err == nil {
fmt.Println(target.Name, target.Value) // Output: example 10
}
func (*Data) UnmarshalJSON ¶
UnmarshalJSON implements the json.Unmarshaler interface.
func (*Data) Value ¶
Value returns the underlying Go value stored within the Data container. After unmarshalling JSON, this will typically be a json.RawMessage or nil (for JSON null). This method is less commonly used than Data.Unmarshal.
type DeadlineReader ¶
type DeadlineReader interface {
io.ReadCloser
SetReadDeadline(time.Time) error
}
DeadlineReader represents an io.ReaderCloser that supports setting a deadline.
type DeadlineWriter ¶
type DeadlineWriter interface {
io.WriteCloser
SetWriteDeadline(time.Time) error
}
DeadlineWrite represents an io.WriteCloser that supports setting a deadline.
type Decoder ¶
type Decoder interface {
// Decode reads the next JSON-encoded value from its input and stores it
// in the value pointed to by v. The context can be used for cancellation
// or deadlines.
Decode(ctx context.Context, v any) error
// Unmarshal parses the JSON-encoded data and stores the result
// in the value pointed to by v. This method must be safe for concurrent use
// by multiple goroutines, as it might be used independently of Decode
// (e.g., in packet-based transports).
Unmarshal(data []byte, v any) error
}
Decoder defines the interface for decoding JSON-RPC messages. It is used by Client and Server to process incoming JSON data.
A compatible implementation must handle standard JSON decoding, including types like json.Marshaler, json.Unmarshaler, json.RawMessage, and json.Number. Support for the `omitzero` struct tag (Go 1.24+) is also required.
Implementations are encouraged to support io.Closer for resource cleanup and DeadlineReader for timeout handling, if applicable to the underlying transport.
func NewDecoder ¶
NewDecoder creates a new StreamDecoder that reads from r. This is the default NewDecoderFunc used by Server if no custom function is provided.
type Encoder ¶
type Encoder interface {
// Encode writes the JSON encoding of v to its output.
// The context can be used for cancellation or deadlines.
Encode(ctx context.Context, v any) error
}
Encoder defines the interface for encoding JSON-RPC messages. It is used by Client and Server to send JSON data.
Implementations MUST be safe for concurrent use by multiple goroutines.
A compatible implementation must handle standard JSON decoding, including types like json.Marshaler, json.Unmarshaler, json.RawMessage, and json.Number. Support for the `omitzero` struct tag (Go 1.24+) is also required.
Implementations are encouraged to support io.Closer for resource cleanup and DeadlineWriter for timeout handling, if applicable to the underlying transport.
func NewEncoder ¶
NewEncoder creates a new StreamEncoder that writes to w. This is the default NewEncoderFunc used by Server if no custom function is provided. It ensures thread-safe writes.
type Error ¶
type Error struct {
Code int64 `json:"code"` // A number indicating the error type. Standard codes are predefined.
Message string `json:"message"` // A string providing a short description of the error.
Data ErrorData `json:"data,omitempty,omitzero"` // Optional additional information about the error.
// contains filtered or unexported fields
}
Error represents a JSON-RPC 2.0 error object as defined in the specification. It encapsulates a numeric code, a descriptive message, and optional, application-defined data.
This type implements the standard Go `error` interface via its Error.Error method, allowing it to be used in standard Go error handling patterns. It also supports comparison using errors.Is by comparing error codes (see Error.Is).
Use the constructor functions NewError or NewErrorWithData to create valid instances. Access the error details using the Error.Code, Error.Message, and Error.Data fields (note: Data is of type ErrorData). Check if an Error instance is uninitialized using Error.IsZero.
See: https://www.jsonrpc.org/specification#error_object
func NewError ¶
NewError creates a new Error instance with the specified code and message. The Data field will be absent.
Example:
appErr := jsonrpc2.NewError(-32002, "Application specific error")
fmt.Printf("Code: %d, Message: %s\n", appErr.Code, appErr.Message)
// Output: Code: -32002, Message: Application specific error
func NewErrorWithData ¶
NewErrorWithData creates a new Error instance with the specified code, message, and additional data. The provided `data` value will be wrapped in an ErrorData struct. The `data` should be marshalable to JSON.
Example:
details := map[string]any{"field": "username", "issue": "cannot be empty", "attempt": 3}
paramErr := jsonrpc2.NewErrorWithData(-32602, "Invalid params", details)
// To access the data later (after receiving an error response):
var receivedDetails map[string]any
if err := paramErr.Data.Unmarshal(&receivedDetails); err == nil {
fmt.Printf("Issue with field '%s': %s (attempt %v)\n",
receivedDetails["field"], receivedDetails["issue"], receivedDetails["attempt"])
}
// Output: Issue with field 'username': cannot be empty (attempt 3)
func (Error) Error ¶
Error implements the standard Go `error` interface. It returns the Error.Message string, making the Error type usable wherever a standard `error` is expected. Note: This means the Code and Data are not included in the basic error string. Use type assertion or errors.As to access the full Error details if needed.
func (Error) Is ¶
Is reports whether the target error `t` is considered equivalent to this Error `e`. Equivalence is determined solely by comparing the Error.Code fields of non-zero errors (created via NewError methods or Unmarshaled). This allows using errors.Is to check if an error matches one of the predefined error types (like ErrInvalidParams, ErrMethodNotFound, etc.) regardless of the message or data content.
Example:
func handleRequest(params any) error {
if params == nil {
// Return a specific instance of InvalidParams with details
return jsonrpc2.ErrInvalidParams.WithData("Parameters cannot be null")
}
// ... process params ...
return nil
}
err := handleRequest(nil)
if errors.Is(err, jsonrpc2.ErrInvalidParams) {
// This block will execute because the codes match (-32602)
fmt.Println("Handling Invalid Parameters error...")
// We can still access the specific error details if needed:
if rpcErr, ok := err.(jsonrpc2.Error); ok {
fmt.Printf(" Details: %v\n", rpcErr.Data.RawMessage()) // Output: Details: "Parameters cannot be null"
}
}
Example ¶
err := NewError(-32601, "Method not found")
if errors.Is(err, ErrMethodNotFound) {
fmt.Println("Error is Method not found.")
}
if !errors.Is(err, ErrInvalidRequest) {
fmt.Println("Error is not Invalid request.")
}
Output: Error is Method not found. Error is not Invalid request.
func (*Error) IsZero ¶
IsZero reports whether the Error instance is the zero value. A zero-value Error typically indicates that no error was present in a JSON-RPC response or that the Error struct was not initialized correctly. Errors created using NewError or NewErrorWithData will not be zero.
func (*Error) UnmarshalJSON ¶
UnmarshalJSON implements the encoding/json.Unmarshaler interface. This custom unmarshaler ensures that the internal flag is set correctly when an Error object is successfully unmarshaled from JSON, distinguishing it from a zero-value Error struct. It uses the package-level Unmarshal function.
func (Error) WithData ¶
WithData creates and returns a *new* Error instance based on the receiver `e`, but with its data field replaced by the provided `data` value (wrapped in ErrorData). The original error `e` remains unchanged. This is useful for adding context to predefined errors.
Example:
// Add specific details to a standard error
detailedErr := jsonrpc2.ErrInvalidRequest.WithData("Request must include 'id' field for non-notifications")
fmt.Println(detailedErr.Code) // Output: -32600
fmt.Println(detailedErr.Message) // Output: Invalid Request
// detailedErr.Data now contains "Request must include 'id' field for non-notifications"
Example ¶
baseErr := NewError(-32602, "Invalid params")
errWithDetails := baseErr.WithData("Parameter 'count' must be positive")
fmt.Printf("Code: %d\n", errWithDetails.Code)
fmt.Printf("Message: %s\n", errWithDetails.Message)
fmt.Printf("Data: %s\n", errWithDetails.Data.Value())
Output: Code: -32602 Message: Invalid params Data: Parameter 'count' must be positive
type ErrorData ¶
type ErrorData = Data
ErrorData represents the optional "data" field within a JSON-RPC 2.0 Error object. It acts as a container for arbitrary, application-defined error information.
ErrorData is an alias for the Data type. When an Error is unmarshaled, the "data" field's JSON value is stored internally as a json.RawMessage within this container. Use the [ErrorData.Unmarshal] (inherited from Data) method to decode the contained data into a specific Go type.
See: https://www.jsonrpc.org/specification#error_object
func NewErrorData ¶
NewErrorData creates a new ErrorData instance containing the provided value `v`. The value `v` should be marshalable to JSON.
This constructor is typically used indirectly via Error.WithData or NewErrorWithData.
Example:
// Create error data containing details about an invalid field.
details := map[string]string{"field": "email", "reason": "must be a valid email address"}
errData := jsonrpc2.NewErrorData(details)
See Data for more details on how the value is handled.
type HTTPBridge ¶
type HTTPBridge struct {
// contains filtered or unexported fields
}
HTTPBridge provides a combined Encoder and Decoder implementation that communicates with a JSON-RPC server over HTTP(S). It uses a standard net/http.Client to send POST requests with JSON payloads and expects JSON responses.
Each call to HTTPBridge.Encode sends an HTTP request and stores the response. The subsequent call to HTTPBridge.Decode processes that stored response.
IMPORTANT: An HTTPBridge instance is NOT safe for concurrent use by multiple goroutines. It maintains internal state related to the last request/response cycle. However, it is safe to use within a *Client or *ClientPool, as these types provide the necessary synchronization before calling Encode/Decode.
Use NewHTTPBridge to create instances.
func NewHTTPBridge ¶
func NewHTTPBridge(url string) *HTTPBridge
NewHTTPBridge creates a new *HTTPBridge configured to send JSON-RPC requests via HTTP POST to the specified url. It initializes an internal net/http.Client with default settings.
Example:
bridge := NewHTTPBridge("https://api.example.com/rpc")
// Typically, the bridge is then used with a Client:
rpcClient := NewClient(bridge, bridge) // Use bridge for both encoding and decoding
defer rpcClient.Close() // This will call bridge.Close()
// Use rpcClient for making calls...
func (*HTTPBridge) Close ¶
func (h *HTTPBridge) Close() error
Close terminates the bridge, closing any idle connections held by the underlying net/http.Client. Subsequent calls to HTTPBridge.Encode or HTTPBridge.Decode will return io.EOF. This method is typically called via Client.Close.
func (*HTTPBridge) Decode ¶
func (h *HTTPBridge) Decode(ctx context.Context, v any) error
Decode processes the HTTP response received by the previous HTTPBridge.Encode call and attempts to unmarshal the JSON body into the value pointed to by `v`. It implements the Decoder interface.
Behavior depends on the stored response:
- If the bridge is closed, returns io.EOF.
- If the context is cancelled, returns the context error.
- If the response had a "Content-Type" of "application/json" and a non-empty body, attempts to unmarshal the body into `v`. Returns any unmarshalling error.
- If the response was JSON but the body was empty, returns ErrHTTPEmptyResponse.
- If the response had a successful status code (2xx) but was not JSON, returns ErrHTTPNotJSON.
- If the response had a non-successful status code (not 2xx), returns ErrHTTPResponse.
The specific HTTP status is included in the formatted error messages for ErrHTTPEmptyResponse, ErrHTTPNotJSON, and ErrHTTPResponse.
func (*HTTPBridge) Encode ¶
func (h *HTTPBridge) Encode(ctx context.Context, v any) error
Encode marshals the provided value `v` to JSON and sends it as an HTTP POST request to the configured URL. It implements the Encoder interface.
It sets the "Content-Type" header to "application/json". The HTTP response status, headers, and body are stored internally for processing by the next call to HTTPBridge.Decode.
If the bridge is closed, it returns io.EOF. Context cancellation is respected during the HTTP request. Errors during JSON marshaling or the HTTP request are returned directly. If the HTTP request completes successfully (regardless of status code), this method returns nil; response handling occurs in Decode.
type HTTPHandler ¶
type HTTPHandler struct {
// Binder allows optional binding of the RPC server lifecycle or context.
// Can be set after creating the handler with NewHTTPHandler.
Binder Binder
// NewEncoder specifies the function to create a JSON encoder for responses.
// Defaults to the package-level jsonrpc2.NewEncoder.
// Can be set after creating the handler with NewHTTPHandler.
NewEncoder NewEncoderFunc
// NewDecoder specifies the function to create a JSON decoder for requests.
// Defaults to the package-level jsonrpc2.NewDecoder.
// Can be set after creating the handler with NewHTTPHandler.
NewDecoder NewDecoderFunc
// MaxBytes specifies the maximum number of bytes to read from the request body.
// If 0 or less, no limit is enforced by this handler (http.MaxBytesReader is not used).
// Can be set after creating the handler with NewHTTPHandler.
MaxBytes int64
// contains filtered or unexported fields
}
HTTPHandler adapts a jsonrpc2 Handler to the standard net/http.Handler interface. It allows serving JSON-RPC 2.0 requests over HTTP.
Create instances using NewHTTPHandler. After creation, you can optionally configure:
- Binder: A Binder implementation to manage server lifecycle or context.
- NewEncoder/NewDecoder: Custom functions for creating JSON encoders/decoders. Defaults to the package-level NewEncoder and NewDecoder.
- MaxBytes: A limit on the request body size (in bytes). Defaults to 0 (no limit).
During request processing, HTTPHandler automatically:
- Validates the "Content-Type" header is "application/json".
- Sets the "Content-Type" header of the response to "application/json".
- Injects the *http.Request into the request context using the CtxHTTPRequest key.
- Handles request size limits if MaxBytes is set.
- Manages an internal RPCServer configured for synchronous, serial processing suitable for HTTP.
- Translates specific errors (like size limits) into appropriate HTTP status codes.
func NewHTTPHandler ¶
func NewHTTPHandler(handler Handler) *HTTPHandler
NewHTTPHandler creates a new *HTTPHandler that wraps the provided jsonrpc2 Handler. It uses the default package-level NewEncoder and NewDecoder functions.
Example:
mux := jsonrpc2.NewMethodMux()
mux.RegisterFunc("echo", func(ctx context.Context, req *jsonrpc2.Request) (any, error) {
return req.Params, nil
})
httpHandler := jsonrpc2.NewHTTPHandler(mux)
http.Handle("/rpc", httpHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
func (*HTTPHandler) ServeHTTP ¶
func (h *HTTPHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request)
ServeHTTP processes an incoming HTTP request, expecting it to be a JSON-RPC 2.0 call. It implements the net/http.Handler interface.
The method performs the following steps:
- Checks if the request "Content-Type" is "application/json". Responds with 415 Unsupported Media Type if not.
- Sets the response "Content-Type" to "application/json".
- If MaxBytes > 0, wraps the request body with http.MaxBytesReader.
- Creates an internal, short-lived RPCServer configured with the handler's NewDecoder, NewEncoder, and Handler. This server runs synchronously (NoRoutines=true, SerialBatch=true) suitable for a single HTTP request/response cycle.
- Creates a new context derived from the request context, adding the *http.Request using the CtxHTTPRequest key.
- If a Binder is configured, calls its Bind method.
- Runs the internal RPCServer to process the request body.
- Handles potential errors: - EOF is ignored (normal end of request). - UnexpectedEOF (often malformed JSON) results in a JSON-RPC Parse Error response. - ErrJSONTooLarge or MaxBytesError results in a 413 response with a JSON-RPC error. - Other errors result in a 500 Internal Server Error response.
- Writes the JSON-RPC response (if any) from the internal buffer to the http.ResponseWriter.
- If no response was generated (e.g., for Notifications), responds with 204 No Content.
type Handler ¶
Handler defines the interface for processing JSON-RPC 2.0 requests. Implementations of Handler are responsible for executing the logic associated with a specific RPC method or routing requests to appropriate handlers.
The Handle method receives the request context and the Request object. It should return a result value (which will be marshaled to JSON) and a nil error on success, or a nil result and an error on failure.
Result Handling:
- If the returned result is a *Response or RawResponse, it is used directly.
- Otherwise, the result is wrapped in a standard Response object.
- If the result is nil, it will be marshaled as JSON `null`.
Error Handling:
- If the returned error is, or wraps, a Error, that Error is used directly.
- Otherwise, the error is wrapped in a standard ErrInternalError, and its Error() string becomes the "data" field of the JSON-RPC error object.
Notifications:
- If the incoming Request is a notification (Request.IsNotification is true), both the result and error returned by Handle are ignored by the server.
func NewFuncHandler ¶
NewFuncHandler wraps a function `f` to create a Handler. This allows using simple functions as JSON-RPC handlers without needing to define a struct type.
Example:
pingHandler := jsonrpc2.NewFuncHandler(func(ctx context.Context, req *jsonrpc2.Request) (any, error) {
if req.Method == "ping" {
return "pong", nil
}
return nil, jsonrpc2.ErrMethodNotFound
})
// This pingHandler can now be used with a server or MethodMux.
type ID ¶
type ID struct {
// contains filtered or unexported fields
}
ID represents a JSON-RPC 2.0 request ID.
According to the specification, an ID should be a String, a Number (floats discouraged), or Null (discouraged). This type enforces these requirements.
Upon unmarshalling JSON:
- JSON strings become Go strings.
- JSON numbers (integers or floats) become json.Number.
- JSON null becomes nil internally.
Use the constructor functions NewID (for string, int64, or json.Number) and NewNullID (for null) to create ID instances programmatically. Avoid creating ID structs directly.
See: https://www.jsonrpc.org/specification#request_object
func NewID ¶
NewID creates a new ID with the given value. The type parameter V can be int64, string, or json.Number. Floats are not directly supported; represent them as json.Number if needed.
Example:
idInt := jsonrpc2.NewID(int64(123))
idStr := jsonrpc2.NewID("request-5")
idNum := jsonrpc2.NewID(json.Number("456.7"))
func NewNullID ¶
func NewNullID() ID
NewNullID creates an ID representing the JSON `null` value. This is distinct from a zero-value ID struct (where ID.IsZero is true). Null IDs are primarily used in responses to requests that could not be parsed correctly to determine their original ID.
Example:
nullID := jsonrpc2.NewNullID() fmt.Println(nullID.IsNull()) // Output: true fmt.Println(nullID.IsZero()) // Output: false
func (*ID) Equal ¶
Equal compares two IDs for equivalence according to JSON-RPC rules.
Rules:
- Zero-value IDs (ID.IsZero is true) are never equal to any other ID, including another zero-value ID.
- Two null IDs (ID.IsNull is true) are equal.
- A null ID is never equal to a non-null ID.
- Two string IDs are equal if their string values are identical (==).
- Two numeric IDs ([int64] or json.Number) are compared numerically:
- If both are json.Number, they are compared as strings (exact representation matters, e.g., "1" != "1.0").
- If one is [int64] and the other is json.Number, the json.Number is converted to int64 (if possible) for comparison.
- String IDs are never equal to numeric IDs.
Example:
id1 := jsonrpc2.NewID(int64(1))
id1Str := jsonrpc2.NewID("1")
id1Num := jsonrpc2.NewID(json.Number("1"))
idNull := jsonrpc2.NewNullID()
fmt.Println(id1.Equal(id1Num)) // Output: true
fmt.Println(id1.Equal(id1Str)) // Output: false
fmt.Println(idNull.Equal(idNull)) // Output: true
fmt.Println(id1.Equal(idNull)) // Output: false
func (*ID) Int64 ¶
Int64 attempts to return the ID value as an int64. It succeeds if the underlying value is an int64 or a json.Number that represents a valid integer within the int64 range. It returns an error (ErrIDNotANumber or a `strconv` error) if the value is not a number, is a non-integer number (e.g., "1.2"), or is outside the int64 range. It does *not* attempt to parse string IDs.
Example:
idInt := jsonrpc2.NewID(int64(123))
var idNumInt jsonrpc2.ID
_ = json.Unmarshal([]byte(`456`), &idNumInt)
var idNumFloat jsonrpc2.ID
_ = json.Unmarshal([]byte(`78.9`), &idNumFloat)
idStr := jsonrpc2.NewID("abc")
i, err := idInt.Int64() // i == 123, err == nil
i, err = idNumInt.Int64() // i == 456, err == nil
_, err = idNumFloat.Int64() // err != nil (strconv.ErrSyntax)
_, err = idStr.Int64() // err == jsonrpc2.ErrIDNotANumber
func (*ID) IsNull ¶
IsNull returns true if the ID represents the JSON `null` value. This happens if the ID was created with NewNullID or unmarshaled from JSON `null`.
Example:
nullID := jsonrpc2.NewNullID() intID := jsonrpc2.NewID(int64(1)) fmt.Println(nullID.IsNull()) // Output: true fmt.Println(intID.IsNull()) // Output: false
func (*ID) IsZero ¶
IsZero returns true if the ID struct is the zero value (i.e., uninitialized). This is distinct from an ID that has been explicitly set to `null` using NewNullID. A zero ID typically indicates an absence of an ID field during processing before marshaling/unmarshaling.
Example:
var zeroID jsonrpc2.ID nullID := jsonrpc2.NewNullID() fmt.Println(zeroID.IsZero()) // Output: true fmt.Println(nullID.IsZero()) // Output: false
func (*ID) MarshalJSON ¶
MarshalJSON implements the json.Marshaler interface. It serializes the ID into the appropriate JSON representation (string, number, or null). A zero-value ID (ID.IsZero is true) marshals as JSON `null`.
func (*ID) Number ¶
Number attempts to return the ID value as a json.Number. It returns the number and `true` only if the underlying value is a json.Number (typically resulting from unmarshalling a JSON number). Otherwise, it returns an empty json.Number and `false`. It does *not* convert int64 or string values.
Example:
var idNum jsonrpc2.ID _ = json.Unmarshal([]byte(`123.45`), &idNum) // Unmarshals to json.Number idInt := jsonrpc2.NewID(int64(1)) n, ok := idNum.Number() // n == "123.45", ok == true _, ok = idInt.Number() // ok == false
func (*ID) String ¶
String attempts to return the ID value as a string. It returns the string and `true` only if the underlying value is a string. Otherwise, it returns an empty string and `false`.
Example:
idStr := jsonrpc2.NewID("request-5")
idInt := jsonrpc2.NewID(int64(1))
s, ok := idStr.String() // s == "request-5", ok == true
_, ok = idInt.String() // ok == false
func (*ID) UnmarshalJSON ¶
UnmarshalJSON implements the json.Unmarshaler interface. It correctly decodes JSON strings, numbers (as json.Number), and null into the ID struct. It returns an error for other JSON types (boolean, object, array).
func (*ID) Value ¶
Value returns the underlying Go value of the ID. The returned type will be one of: string, int64, json.Number, or nil. Returns nil if the ID is the zero value (ID.IsZero is true).
Example:
idInt := jsonrpc2.NewID(int64(123))
idStr := jsonrpc2.NewID("req-abc")
idNull := jsonrpc2.NewNullID()
fmt.Printf("%T\n", idInt.Value()) // Output: int64
fmt.Printf("%T\n", idStr.Value()) // Output: string
fmt.Printf("%T\n", idNull.Value()) // Output: <nil>
type JSONDecoder ¶
type JSONDecoder interface {
// Decode reads the next JSON-encoded value from its input and stores it in the value pointed to by v.
Decode(v any) error
}
JSONDecoder defines the interface required for stream-based JSON decoding, compatible with encoding/json.Decoder. It's used by the default implementations of Decoder and PacketDecoder.
type JSONEncoder ¶
type JSONEncoder interface {
// Encode writes the JSON encoding of v to the stream, followed by a newline character.
Encode(v any) error
}
JSONEncoder defines the interface required for stream-based JSON encoding, compatible with encoding/json.Encoder. It's used by the default implementations of Encoder and PacketEncoder.
type MethodMux ¶
type MethodMux struct {
// contains filtered or unexported fields
}
MethodMux routes incoming JSON-RPC requests to different Handler implementations based on the method name specified in the Request. Method names are case-sensitive.
A MethodMux itself implements the Handler interface, allowing it to be used directly as the main handler for an RPCServer or nested within other muxes. If no specific handler is found for a method, and a default handler has been set via MethodMux.SetDefault or MethodMux.SetDefaultFunc, the default handler is invoked. Otherwise, ErrMethodNotFound is returned.
MethodMux is safe for concurrent use. Reads (MethodMux.Handle) and writes (MethodMux.Register, MethodMux.Replace, MethodMux.Delete, etc.) can happen concurrently. Use NewMethodMux to create instances.
func NewMethodMux ¶
func NewMethodMux() *MethodMux
NewMethodMux creates and returns a new, initialized *MethodMux.
func (*MethodMux) Delete ¶
Delete removes the handler associated with the given method name. If no handler is registered for the method, this is a no-op.
Example:
mux.Delete("obsoleteMethod")
func (*MethodMux) Handle ¶
Handle implements the Handler interface for MethodMux. It looks up the handler registered for the method specified in the Request. If a specific handler is found, Handle delegates the request processing to it. If no specific handler is found, it checks if a default handler is set. If a default handler exists, it delegates to the default handler. If neither a specific nor a default handler is found, it returns ErrMethodNotFound.
func (*MethodMux) Methods ¶
Methods returns a slice containing the names of all currently registered methods. The order of methods in the slice is not guaranteed.
func (*MethodMux) Register ¶
Register associates a Handler with a specific method name. It returns ErrMethodAlreadyExists if a handler for the given method name is already registered. Method names are case-sensitive. Use MethodMux.Replace to overwrite an existing handler.
Example:
mux := jsonrpc2.NewMethodMux()
err := mux.Register("arith.add", addHandler) // addHandler implements jsonrpc2.Handler
if err != nil {
// Handle registration error (e.g., duplicate method)
}
func (*MethodMux) RegisterFunc ¶
func (mm *MethodMux) RegisterFunc(method string, f func(context.Context, *Request) (any, error)) error
RegisterFunc associates a handler function `f` with a specific method name. It wraps the function using NewFuncHandler before registering it. It returns ErrMethodAlreadyExists if a handler for the given method name is already registered. Method names are case-sensitive. Use MethodMux.ReplaceFunc to overwrite an existing handler.
Example:
mux := jsonrpc2.NewMethodMux()
err := mux.RegisterFunc("utils.echo", func(ctx context.Context, req *jsonrpc2.Request) (any, error) {
var params []any
if err := req.Params.Unmarshal(¶ms); err != nil {
return nil, jsonrpc2.ErrInvalidParams.WithData(err.Error())
}
return params, nil
})
if err != nil {
// Handle registration error
}
func (*MethodMux) Replace ¶
Replace registers or replaces the handler for the given method name. If a handler for the method already exists, it is overwritten. Method names are case-sensitive.
Example:
mux := jsonrpc2.NewMethodMux()
mux.Register("myMethod", oldHandler)
mux.Replace("myMethod", newHandler) // Overwrites oldHandler
mux.Replace("newMethod", handler) // Registers handler for newMethod
func (*MethodMux) ReplaceFunc ¶
ReplaceFunc registers or replaces the handler for the given method name using the provided handler function `f`. It wraps the function using NewFuncHandler. If a handler for the method already exists, it is overwritten. Method names are case-sensitive.
Example:
mux := jsonrpc2.NewMethodMux()
echoFunc := func(ctx context.Context, req *jsonrpc2.Request) (any, error) { return req.Params, nil }
mux.ReplaceFunc("utils.echo", echoFunc) // Registers or replaces handler for utils.echo
func (*MethodMux) SetDefault ¶
SetDefault sets the default Handler to be called when no specific handler is found for a requested method. Calling with `nil` removes the default handler.
Example:
// Handler that returns a custom error for unknown methods
unknownMethodHandler := jsonrpc2.NewFuncHandler(func(ctx context.Context, req *jsonrpc2.Request) (any, error) {
return nil, jsonrpc2.NewError(1234, "Unknown method requested: "+req.Method)
})
mux.SetDefault(unknownMethodHandler)
// To remove the default handler:
// mux.SetDefault(nil)
func (*MethodMux) SetDefaultFunc ¶
SetDefaultFunc sets the default handler function to be called when no specific handler is found. It wraps the function `f` using NewFuncHandler. Calling with `nil` removes the default handler.
Example:
mux.SetDefaultFunc(func(ctx context.Context, req *jsonrpc2.Request) (any, error) {
log.Printf("Warning: Unhandled method called: %s", req.Method)
return nil, jsonrpc2.ErrMethodNotFound // Still return standard error
})
// To remove the default handler:
// mux.SetDefaultFunc(nil)
type NewDecoderFunc ¶
NewDecoderFunc defines the signature for functions that create a new Decoder. This allows customizing the decoder used by Server or Client.
type NewEncoderFunc ¶
NewEncoderFunc defines the signature for functions that create a new Encoder. This allows customizing the encoder used by Server or Client.
type NewPacketDecoderFunc ¶
type NewPacketDecoderFunc func(conn net.PacketConn) PacketDecoder
NewPacketDecoderFunc defines the signature for functions that create a new PacketDecoder. This allows customizing the packet decoder used by Server.
type NewPacketEncoderFunc ¶
type NewPacketEncoderFunc func(conn net.PacketConn) PacketEncoder
NewPacketEncoderFunc defines the signature for functions that create a new PacketEncoder. This allows customizing the packet encoder used by Server.
type Notification ¶
type Notification Request
Notification represents a JSON-RPC 2.0 Notification object. A Notification is structurally the same as a Request but lacks an ID field. This signifies that the client does not expect a response from the server.
Use the constructor functions NewNotification or NewNotificationWithParams to create instances.
See: https://www.jsonrpc.org/specification#notification
func NewNotification ¶
func NewNotification(method string) *Notification
NewNotification creates a new Notification with the specified method name. The Params field is initially empty.
Example:
notif := jsonrpc2.NewNotification("logEvent")
// Marshals to: {"jsonrpc":"2.0","method":"logEvent"}
func NewNotificationWithParams ¶
func NewNotificationWithParams(method string, p Params) *Notification
NewNotificationWithParams creates a new Notification with the specified method name and parameters.
Example:
notif := jsonrpc2.NewNotificationWithParams("system.log", jsonrpc2.NewParamsArray([]any{"User logged out", []map[string]any{"userId": 123}))
// Marshals to: {"jsonrpc":"2.0","method":"system.log","params":["User logged out",{"userId":123}]}
func (*Notification) AsRequest ¶
func (n *Notification) AsRequest() *Request
AsRequest returns the *Notification as a *Request pointer.
**Warning:** This function uses `unsafe.Pointer` for type conversion. It does **not** create a copy. The returned *Request pointer refers to the **exact same memory location** as the original *Notification. Modifying the fields through the *Request pointer will change the original *Notification, and vice-versa.
Example (demonstrating shared memory):
notif := jsonrpc2.NewNotification("update")
reqPtr := notif.AsRequest()
fmt.Println(notif.Method) // Output: update
fmt.Println(reqPtr.Method) // Output: update
// Modify via the Request pointer
reqPtr.Method = "statusUpdate"
fmt.Println(notif.Method) // Output: statusUpdate (original notification is changed)
fmt.Println(reqPtr.Method) // Output: statusUpdate
type PacketConnDecoder ¶
type PacketConnDecoder struct {
// contains filtered or unexported fields
}
PacketConnDecoder provides a default implementation of PacketDecoder for use with net.PacketConn. It reads datagrams, decodes the JSON content, and supports configurable read limits and idle timeouts. Use NewPacketConnDecoder to create instances.
func NewPacketConnDecoder ¶
func NewPacketConnDecoder(conn net.PacketConn) *PacketConnDecoder
NewPacketConnDecoder creates and returns a new *PacketConnDecoder that reads packets from the provided net.PacketConn.
func (*PacketConnDecoder) Close ¶
func (i *PacketConnDecoder) Close() error
Close closes the underlying net.PacketConn.
func (*PacketConnDecoder) DecodeFrom ¶
DecodeFrom reads a single datagram from the underlying net.PacketConn, decodes the JSON payload into v, and returns the sender's address. It respects the configured read limit (PacketConnDecoder.SetLimit) for buffer sizing and the idle timeout (PacketConnDecoder.SetIdleTimeout) using read deadlines. The provided context can also be used for cancellation.
func (*PacketConnDecoder) SetIdleTimeout ¶
func (i *PacketConnDecoder) SetIdleTimeout(d time.Duration)
SetIdleTimeout configures an idle timeout for the PacketConnDecoder.DecodeFrom operation. If no packet is received for the duration d, the ongoing DecodeFrom call will be interrupted by setting a past read deadline on the underlying net.PacketConn.
A duration of 0 or less disables the idle timeout.
Example:
dec := NewPacketConnDecoder(conn) dec.SetIdleTimeout(60 * time.Second) // Set a 60-second idle timeout
func (*PacketConnDecoder) SetLimit ¶
func (i *PacketConnDecoder) SetLimit(n int64)
SetLimit configures the maximum allowed size (in bytes) for an incoming datagram. If a datagram larger than n bytes is received, PacketConnDecoder.DecodeFrom might truncate it or return an error depending on the underlying OS behavior, but this decoder primarily uses it to size its internal read buffer. If n is 0 or less, DefaultPacketBufferSize is used.
Example:
dec := NewPacketConnDecoder(conn) dec.SetLimit(1500) // Limit buffer to typical Ethernet MTU
func (*PacketConnDecoder) Unmarshal ¶
func (i *PacketConnDecoder) Unmarshal(data []byte, v any) error
Unmarshal decodes a single JSON object from the provided byte slice into v. This method implements the PacketDecoder interface requirement and is goroutine-safe. It uses the global Unmarshal function variable.
type PacketConnEncoder ¶
type PacketConnEncoder struct {
// contains filtered or unexported fields
}
PacketConnEncoder provides a default implementation of PacketEncoder for use with net.PacketConn. It marshals Go types to JSON and sends them as datagrams, supporting configurable idle timeouts for write operations. Use NewPacketConnEncoder to create instances.
func NewPacketConnEncoder ¶
func NewPacketConnEncoder(w net.PacketConn) *PacketConnEncoder
NewPacketConnEncoder creates and returns a new *PacketConnEncoder that sends packets via the provided net.PacketConn.
func (*PacketConnEncoder) Close ¶
func (i *PacketConnEncoder) Close() error
Close closes the underlying net.PacketConn.
func (*PacketConnEncoder) EncodeTo ¶
EncodeTo marshals v to JSON and sends it as a datagram to the target address `addr`. It respects the configured idle timeout (PacketConnEncoder.SetIdleTimeout) using write deadlines and uses the provided context for cancellation. This method is goroutine-safe.
func (*PacketConnEncoder) SetIdleTimeout ¶
func (i *PacketConnEncoder) SetIdleTimeout(d time.Duration)
SetIdleTimeout configures an idle timeout for the PacketConnEncoder.EncodeTo operation. If sending the packet (writing to the connection) takes longer than duration d, the ongoing EncodeTo call will be interrupted by setting a past write deadline on the underlying net.PacketConn.
A duration of 0 or less disables the idle timeout.
Example:
enc := NewPacketConnEncoder(conn) enc.SetIdleTimeout(5 * time.Second) // Set a 5-second write timeout
type PacketDecoder ¶
type PacketDecoder interface {
// DecodeFrom reads a single packet (datagram) from the underlying connection,
// decodes the JSON payload into the value pointed to by v, and returns the
// network address ([net.Addr]) of the sender.
// The context can be used for cancellation or deadlines.
DecodeFrom(ctx context.Context, v any) (net.Addr, error)
// Unmarshal parses the JSON-encoded data from a byte slice (typically a received packet)
// and stores the result in the value pointed to by v. This method must be goroutine-safe.
Unmarshal(data []byte, v any) error
}
PacketDecoder defines the interface for decoding JSON-RPC messages from packet-based network connections like UDP (net.PacketConn). It's used by Server for handling datagram protocols.
Implementations are expected to decode a single JSON object or array per datagram. All methods must be safe for concurrent use by multiple goroutines.
func NewPacketDecoder ¶
func NewPacketDecoder(conn net.PacketConn) PacketDecoder
NewPacketDecoder creates a new PacketConnDecoder for the given net.PacketConn. This is the default NewPacketDecoderFunc used by Server when configured for packet-based protocols (e.g., "udp").
type PacketEncoder ¶
type PacketEncoder interface {
// EncodeTo marshals the value v into JSON, then sends it as a single datagram
// to the specified network address `addr` using the underlying connection.
// The context can be used for cancellation or deadlines, typically implemented
// via [net.PacketConn.SetWriteDeadline].
EncodeTo(ctx context.Context, v any, addr net.Addr) error
}
PacketEncoder defines the interface for encoding JSON-RPC messages to packet-based network connections like UDP (net.PacketConn). It's used by Server for sending responses or notifications over datagram protocols.
Implementations are expected to encode a single JSON object or array per datagram. All methods must be safe for concurrent use by multiple goroutines.
func NewPacketEncoder ¶
func NewPacketEncoder(w net.PacketConn) PacketEncoder
NewPacketEncoder creates a new PacketConnEncoder for the given net.PacketConn. This is the default NewPacketEncoderFunc used by Server when configured for packet-based protocols (e.g., "udp").
type Params ¶
type Params struct {
// contains filtered or unexported fields
}
Params represent the params member of a jsonrpc2 Request (Request.Params).
Params always decodes to a json.RawMessage internally.
Params must be a json object or array.
func NewParamsArray ¶
NewParamsArray returns a new Params with its value set to v. The underlying value will be the provided slice.
Example:
params := jsonrpc2.NewParamsArray([]any{1, "hello", true})
// params.Value() will be []any{1, "hello", true}
func NewParamsObject ¶
func NewParamsObject[K comparable, V any, P ~map[K]V](v P) Params
NewParamsObject returns a new Params with its value set to v. The underlying value will be the provided map.
Example:
user := map[string]any{"name": "Alice", "age": 30}
params := jsonrpc2.NewParamsObject(user)
// params.Value() will be map[string]any{"name":"Alice", "age":30}
func NewParamsRaw ¶
func NewParamsRaw(v json.RawMessage) Params
NewParamsRaw returns a new Params wrapping the provided json.RawMessage. This is useful when you have parameters already encoded as JSON and want to include them directly in a request without further encoding/decoding cycles until the final processing stage.
It is also used when you need to Marshal a custom struct to use a parameters.
func (*Params) IsZero ¶
IsZero returns if Params represents its zero value.
IsZero is true if the internal value is nil or it contains a json.RawMessage of length 0.
func (*Params) MarshalJSON ¶
MarshalJSON implements the json.Marshaler interface.
func (*Params) RawMessage ¶
func (p *Params) RawMessage() json.RawMessage
RawMessage returns the internally stored json.RawMessage.
If the stored value is not a json.RawMessage nil is returned.
func (*Params) TypeHint ¶
TypeHint provides a hint for the type of json data contained within the Params. See TypeHint.
Returns TypeNotJSON if the underlying type is not a json.RawMessage.
func (*Params) Unmarshal ¶
Unmarshal will unmarshal the internally stored json.RawMessage into v, returning any errors.
If a json.RawMessage is not stored internally, it will return ErrNotRawMessage.
func (*Params) UnmarshalJSON ¶
UnmarshalJSON implements the json.Unmarshaler interface. It ensures that the incoming JSON data represents either a JSON object or array, as required by the JSON-RPC 2.0 specification for the 'params' field. The raw JSON data is stored internally for potential later unmarshalling into a specific Go type via the Params.Unmarshal method.
func (*Params) Value ¶
Value returns the raw internal value. May be a raw go type, json.RawMessage, or nil.
type RPCServer ¶
type RPCServer struct {
// Callbacks provides hooks for customizing server behavior on events like errors or panics.
// See the [Callbacks] type documentation for details on available hooks.
Callbacks Callbacks
// Handler is the core logic implementation for processing JSON-RPC requests.
// This field is mandatory and must be non-nil.
Handler Handler
// SerialBatch controls whether requests within a JSON-RPC batch array ([])
// are processed sequentially (true) or concurrently using goroutines (false).
// Default is false (concurrent).
SerialBatch bool
// NoRoutines controls whether each incoming request or batch is processed
// synchronously within the main server loop (true) or in its own goroutine (false).
// Default is false (use goroutines).
NoRoutines bool
// WaitOnClose determines the shutdown behavior when Run exits.
// If true, Run waits for all active request goroutines to complete before returning.
// If false (default), it signals cancellation to active handlers immediately
// via the context passed to Handle, and then waits.
WaitOnClose bool
// contains filtered or unexported fields
}
RPCServer represents a single JSON-RPC 2.0 server instance that handles requests and notifications over a specific transport (like a single network connection or packet stream). It reads requests using an internal decoder (wrapping Decoder or PacketDecoder), processes them using the configured Handler, and writes responses using an internal encoder (wrapping Encoder or PacketEncoder).
Instances are typically created by a Server for each incoming connection or managed manually for specific use cases. Use constructor functions like NewStreamServer or NewPacketServer to create instances.
Configuration ¶
After creating an RPCServer using a constructor, you can customize its behavior:
- Callbacks: Set fields in the Callbacks struct to handle events like decoding errors, encoding errors, or panics within handlers. A default panic handler is provided.
- Handler: The core logic that processes RPC methods. This is mandatory and usually provided during construction. See Handler.
- SerialBatch: If true, processes requests within a single JSON-RPC batch sequentially in the order received. If false (default), processes them concurrently using goroutines.
- NoRoutines: If true, processes each incoming request or batch synchronously within the main RPCServer.Run loop. If false (default), spawns a new goroutine for each request or batch. Batch members may still be processed concurrently based on SerialBatch.
- WaitOnClose: If true, when RPCServer.Run is shutting down (due to context cancellation or connection error), it waits for all active request-handling goroutines to complete before returning. If false (default), it cancels the internal context immediately and then waits.
Context Values ¶
Within the Handler.Handle method, the context will contain:
- CtxRPCServer: The *RPCServer instance handling the request.
- CtxFromAddr: For packet-based servers (NewPacketServer), the sender's net.Addr.
- Other values from the parent context passed to RPCServer.Run.
func NewPacketServer ¶
func NewPacketServer(d PacketDecoder, e PacketEncoder, handler Handler) *RPCServer
NewPacketServer creates a new *RPCServer configured to operate over a packet-oriented transport (like UDP or Unix datagram sockets) using the provided PacketDecoder and PacketEncoder. The handler processes incoming requests.
Example:
conn, err := net.ListenPacket("udp", ":9091")
// ... handle error ...
defer conn.Close()
mux := jsonrpc2.NewMethodMux()
// ... register methods on mux ...
decoder := jsonrpc2.NewPacketDecoder(conn)
encoder := jsonrpc2.NewPacketEncoder(conn)
server := jsonrpc2.NewPacketServer(decoder, encoder, mux)
// Run the server...
err := server.Runc(ctx)
func NewRPCServerFromPacket ¶
func NewRPCServerFromPacket(rw net.PacketConn, handler Handler) *RPCServer
NewRPCServerFromPacket creates a new *RPCServer configured for packet-oriented transports, using the provided net.PacketConn. It automatically wraps rw with the default NewPacketDecoder and NewPacketEncoder.
Example:
conn, err := net.ListenPacket("udp", ":9091")
// ... handle error ...
defer conn.Close()
mux := jsonrpc2.NewMethodMux()
// ... register methods on mux ...
server := jsonrpc2.NewRPCServerFromPacket(conn, mux)
// Run the server...
func NewStreamServer ¶
NewStreamServer creates a new *RPCServer configured to operate over a stream-oriented transport (like TCP or Unix sockets) using the provided Decoder and Encoder. The handler is used to process incoming requests.
Example:
conn, err := net.Dial("tcp", "localhost:9090")
// ... handle error ...
defer conn.Close()
mux := jsonrpc2.NewMethodMux()
// ... register methods on mux ...
server := jsonrpc2.NewStreamServer(jsonrpc2.NewDecoder(conn), jsonrpc2.NewEncoder(conn), mux)
// server.NoRoutines = true // Optional configuration
// Run the server (typically managed by a jsonrpc2.Server)
// err = server.Run(context.Background())
// ... handle error ...
func NewStreamServerFromIO ¶
func NewStreamServerFromIO(rw io.ReadWriter, handler Handler) *RPCServer
NewStreamServerFromIO creates a new *RPCServer configured for stream-oriented transports, using the provided io.ReadWriter (like a net.Conn). It automatically wraps rw with the default stream NewDecoder and NewEncoder.
Example:
conn, err := net.Dial("tcp", "localhost:9090")
// ... handle error ...
defer conn.Close()
mux := jsonrpc2.NewMethodMux()
// ... register methods on mux ...
server := jsonrpc2.NewStreamServerFromIO(conn, mux)
// Run the server...
func (*RPCServer) Close ¶
Close attempts to close the underlying decoder and encoder if they implement io.Closer. This is typically used to release resources like network connections or files associated with the transport. Errors from closing both are joined together.
func (*RPCServer) Run ¶
Run starts the main loop of the RPC server. It continuously reads messages from the decoder, processes them, and sends responses via the encoder.
It blocks until the provided context `ctx` is cancelled, an unrecoverable error occurs during decoding (like io.EOF or a connection error), or a fatal JSON syntax error is encountered on a stream-based transport.
Error Handling:
- io.EOF or context cancellation errors generally indicate a clean shutdown and are returned after cleanup.
- JSON syntax errors (json.SyntaxError): A JSON-RPC Parse Error response is sent. For stream-based decoders, Run returns immediately as the stream state is uncertain. For packet-based decoders, Run continues processing subsequent packets.
- JSON type errors (json.UnmarshalTypeError): A JSON-RPC Invalid Request error response is sent. Run continues processing as the stream/packet state is likely recoverable.
- ErrJSONTooLarge: A Message too large response is sent best-effort and Run returns.
- Other decoding errors: Run returns the error.
Shutdown: When Run exits, it performs cleanup:
- Waits for active goroutines according to `WaitOnClose`.
- Cancels the internal server context (`sctx`).
- Calls RPCServer.Close to close the underlying encoder/decoder if possible.
- Calls the Callbacks.OnExit callback.
- Returns any error encountered during shutdown or the initial error causing exit, joined together.
type RawNotification ¶
type RawNotification json.RawMessage
RawNotification represents a fully encoded *Request that is a notification. It may be used in certain client calls for sending specially formed notifications. When using a RawNotification, no encoding checking is done to ensure its validity.
type RawRequest ¶
type RawRequest json.RawMessage
RawRequest represents a fully encoded *Request. It may be used in certain client calls for sending specially formed requests. When using a RawRequest, no encoding checking is done to ensure its validity.
type RawResponse ¶
type RawResponse json.RawMessage
RawRequest represents a fully encoded *Response It may be used as a result from the Handler to provided a specially formed response. When using a RawResponse, no encoding checking is done to ensure its validity.
type Request ¶
type Request struct {
Jsonrpc Version `json:"jsonrpc"`
Method string `json:"method"`
Params Params `json:"params,omitzero,omitempty"`
ID ID `json:"id,omitzero"`
}
Request represents a JSON-RPC 2.0 request object sent from a client to a server. It includes the JSON-RPC version (Request.Jsonrpc), the method to be invoked (Request.Method), optional parameters (Request.Params), and an ID (Request.ID) used to correlate requests and responses. If the Request.ID is omitted, the request is treated as a Notification.
func NewRequest ¶
NewRequest creates a new JSON-RPC request object with the specified ID and method. The Request.Params field is initially empty (zero value).
Example:
reqInt := jsonrpc2.NewRequest(int64(1), "add")
reqStr := jsonrpc2.NewRequest("req-001", "getUser")
func NewRequestWithParams ¶
NewRequestWithParams creates a new JSON-RPC request object with the specified ID (Request.ID), method (Request.Method), and parameters (Request.Params).
Example:
req := jsonrpc2.NewRequestWithParams(int64(2), "add", jsonrpc2.NewParamsArray([]any{1, 2}))
userParams := map[string]any{"userId": 123}
paramsObj := jsonrpc2.NewParamsObject(userParams)
reqUser := jsonrpc2.NewRequestWithParams("user-req", "getUser", paramsObj)
func (*Request) AsNotification ¶
func (r *Request) AsNotification() *Notification
AsNotification attempts to convert the Request into a *Notification.
It returns a valid *Notification pointer if the Request.ID is zero (meaning it's structurally a notification). Otherwise, it returns nil.
Important: This function performs a type conversion using unsafe.Pointer. It does NOT create a copy. The returned *Notification points to the same underlying memory as the original *Request. Modifying the *Notification will modify the *Request and vice-versa. Use with caution.
Example:
notifReq := jsonrpc2.NewNotification("logEvent", jsonrpc2.NewParamsArray([]any{"system start"}))
req := notifReq.AsRequest() // req is now a *Request, but IsNotification() is true
if n := req.AsNotification(); n != nil {
fmt.Printf("Got notification: Method=%s\n", n.Method)
// Modify through notification pointer
n.Method = "newEvent"
}
fmt.Println(req.Method) // Output: newEvent
regularReq := jsonrpc2.NewRequest(int64(1), "getData")
if n := regularReq.AsNotification(); n == nil {
fmt.Println("Regular request is not a notification")
}
func (*Request) IsNotification ¶
IsNotification returns true if the request object represents a JSON-RPC Notification. A Notification is a Request without an Request.ID field.
Example:
req := jsonrpc2.NewRequest(int64(1), "regularMethod")
fmt.Println(req.IsNotification()) // Output: false
// Notifications are typically created using NewNotification
notif := jsonrpc2.NewNotification("logMessage")
fmt.Println(notif.AsRequest().IsNotification()) // Output: true
func (*Request) ResponseWithError ¶
ResponseWithError constructs a Response for the current *Request and populates the Response.Error field. If e is of type Error it will be used directly. Other values for error will automatically be converted to an ErrInternalError with the data field populated with the error string.
func (*Request) ResponseWithResult ¶
ResponseWithResult constructs a Response for the current *Request and populates the Response.Result with result.
type Response ¶
type Response struct {
Jsonrpc Version `json:"jsonrpc"` // Specifies the JSON-RPC version ("2.0").
Result Result `json:"result,omitempty,omitzero"` // The result of the method invocation, if successful. Omitted if an error occurred.
Error Error `json:"error,omitempty,omitzero"` // An error object if an error occurred during invocation. Omitted if successful.
ID ID `json:"id"` // Must be the same as the request ID. Should be null if the request ID could not be determined (e.g., parse error).
}
Response represents a JSON-RPC 2.0 response object.
A response must contain either a Result or an Error, but not both. The ID field must mirror the ID from the request it is responding to. Use the constructor functions (NewResponseWithResult, NewResponseWithError, NewResponseError) to ensure compliance with the specification.
See: https://www.jsonrpc.org/specification#response_object
func NewResponseError ¶
NewResponseError creates an error response with a null ID. This is primarily used when a request is malformed and its ID cannot be determined. For errors related to valid requests, use NewResponseWithError.
If 'e' is already a jsonrpc2.Error, it is used directly. Otherwise, 'e' is wrapped in a standard ErrInternalError, and its Error() string becomes the 'data' field.
Example:
// Responding to a parsing error where the request ID is unknown
parseErr := jsonrpc2.ErrParseErrorError.WithData("Invalid JSON received")
resp := jsonrpc2.NewResponseError(parseErr)
// Marshals to: {"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error","data":"Invalid JSON received"},"id":null}
func NewResponseWithError ¶
NewResponseWithError creates an error response for a given request ID and error.
If 'e' is already a jsonrpc2.Error, it is used directly. Otherwise, 'e' is wrapped in a standard ErrInternalError, and its Error() string becomes the 'data' field of the JSON-RPC error object.
Example:
// Responding to request ID "req-01" with a custom RPC error
rpcErr := jsonrpc2.NewError(100, "Resource not found")
resp1 := jsonrpc2.NewResponseWithError("req-01", rpcErr)
// Marshals to: {"jsonrpc":"2.0","error":{"code":100,"message":"Resource not found"},"id":"req-01"}
// Responding to request ID 2 with a standard Go error
stdErr := fmt.Errorf("database connection failed")
resp2 := jsonrpc2.NewResponseWithError(2, stdErr)
// Marshals to: {"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal error","data":"database connection failed"},"id":2}
func NewResponseWithResult ¶
NewResponseWithResult creates a successful response for a given request ID and result. The result 'r' will be marshaled to JSON.
Example:
// Responding to a request with ID 1 and result "pong"
resp := jsonrpc2.NewResponseWithResult(1, "pong")
// Marshals to: {"jsonrpc":"2.0","result":"pong","id":1}
func (*Response) IsError ¶
IsError returns true if the response contains an Error object (indicating failure), and false otherwise (indicating success).
func (*Response) Unmarshal ¶ added in v0.1.3
Unmarshal attempts to unmarshal the result of the JSON-RPC response into the provided variable v.
If the response itself represents an error (i.e., r.Error is not zero), Unmarshal returns ErrResponseIsError and does not attempt to unmarshal the result. Otherwise, it calls the Unmarshal method on r.Result, passing v as the target.
The v argument must be a pointer, similar to the behavior of json.Unmarshal.
func (*Response) UnmarshalError ¶ added in v0.1.3
UnmarshalError unmarshals the error data from the response into the given variable v.
If the response does not represent an error (i.e., r.Error is nil or zero), it returns ErrResponseIsResponse. Otherwise, it attempts to unmarshal the r.Error.Data field into v.
It returns an error if unmarshalling fails or if the response is not an error.
type Result ¶
type Result = Data
Result represents the "result" field within a successful JSON-RPC 2.0 Response object. It acts as a container for the data returned by the invoked method.
Result is an alias for the Data type. When a successful Response is unmarshaled, the "result" field's JSON value is stored internally as a json.RawMessage within this container. Use the [Result.Unmarshal] (inherited from Data) method to decode the contained data into a specific Go type.
See: https://www.jsonrpc.org/specification#response_object
func NewResult ¶
NewResult creates a new Result instance containing the provided value `v`. The value `v` should be marshalable to JSON.
This constructor is typically used indirectly via NewResponseWithResult.
Example:
// Create a result containing a simple string.
resultData := jsonrpc2.NewResult("operation successful")
See Data for more details on how the value is handled.
type Server ¶
type Server struct {
// Binder allows optional binding of the RPC server lifecycle or context,
// called just before an RPCServer starts processing requests for a connection or packet stream.
Binder Binder
// NewEncoder specifies the function to create a stream-based JSON encoder.
// Defaults to [NewEncoder].
NewEncoder NewEncoderFunc
// NewDecoder specifies the function to create a stream-based JSON decoder.
// Defaults to [NewDecoder].
NewDecoder NewDecoderFunc
// NewPacketEncoder specifies the function to create a packet-based JSON encoder.
// Defaults to [NewPacketEncoder]. Used by ServePacket.
NewPacketEncoder NewPacketEncoderFunc
// NewPacketDecoder specifies the function to create a packet-based JSON decoder.
// Defaults to [NewPacketDecoder]. Used by ServePacket.
NewPacketDecoder NewPacketDecoderFunc
// HTTPReadTimeout sets the ReadHeaderTimeout for the internal http.Server
// when using ListenAndServe with an "http" scheme. Defaults to DefaultHTTPReadTimeout seconds.
HTTPReadTimeout time.Duration
// HTTPShutdownTimeout sets the timeout for graceful shutdown of the internal http.Server.
// Defaults to DefaultHTTPShutdownTimeout seconds.
HTTPShutdownTimeout time.Duration
// PacketRoutines specifies the number of goroutines to spawn for processing packets
// when using ServePacket or ListenAndServe with UDP/Unixgram schemes.
// Defaults to min(runtime.NumCPU(), runtime.GOMAXPROCS()).
PacketRoutines int
// contains filtered or unexported fields
}
Server manages listening for incoming JSON-RPC 2.0 connections or packets and dispatching them to a configured Handler. It simplifies setting up listeners for various network protocols (TCP, UDP, Unix sockets, HTTP).
For stream-based protocols (TCP, Unix), it accepts connections and creates a dedicated *RPCServer instance and goroutine for each connection. For packet-based protocols (UDP, Unixgram), it typically uses multiple goroutines (controlled by PacketRoutines) reading from a single net.PacketConn, each served by an *RPCServer. For HTTP, it sets up an net/http.Server using an HTTPHandler.
Use NewServer to create instances. Configuration options like custom encoders/decoders (NewEncoderFunc, NewDecoderFunc, etc.), a Binder, HTTP timeouts, and the number of packet processing routines can be set on the Server instance after creation.
func NewServer ¶
NewServer creates a new *Server configured with the provided Handler. It initializes the server with default encoder/decoder functions (e.g., NewEncoder), default HTTP timeouts, and a default number of packet processing routines based on CPU cores. These defaults can be overridden by modifying the fields of the returned Server instance before calling listening methods like Server.ListenAndServe or Server.Serve.
Example:
mux := jsonrpc2.NewMethodMux() // ... register methods on mux ... server := jsonrpc2.NewServer(mux) // Optionally customize: // server.HTTPShutdownTimeout = 60 * time.Second // server.PacketRoutines = 8 // server.Binder = myCustomBinder err := server.ListenAndServe(context.Background(), "tcp::9090") // ... handle error ...
func (*Server) ListenAndServe ¶
ListenAndServe parses the listenURI, determines the protocol, and starts listening for incoming connections or packets, serving them using the configured handler. It blocks until the provided context is cancelled or an unrecoverable error occurs.
Supported URI schemes:
- tcp, tcp4, tcp6: Listens on a TCP address (e.g., "tcp:127.0.0.1:9090", "tcp::9090"). Delegates to Server.Serve.
- unix: Listens on a Unix domain socket (e.g., "unix:///tmp/mysocket.sock"). Delegates to Server.Serve.
- udp, udp4, udp6: Listens on a UDP address (e.g., "udp::9091"). Delegates to Server.ServePacket.
- unixgram: Listens on a Unix domain datagram socket (e.g., "unixgram:///tmp/mydgram.sock"). Delegates to Server.ServePacket.
- http: Starts an HTTP server (e.g., "http://localhost:8080/rpc"). The path component (/rpc) is used for the handler. Delegates to internal listenAndServeHTTP.
The server gracefully shuts down when the context is cancelled. For HTTP servers, shutdown respects the Server.HTTPShutdownTimeout. For other listeners, cancellation closes the listener, allowing active connections/goroutines to finish processing.
It is safe to call ListenAndServe multiple times concurrently with non-conflicting listening URIs.
Example:
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
mux := jsonrpc2.NewMethodMux()
// ... register methods ...
server := jsonrpc2.NewServer(mux)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
log.Println("Starting TCP server on :9090")
if err := server.ListenAndServe(ctx, "tcp::9090"); err != nil && !errors.Is(err, net.ErrClosed) {
log.Printf("TCP server error: %v", err)
}
}()
wg.Add(1)
go func() {
defer wg.Done()
log.Println("Starting HTTP server on :8080/rpc")
if err := server.ListenAndServe(ctx, "http://:8080/rpc"); err != nil && !errors.Is(err, net.ErrClosed) && !errors.Is(err, http.ErrServerClosed) {
log.Printf("HTTP server error: %v", err)
}
}()
// Wait for interrupt signal to cancel context and shut down servers
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
<-sigChan
log.Println("Shutting down servers...")
cancel()
// Wait for servers to stop
wg.Wait()
func (*Server) Serve ¶
Serve accepts incoming connections from the provided net.Listener and serves them using the Server's configured Handler. It blocks until the listener returns an error (e.g., due to closing) or the provided context is cancelled.
For each accepted connection, it spawns a new goroutine running an *RPCServer instance configured with the Server's stream-based NewDecoderFunc and NewEncoderFunc. The connection's net.Conn is added to the request context via the CtxNetConn key. If a Binder is configured on the Server, it is called for each new connection's RPCServer.
The method ensures the listener is closed when the context is cancelled. It waits for all connection-handling goroutines to complete before returning.
It is safe to call Serve multiple times concurrently with different listeners.
func (*Server) ServePacket ¶
ServePacket reads packets from the provided net.PacketConn and processes them using the Server's configured Handler. It blocks until the connection returns an error or the provided context is cancelled.
It spawns multiple goroutines (controlled by Server.PacketRoutines) to read and process packets concurrently. Each goroutine runs an *RPCServer instance configured with the Server's packet-based NewPacketDecoderFunc and NewPacketEncoderFunc. The underlying net.PacketConn is added to the context via the CtxNetPacketConn key. The sender's address for each packet is typically added to the request context using the CtxFromAddr key.
If a Binder is configured on the Server, it is called for each spawned RPCServer instance.
The method ensures the PacketConn is closed when the context is cancelled. It waits for all processing goroutines to complete before returning, aggregating any errors encountered.
It is safe to call ServePacket multiple times concurrently with different PacketConns.
type StreamDecoder ¶
type StreamDecoder struct {
// contains filtered or unexported fields
}
StreamDecoder provides a stream-based Decoder implementation. It reads JSON objects sequentially from an io.Reader. Use NewStreamDecoder to create instances. It supports optional read limits via StreamDecoder.SetLimit and idle timeouts via StreamDecoder.SetIdleTimeout.
func NewStreamDecoder ¶
func NewStreamDecoder(r io.Reader) *StreamDecoder
NewStreamDecoder creates and returns a new *StreamDecoder that reads from r. It initializes the decoder with a standard JSON decoder.
func (*StreamDecoder) Close ¶
func (i *StreamDecoder) Close() error
Close attempts to close the underlying io.Reader if it implements io.Closer. This is useful for releasing resources like network connections or files. Returns nil if the reader does not implement io.Closer.
func (*StreamDecoder) Decode ¶
func (i *StreamDecoder) Decode(ctx context.Context, v any) error
Decode reads the next JSON value from the stream and decodes it into v. It respects the configured read limit (StreamDecoder.SetLimit) and idle timeout (StreamDecoder.SetIdleTimeout). It uses the provided context for cancellation.
func (*StreamDecoder) SetIdleTimeout ¶
func (i *StreamDecoder) SetIdleTimeout(d time.Duration)
SetIdleTimeout configures an idle timeout for the StreamDecoder.Decode operation. If no data is received for the duration d, the ongoing Decode call may be interrupted.
Timeout mechanism depends on the underlying io.Reader:
- If the reader implements DeadlineReader (like net.Conn), its SetReadDeadline method is used. This is the preferred mechanism as it doesn't necessarily close the connection.
- If the reader implements io.Closer but not DeadlineReader, its Close method is called upon timeout to interrupt the blocking read.
- If the reader implements neither, timeouts are not supported by this method.
A duration of 0 or less disables the idle timeout.
Example:
dec := NewStreamDecoder(conn) dec.SetIdleTimeout(30 * time.Second) // Set a 30-second idle timeout
func (*StreamDecoder) SetLimit ¶
func (i *StreamDecoder) SetLimit(n int64)
SetLimit configures a maximum number of bytes (n) to read for a single JSON object during a StreamDecoder.Decode call. If a JSON object exceeds this limit, StreamDecoder.Decode will return ErrJSONTooLarge. A limit of 0 or less disables the read limit.
Example:
dec := NewStreamDecoder(conn) dec.SetLimit(1024 * 1024) // Limit reads to 1 MiB per JSON object
type StreamEncoder ¶
type StreamEncoder struct {
// contains filtered or unexported fields
}
StreamEncoder provides a stream-based Encoder implementation. It writes JSON objects sequentially to an io.Writer. Use NewStreamEncoder to create instances. It ensures thread-safe writes to the underlying writer and supports optional idle timeouts via StreamEncoder.SetIdleTimeout.
func NewStreamEncoder ¶
func NewStreamEncoder(w io.Writer) *StreamEncoder
NewStreamEncoder creates and returns a new *StreamEncoder that writes to w. It is safe for concurrent StreamEncoder.Encode calls.
func (*StreamEncoder) Close ¶
func (i *StreamEncoder) Close() error
Close attempts to close the underlying io.Writer if it implements io.Closer. This is useful for releasing resources like network connections or files. Returns nil if the writer does not implement io.Closer.
func (*StreamEncoder) Encode ¶
func (i *StreamEncoder) Encode(ctx context.Context, v any) error
Encode writes the JSON encoding of v to the underlying stream. It respects the configured idle timeout (StreamEncoder.SetIdleTimeout) and uses the provided context for cancellation. This method is goroutine-safe due to internal locking.
func (*StreamEncoder) SetIdleTimeout ¶
func (i *StreamEncoder) SetIdleTimeout(d time.Duration)
SetIdleTimeout configures an idle timeout for the StreamEncoder.Encode operation. If writing the JSON object takes longer than duration d, the ongoing Encode call may be interrupted.
Timeout mechanism depends on the underlying io.Writer:
- If the writer implements DeadlineWriter (like net.Conn), its SetWriteDeadline method is used. This is the preferred mechanism as it doesn't necessarily close the connection.
- If the writer implements io.Closer but not DeadlineWriter, its Close method is called upon timeout to interrupt the blocking write.
- If the writer implements neither, timeouts are not supported by this method.
A duration of 0 or less disables the idle timeout.
Example:
enc := NewStreamEncoder(conn) enc.SetIdleTimeout(10 * time.Second) // Set a 10-second write timeout
type TransportClient ¶
type TransportClient struct {
// contains filtered or unexported fields
}
TransportClient provides methods for making JSON-RPC 2.0 calls to a remote server over a single underlying connection or stream.
Most users will want to use Client instead.
A TransportClient instance manages encoding requests and decoding responses. It is goroutine-safe, meaning multiple goroutines can make calls concurrently using the same TransportClient instance. However, all calls are serialized over the single underlying transport managed by the associated Encoder and Decoder. For concurrent connections, consider using a [TransportClientPool].
Use DialTransport, NewTransportClient, or NewTransportClientIO to create instances.
func DialTransport ¶
func DialTransport(ctx context.Context, destURI string) (*TransportClient, error)
Dial establishes a JSON-RPC 2.0 client connection to the specified destination URI. It returns a *TransportClient ready for making RPC calls.
The `destURI` format is `scheme:address` or an http/https URL.
Supported Schemes:
- `tcp`, `tcp4`, `tcp6`: Establishes a standard TCP connection. Address is `host:port`.
- `udp`, `udp4`, `udp6`: Uses UDP. Address is `host:port`. Note: UDP is connectionless; `Dial` sets up the local side.
- `unix`, `unixgram`, `unixpacket`: Connects to a Unix domain socket. Address is the socket file path.
- `http`, `https`: Connects via HTTP(S). The full URL is used. Creates a client wrapping an *HTTPBridge.
- `tls`: Establishes a TLS connection over TCP. Address is `host:port`. Uses default *tls.Config.
- `tls4`: Establishes a TLS connection over TCPv4. Address is `host:port`. Uses default *tls.Config.
- `tls6`: Establishes a TLS connection over TCPv6. Address is `host:port`. Uses default *tls.Config.
Examples:
- `tcp:127.0.0.1:9090`
- `tcp:localhost:9090`
- `udp::9090` (UDP to port 9090 on all interfaces)
- `http://127.0.0.1:8080/rpc`
- `https://api.example.com/jsonrpc`
- `unix:///tmp/mysocket.sock`
- `tls:127.0.0.1:9443`
Returns ErrUnknownScheme if the scheme is not supported. Other errors may be returned from underlying network or TLS dialers, or URL parsing.
func NewTransportClient ¶
func NewTransportClient(e Encoder, d Decoder) *TransportClient
NewTransportClient creates a new TransportClient that uses the provided Encoder and Decoder for communication. This allows using custom encoding/decoding logic or transports.
func NewTransportClientIO ¶
func NewTransportClientIO(rw io.ReadWriter) *TransportClient
NewTransportClientIO creates a new TransportClient that communicates over the given io.ReadWriter. It wraps the `rw` with the default NewEncoder and NewDecoder implementations. This is a convenient way to create a client for standard network connections (net.Conn) or other stream-based transports.
func (*TransportClient) Call ¶
Call sends a JSON-RPC request and waits for its response. It encodes the provided *Request, sends it to the server, waits for the server's response, decodes it, and returns the resulting *Response.
Example:
client := jsonrpc2.NewTransportClientIO(conn) // Assume conn is an established io.ReadWriter
defer client.Close()
req := jsonrpc2.NewRequestWithParams(1, "arith.add", NewParamsArray([]int{2, 3}))
resp, err := client.Call(context.Background(), req)
if err != nil {
log.Fatalf("Call failed: %v", err)
}
if resp.IsError() {
log.Fatalf("RPC Error: %v", resp.Error)
}
var result int
if err := resp.Result.Unmarshal(&result); err != nil {
log.Fatalf("Failed to unmarshal result: %v", err)
}
fmt.Println("Result:", result) // Output: Result: 5
func (*TransportClient) CallBatch ¶
func (c *TransportClient) CallBatch(ctx context.Context, r Batch[*Request]) (Batch[*Response], error)
CallBatch sends a batch of requests ([Batch[*Request]]) and waits for the batch response. The server should respond with a JSON array containing Response objects, potentially out of order and possibly omitting responses for notifications included in the batch.
If the server incorrectly responds with a single JSON object instead of an array, this method attempts to parse that single object as a Response and returns it wrapped in a Batch of length 1.
Example:
req1 := jsonrpc2.NewRequest(int64(1), "method1")
req2 := jsonrpc2.NewRequest(int64(2), "method2")
batchReq := jsonrpc2.NewBatch[*jsonrpc2.Request](0)
batchReq.Add(req1, req2)
batchResp, err := client.CallBatch(context.Background(), batchReq)
if err != nil {
log.Fatalf("CallBatch failed: %v", err)
}
// Process responses in batchResp, potentially using BatchCorrelate
jsonrpc2.BatchCorrelate(batchReq, batchResp, func(req *jsonrpc2.Request, res *jsonrpc2.Response) bool {
// ... handle matched req/res pairs ...
return true
})
func (*TransportClient) CallBatchWithTimeout ¶
func (c *TransportClient) CallBatchWithTimeout(ctx context.Context, timeout time.Duration, r Batch[*Request]) (Batch[*Response], error)
CallBatchWithTimeout is a convenience method that calls TransportClient.CallBatch with a derived context that includes the specified `timeout`. See TransportClient.CallBatch for more details.
func (*TransportClient) CallRaw ¶
func (c *TransportClient) CallRaw(ctx context.Context, r RawRequest) (*Response, error)
CallRaw sends a pre-encoded JSON-RPC request (RawRequest) and waits for its response. This is useful when the request payload is already available as raw JSON bytes. It decodes the response into a *Response struct.
Example:
rawJSON := `{"jsonrpc":"2.0","method":"echo","params":["hello"],"id":10}`
rawReq := jsonrpc2.RawRequest(rawJSON)
resp, err := client.CallRaw(context.Background(), rawReq)
// ... process response as in Call example ...
func (*TransportClient) CallRawWithTimeout ¶
func (c *TransportClient) CallRawWithTimeout(ctx context.Context, timeout time.Duration, r RawRequest) (*Response, error)
CallRawWithTimeout is a convenience method that calls TransportClient.CallRaw with a derived context that includes the specified `timeout`. See TransportClient.CallRaw for more details.
func (*TransportClient) CallWithTimeout ¶
func (c *TransportClient) CallWithTimeout(ctx context.Context, timeout time.Duration, r *Request) (*Response, error)
CallWithTimeout is a convenience method that calls TransportClient.Call with a derived context that includes the specified `timeout`. See TransportClient.Call for more details.
func (*TransportClient) Close ¶
func (c *TransportClient) Close() error
Close attempts to close the underlying Encoder and Decoder if they implement the io.Closer interface. It ensures that closing happens safely even if called concurrently with other operations.
It is safe to call Close multiple times; subsequent calls after the first will have no effect.
After Close is called, the TransportClient should no longer be used for making calls, as the underlying transport will likely be closed. Errors from closing both the encoder and decoder (if applicable) are joined using errors.Join.
func (*TransportClient) Notify ¶
func (c *TransportClient) Notify(ctx context.Context, n *Notification) error
Notify sends a JSON-RPC notification. It encodes the provided *Notification, sends it to the server, and returns immediately without waiting for any response from the server.
Example:
notif := jsonrpc2.NewNotificationWithParams("logEvent", NewParamsObject(map[string]string{"level": "info", "message": "User logged in"}))
err := client.Notify(context.Background(), notif)
if err != nil {
log.Printf("Notify failed: %v", err) // Log error, but don't expect a response error
}
An oversized notification may cause the next call on this connection to receive the server’s message-too-large error; use Call when exact attribution matters.
func (*TransportClient) NotifyBatch ¶
func (c *TransportClient) NotifyBatch(ctx context.Context, n Batch[*Notification]) error
NotifyBatch sends a batch of notifications ([Batch[*Notification]]). It encodes the batch, sends it to the server, and returns immediately. No response is expected or processed.
Example:
notif1 := jsonrpc2.NewNotification("event1")
notif2 := jsonrpc2.NewNotification("event2")
batchNotif := jsonrpc2.NewBatch[*jsonrpc2.Notification](0)
batchNotif.Add(notif1, notif2)
err := client.NotifyBatch(context.Background(), batchNotif)
// ... handle potential send error ...
An oversized notification may cause the next call on this connection to receive the server’s message-too-large error; use Call when exact attribution matters.
func (*TransportClient) NotifyBatchWithTimeout ¶
func (c *TransportClient) NotifyBatchWithTimeout(ctx context.Context, timeout time.Duration, n Batch[*Notification]) error
NotifyBatchWithTimeout is a convenience method that calls TransportClient.NotifyBatch with a derived context that includes the specified `timeout`. See TransportClient.NotifyBatch for more details.
func (*TransportClient) NotifyRaw ¶
func (c *TransportClient) NotifyRaw(ctx context.Context, n RawNotification) error
NotifyRaw sends a pre-encoded JSON-RPC notification (RawNotification). It sends the raw JSON bytes to the server and returns immediately.
Example:
rawJSON := `{"jsonrpc":"2.0","method":"systemUpdate"}`
rawNotif := jsonrpc2.RawNotification(rawJSON)
err := client.NotifyRaw(context.Background(), rawNotif)
// ... handle potential send error ...
An oversized notification may cause the next call on this connection to receive the server’s message-too-large error; use Call when exact attribution matters.
func (*TransportClient) NotifyRawWithTimeout ¶
func (c *TransportClient) NotifyRawWithTimeout(ctx context.Context, timeout time.Duration, n RawNotification) error
NotifyRawWithTimeout is a convenience method that calls TransportClient.NotifyRaw with a derived context that includes the specified `timeout`. See TransportClient.NotifyRaw for more details.
func (*TransportClient) NotifyWithTimeout ¶
func (c *TransportClient) NotifyWithTimeout(ctx context.Context, timeout time.Duration, n *Notification) error
NotifyWithTimeout is a convenience method that calls TransportClient.Notify with a derived context that includes the specified `timeout`. See TransportClient.Notify for more details.
type TypeHint ¶
type TypeHint int
TypeHint provides a quick classification of the likely top-level JSON type contained within a json.RawMessage, based solely on its first non-whitespace character. It is used by types like Data and Params to offer insight into the contained data without performing a full unmarshal.
Note: This is only a hint and does not guarantee the json.RawMessage contains valid JSON of that type.
const ( TypeUnknown TypeHint = iota // Could not determine type from the first character (e.g., invalid JSON start). TypeArray // Likely a JSON array (starts with '['). TypeObject // Likely a JSON object (starts with '{'). TypeBool // Likely a JSON boolean (starts with 't' or 'f'). TypeNumber // Likely a JSON number (starts with '-', '0'-'9'). TypeString // Likely a JSON string (starts with '"'). TypeNull // Likely the JSON null value (starts with 'n'). // TypeEmpty is returned when the [json.RawMessage], after trimming whitespace, // has zero length. TypeEmpty // TypeNotJSON is returned by container types ([Data], [Params]) when their // internal value is not a [json.RawMessage], preventing a JSON type hint. TypeNotJSON )
func HintType ¶ added in v0.1.2
func HintType(m json.RawMessage) TypeHint
HintType examines the first non-whitespace byte of a json.RawMessage to provide a TypeHint about the potential JSON data type it represents. It returns TypeEmpty if the message is empty after trimming whitespace. It returns TypeUnknown if the first character doesn't match known JSON type indicators.
This function provides a fast check but does not validate the entire JSON structure.
type Version ¶
type Version struct {
// contains filtered or unexported fields
}
Version represents the "jsonrpc" field required in JSON-RPC 2.0 requests and responses. Its primary role is to ensure the correct protocol version ("2.0") is specified during JSON unmarshalling.
The zero value of Version is considered invalid until successfully unmarshaled from the correct JSON string `"2.0"`.
See: https://www.jsonrpc.org/specification#request_object See: https://www.jsonrpc.org/specification#response_object
func (*Version) IsValid ¶
IsValid returns true if the Version was successfully unmarshaled from the JSON string `"2.0"`. A zero-value Version or one unmarshaled from incorrect data will return false.
func (Version) MarshalJSON ¶
MarshalJSON implements the json.Marshaler interface. It always returns the JSON string `"2.0"`, regardless of the Version's internal state ([IsValid]). This ensures that marshaled requests/responses always include the correct protocol version string.
func (*Version) UnmarshalJSON ¶
UnmarshalJSON implements the json.Unmarshaler interface. It expects the input `data` to be a JSON string containing exactly "2.0". If the input is not a string or does not match ProtocolVersion, it returns an error (ErrDecoding or ErrWrongProtocolVersion). On success, it marks the Version as valid (so [IsValid] returns true).
Source Files
¶
- batch.go
- batchbuilder.go
- binder.go
- callbacks.go
- client.go
- clientpool.go
- data.go
- deadliner.go
- decoder.go
- dial.go
- encoder.go
- error.go
- errordata.go
- handler.go
- httpbridge.go
- httphandler.go
- id.go
- jsonrpc2.go
- notification.go
- packetdecoder.go
- packetencoder.go
- params.go
- raw.go
- request.go
- response.go
- result.go
- rpcserver.go
- server.go
- shims.go
- transportclient.go
- typehint.go
- version.go