Documentation
¶
Overview ¶
Package jsonerr provides support for sending errors over the wire as JSON.
An encoded error carries two things: a message, which any receiver can use, and, when the error has state that can be encoded, a typed payload that a receiver which has registered the error's type can decode back into the original concrete error. The type of an error is identified by its fully qualified name (e.g. "example.com/pkg.MyError") and the payload uses the representation defined by cloudeng.io/encoding/json/jsonpayload.
An error type is an ordinary struct with an Error method; it needs no JSON methods of its own, since its payload is encoded and decoded by the standard struct encoding. Types are registered for decoding with jsonpayload.RegisterType:
type NotFound struct {
Name string `json:"name"`
}
func (e *NotFound) Error() string { return e.Name + " not found" }
func init() { jsonpayload.RegisterType[NotFound]() }
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ErrorForWire ¶
ErrorForWire returns the error represented by w. If w carries a payload whose type has been registered with jsonpayload.RegisterType then the original concrete error is returned, so that errors.Is and errors.As can be used on it. Otherwise an error carrying only the message is returned, which means that an unregistered type degrades to its message rather than to a failure. A nil error is returned for the zero Wire.
func Marshal ¶
Marshal encodes err as its Wire representation.
Example ¶
ExampleMarshal shows an error being sent and reconstructed as its original concrete type, so that errors.As can be used on the far side.
package main
import (
"errors"
"fmt"
"log"
"cloudeng.io/encoding/json/jsonerr"
)
// Timeout is an ordinary error type: a struct with an Error method. It needs
// no JSON methods, since its payload is encoded by the standard struct
// encoding.
type Timeout struct {
Op string `json:"op"`
Seconds int `json:"seconds"`
}
func (e *Timeout) Error() string {
return fmt.Sprintf("%v timed out after %vs", e.Op, e.Seconds)
}
func main() {
buf, err := jsonerr.Marshal(&Timeout{Op: "read", Seconds: 30})
if err != nil {
log.Fatal(err)
}
fmt.Println(string(buf))
received, err := jsonerr.Unmarshal(buf)
if err != nil {
log.Fatal(err)
}
var timeout *Timeout
fmt.Println(errors.As(received, &timeout), timeout.Op, timeout.Seconds)
}
Output: {"error":"read timed out after 30s","detail":{"type":"cloudeng.io/encoding/json/jsonerr_test.Timeout","payload":{"op":"read","seconds":30}}} true read 30
func Unmarshal ¶
Unmarshal decodes an error encoded by Marshal. The outer error reports a failure to decode; the inner one is the error that was encoded.
Example ¶
ExampleUnmarshal shows what happens to errors that cannot be reconstructed: one whose type is not registered by the receiver, and one that has no state to encode in the first place. Both keep their message.
package main
import (
"errors"
"fmt"
"log"
"cloudeng.io/encoding/json/jsonerr"
)
func main() {
// An error with no exported state carries only its message.
buf, err := jsonerr.Marshal(errors.New("something went wrong"))
if err != nil {
log.Fatal(err)
}
fmt.Println(string(buf))
received, err := jsonerr.Unmarshal(buf)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%T %v\n", received, received)
}
Output: {"error":"something went wrong"} *errors.errorString something went wrong
Types ¶
type ReadWriter ¶
type ReadWriter struct {
Err error
}
ReadWriter is an error that can be both encoded and decoded, for use as an ordinary tagged field of a struct:
type Response struct {
Result string `json:"result"`
Err jsonerr.ReadWriter `json:"err"`
}
Example ¶
ExampleReadWriter shows an error carried as an ordinary tagged field of a struct that is both encoded and decoded, which is the usual case for a type shared by both ends. Use Writer or Reader instead when only one direction is needed, as in ExampleWriter and ExampleReader.
package main
import (
"encoding/json/v2"
"errors"
"fmt"
"log"
"cloudeng.io/encoding/json/jsonerr"
)
// Timeout is an ordinary error type: a struct with an Error method. It needs
// no JSON methods, since its payload is encoded by the standard struct
// encoding.
type Timeout struct {
Op string `json:"op"`
Seconds int `json:"seconds"`
}
func (e *Timeout) Error() string {
return fmt.Sprintf("%v timed out after %vs", e.Op, e.Seconds)
}
func main() {
type Response struct {
Result string `json:"result"`
Err jsonerr.ReadWriter `json:"err"`
}
out := Response{
Result: "partial",
Err: jsonerr.ReadWriter{Err: &Timeout{Op: "write", Seconds: 5}},
}
buf, err := json.Marshal(&out)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(buf))
var in Response
if err := json.Unmarshal(buf, &in); err != nil {
log.Fatal(err)
}
var timeout *Timeout
fmt.Println(in.Result, errors.As(in.Err.Err, &timeout), in.Err.Err)
}
Output: {"result":"partial","err":{"error":"write timed out after 5s","detail":{"type":"cloudeng.io/encoding/json/jsonerr_test.Timeout","payload":{"op":"write","seconds":5}}}} partial true write timed out after 5s
func (ReadWriter) MarshalJSONTo ¶
func (rw ReadWriter) MarshalJSONTo(enc *jsontext.Encoder) error
func (*ReadWriter) UnmarshalJSONFrom ¶
func (rw *ReadWriter) UnmarshalJSONFrom(dec *jsontext.Decoder) error
type Reader ¶
type Reader struct {
Err error
}
Reader decodes an error encoded by Writer, for use where an error is a field of a struct that is itself decoded from JSON.
Example ¶
ExampleReader shows the other half: a party that only reads errors decoding what a Writer produced, recovering the concrete error type because it has registered it.
package main
import (
"encoding/json/v2"
"errors"
"fmt"
"log"
"cloudeng.io/encoding/json/jsonerr"
)
// Timeout is an ordinary error type: a struct with an Error method. It needs
// no JSON methods, since its payload is encoded by the standard struct
// encoding.
type Timeout struct {
Op string `json:"op"`
Seconds int `json:"seconds"`
}
func (e *Timeout) Error() string {
return fmt.Sprintf("%v timed out after %vs", e.Op, e.Seconds)
}
// serverResponse is written by a process that only ever reports errors, so it
// needs no decoder for them.
type serverResponse struct {
Result string `json:"result"`
Err jsonerr.Writer `json:"err"`
}
// clientResponse is read by a process that only ever receives errors. It is
// wire compatible with serverResponse: Writer and Reader are the two halves
// of the same representation.
type clientResponse struct {
Result string `json:"result"`
Err jsonerr.Reader `json:"err"`
}
func main() {
buf, err := json.Marshal(&serverResponse{
Result: "partial",
Err: jsonerr.Writer{Err: &Timeout{Op: "write", Seconds: 5}},
})
if err != nil {
log.Fatal(err)
}
var in clientResponse
if err := json.Unmarshal(buf, &in); err != nil {
log.Fatal(err)
}
var timeout *Timeout
fmt.Println(in.Result, errors.As(in.Err.Err, &timeout), timeout.Op, timeout.Seconds)
// A response carrying no error decodes to a nil error.
buf, err = json.Marshal(&serverResponse{Result: "ok"})
if err != nil {
log.Fatal(err)
}
in = clientResponse{}
if err := json.Unmarshal(buf, &in); err != nil {
log.Fatal(err)
}
fmt.Println(in.Result, in.Err.Err == nil)
}
Output: partial true write 5 ok true
type Wire ¶
type Wire struct {
Message string `json:"error"`
Detail *jsonpayload.Wire `json:"detail,omitempty"`
}
Wire is the 'on-the-wire' representation of an error and documents the format that this package produces and accepts. Message is always present so that a receiver can report something useful for an error whose type it does not know. Detail is present only when the error's state could be encoded, and is the representation used by jsonpayload.
func WireForError ¶
WireForError returns the Wire representation of err. Encoding the error's state is best effort: an error such as one returned by errors.New or fmt.Errorf has no exported state to encode, and is represented by its message alone rather than being reported as a failure.
type Writer ¶
type Writer struct {
Err error
}
Writer encodes an error, for use where an error is a field of a struct that is itself encoded as JSON, or is otherwise written to an encoder.
Example ¶
ExampleWriter shows an error being encoded by a party that only writes them, such as a server reporting a failure. Writer supplies MarshalJSONTo, so an error can be an ordinary tagged field of a struct that is itself encoded as JSON.
package main
import (
"encoding/json/v2"
"fmt"
"log"
"cloudeng.io/encoding/json/jsonerr"
)
// Timeout is an ordinary error type: a struct with an Error method. It needs
// no JSON methods, since its payload is encoded by the standard struct
// encoding.
type Timeout struct {
Op string `json:"op"`
Seconds int `json:"seconds"`
}
func (e *Timeout) Error() string {
return fmt.Sprintf("%v timed out after %vs", e.Op, e.Seconds)
}
// serverResponse is written by a process that only ever reports errors, so it
// needs no decoder for them.
type serverResponse struct {
Result string `json:"result"`
Err jsonerr.Writer `json:"err"`
}
func main() {
out := serverResponse{
Result: "partial",
Err: jsonerr.Writer{Err: &Timeout{Op: "write", Seconds: 5}},
}
buf, err := json.Marshal(&out)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(buf))
// An absent error is written as an empty message.
buf, err = json.Marshal(&serverResponse{Result: "ok"})
if err != nil {
log.Fatal(err)
}
fmt.Println(string(buf))
}
Output: {"result":"partial","err":{"error":"write timed out after 5s","detail":{"type":"cloudeng.io/encoding/json/jsonerr_test.Timeout","payload":{"op":"write","seconds":5}}}} {"result":"ok","err":{"error":""}}