jsonerr

package
v0.0.0-...-6d1a572 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

README

Package cloudeng.io/encoding/json/jsonerr

import cloudeng.io/encoding/json/jsonerr

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]() }

Functions

Func ErrorForWire
func ErrorForWire(w Wire) (error, error)

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
func Marshal(err error) ([]byte, error)

Marshal encodes err as its Wire representation.

Func Unmarshal
func Unmarshal(data []byte) (error, error)

Unmarshal decodes an error encoded by Marshal. The outer error reports a failure to decode; the inner one is the error that was encoded.

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"`
}
Methods
func (rw ReadWriter) MarshalJSONTo(enc *jsontext.Encoder) error
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.

Methods
func (r *Reader) UnmarshalJSONFrom(dec *jsontext.Decoder) error
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.

Functions
func WireForError(err error) Wire

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.

Methods
func (w Writer) MarshalJSONTo(enc *jsontext.Encoder) error

Examples

ExampleMarshal

ExampleMarshal shows an error being sent and reconstructed as its original concrete type, so that errors.As can be used on the far side.

ExampleUnmarshal

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.

ExampleReadWriter

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.

ExampleReader

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.

ExampleWriter

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.

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

func ErrorForWire(w Wire) (error, error)

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

func Marshal(err error) ([]byte, error)

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

func Unmarshal(data []byte) (error, error)

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

func (*Reader) UnmarshalJSONFrom

func (r *Reader) UnmarshalJSONFrom(dec *jsontext.Decoder) error

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

func WireForError(err error) Wire

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":""}}

func (Writer) MarshalJSONTo

func (w Writer) MarshalJSONTo(enc *jsontext.Encoder) error

Jump to

Keyboard shortcuts

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