canoto

package module
v0.17.2 Latest Latest
Warning

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

Go to latest
Published: Jul 22, 2025 License: BSD-3-Clause Imports: 13 Imported by: 10

README

Canoto

Canoto is a serialization format designed to be:

  1. Fast
  2. Compact
  3. Canonical
  4. Backwards compatible
  5. Read compatible with Protocol Buffers.

Install

go install github.com/StephenButtolph/canoto/canoto@latest

Define Messages

Canoto messages are defined as normal golang structs:

type ExampleStruct0 struct {
	Int32              int32           `canoto:"int,1"`
	Int64              int64           `canoto:"int,2"`
	Uint32             uint32          `canoto:"uint,3"`
	Uint64             uint64          `canoto:"uint,4"`
	Sfixed32           int32           `canoto:"fint32,5"`
	Fixed32            uint32          `canoto:"fint32,6"`
	Sfixed64           int64           `canoto:"fint64,7"`
	Fixed64            uint64          `canoto:"fint64,8"`
	Bool               bool            `canoto:"bool,9"`
	String             string          `canoto:"string,10"`
	Bytes              []byte          `canoto:"bytes,11"`
	OtherStruct        ExampleStruct1  `canoto:"value,12"`
	OtherStructPointer *ExampleStruct1 `canoto:"pointer,13"`
	OtherStructField   *ExampleStruct1 `canoto:"field,14"`

	canotoData canotoData_ExampleStruct0
}

type ExampleStruct1 struct {
	Int32 int32 `canoto:"int,536870911"`

	canotoData canotoData_ExampleStruct1
}

All structs must include a field called canotoData that will cache the results of calculating the size of the struct.

The type canotoData_${structName} is automatically generated by Canoto.

For a given Struct, Canoto automatically implements the Message and FieldMaker[*Struct] interfaces:

// Message defines a type that can be a stand-alone Canoto message.
type Message interface {
	Field
	// MarshalCanoto returns the Canoto representation of this message.
	//
	// It is assumed that this message is ValidCanoto.
	MarshalCanoto() []byte
	// UnmarshalCanoto unmarshals a Canoto-encoded byte slice into the message.
	UnmarshalCanoto(bytes []byte) error
}

// Field defines a type that can be included inside of a Canoto message.
type Field interface {
	// CanotoSpec returns the specification of this canoto message.
	//
	// If there is not a valid specification of this type, it returns nil.
	CanotoSpec(types ...reflect.Type) *Spec
	// MarshalCanotoInto writes the field into a [Writer] and returns the
	// resulting [Writer].
	//
	// It is assumed that CalculateCanotoCache has been called since the last
	// modification to this field.
	//
	// It is assumed that this field is ValidCanoto.
	MarshalCanotoInto(w Writer) Writer
	// CalculateCanotoCache populates internal caches based on the current
	// values in the struct.
	CalculateCanotoCache()
	// CachedCanotoSize returns the previously calculated size of the Canoto
	// representation from CalculateCanotoCache.
	//
	// If CalculateCanotoCache has not yet been called, or the field has been
	// modified since the last call to CalculateCanotoCache, the returned size
	// may be incorrect.
	CachedCanotoSize() uint64
	// UnmarshalCanotoFrom populates the field from a [Reader].
	UnmarshalCanotoFrom(r Reader) error
	// ValidCanoto validates that the field can be correctly marshaled into the
	// Canoto format.
	ValidCanoto() bool
}

// FieldMaker is a Field that can create a new value of type T.
//
// The returned value must be able to be unmarshaled into.
//
// This type can be used when implementing a generic Field. However, if T is an
// interface, it is possible for generated code to compile and panic at runtime.
type FieldMaker[T any] interface {
	Field
	MakeCanoto() T
}

Generate

In order to generate canoto information for all of the structs in a file, simply run the canoto command with one or more files.

canoto example0.go example1.go

The above example will generate example0.canoto.go and example1.canoto.go.

The corresponding proto file for a canoto file can also be generated by adding the --proto.

canoto --proto example.go

The above example will generate example.canoto.go and example.proto.

go:generate

To automatically generate the .canoto.go version of a file, it is recommended to use go:generate

Placing

//go:generate canoto $GOFILE

at the top of a file will update the .canoto.go version of the file every time go generate ./... is run.

Best Practices

canoto only inspects a single golang file at a time, so it is recommended to define nested messages in the same file to be able to generate the most useful proto file.

Additionally, while fully supported in the canoto output, type aliases and generic types will result in proto files with default types. It is still guaranteed for the generated proto file to be able to parse canoto data, but the types may not be as specific as they could be.

If type aliases are needed, it may make sense to modify the generated proto file to specify the most specific proto type possible.

Generics

There are two ways to utilize generics with canoto.

Value and Pointer Types

To guarantee safe usage of a struct, type constraints can be used to implement a struct with a generic field T. Canoto inspects the generic types, so the struct must include a type parameter of canoto.FieldPointer[T]. Such as:

type GenericField[T any, _ canoto.FieldPointer[T]] struct {
	Value   T  `canoto:"value,1"`
	Pointer *T `canoto:"pointer,2"`

	canotoData canotoData_GenericField
}

If canoto.FieldPointer is aliased to a different type or is otherwise re-implemented, Canoto will not be able to correctly tie the type constraints together.

Field Types

Because using multiple types to constrain a single type is clunky, there is support for canoto.FieldMakers. canoto.FieldMakers can be used to allocate new messages during parsing. In order for canoto.FieldMakers to work safely, the implementing type must have a useful zero value.

[!WARNING] CanotoSpec, MakeCanoto, CalculateCanotoCache, CachedCanotoSize, ValidCanoto and MarshalCanotoInto must be able to be called with the zero value of the type implementing canoto.FieldMaker to avoid runtime panics. It is never safe to pass an interface as the canoto.FieldMaker type.

An example of correctly using canoto.FieldMaker:

type GenericField[T canoto.FieldMaker[T]] struct {
	Value T `canoto:"field,1"`

	canotoData canotoData_GenericField
}

var _ canoto.Message = (*GenericField[*ExampleStruct0])(nil)

An example of incorrectly using canoto.FieldMaker:

type GenericField[T canoto.FieldMaker[T]] struct {
	Value T `canoto:"field,1"`

	canotoData canotoData_GenericField
}

type BadUsage interface {
	canoto.Field
	MakeCanoto() BadUsage
}

var _ canoto.Message = (*GenericField[BadUsage])(nil)

Because BadUsage is an interface, it does not have a useful zero value and will panic when GenericField attempts to call its methods.

Pass-By-Value Messages

By default, the auto-generated canotoData struct includes atomic variables. This ensures that MarshalCanoto can be called at the same time on multiple threads, which is expected for what appears to be a read only method.

However, this results in being unable to pass messages by value due to the NoCopy included on atomic variables.

Because calling MarshalCanoto concurrently with copying a message is not safe, the canoto:"nocopy" tag can be added to the canotoData field to disallow message copying.

For example:

type NotPassableByValue struct {
	Int int64 `canoto:"int,1"`

	canotoData canotoData_NotPassableByValue `canoto:"nocopy"`
}
Standalone Implementations

In some instances, it may be desirable for the generated code to avoid introducing the dependency on this repo into the go.mod file. As an example, if the user must support having multiple versions of canoto utilized in the same application.

There are two CLI flags that enable using canoto without impacting the go.mod.

  1. --library when specified generates the canoto library in the provided folder. For example --library="./internal" generates the canoto library in the ./internal/canoto package.
  2. --import specifies the canoto library to depend on in any generated code.

For example:

canoto --library="./internal" --import="github.com/StephenButtolph/canoto/internal/canoto" ./canoto.go

Will generate the canoto library in ./internal/canoto and will import "github.com/StephenButtolph/canoto/internal/canoto" rather than the default "github.com/StephenButtolph/canoto" when generating ./canoto.canoto.go.

Supported Types

go type canoto type proto type wire type
int8 int sint32 varint
int16 int sint32 varint
int32 int sint32 varint
int64 int sint64 varint
uint8 uint uint32 varint
uint16 uint uint32 varint
uint32 uint uint32 varint
uint64 uint uint64 varint
int32 fint32 sfixed32 i32
uint32 fint32 fixed32 i32
int64 fint64 sfixed64 i64
uint64 fint64 fixed64 i64
bool bool bool varint
string string string len
[]byte bytes bytes len
[x]byte fixed bytes bytes len
T Message value message len
*T Message pointer message len
T FieldMaker field message len
[]int8 repeated int repeated sint32 len
[]int16 repeated int repeated sint32 len
[]int32 repeated int repeated sint32 len
[]int64 repeated int repeated sint64 len
[]uint8 repeated uint repeated uint32 len
[]uint16 repeated uint repeated uint32 len
[]uint32 repeated uint repeated uint32 len
[]uint64 repeated uint repeated uint64 len
[]int32 repeated fint32 repeated sfixed32 len
[]uint32 repeated fint32 repeated fixed32 len
[]int64 repeated fint64 repeated sfixed64 len
[]uint64 repeated fint64 repeated fixed64 len
[]bool repeated bool repeated bool len
[]string repeated string repeated string len
[][]byte repeated bytes repeated bytes len
[][x]byte repeated fixed bytes repeated bytes len
[]T Message repeated value repeated message len
[]*T Message repeated pointer repeated message len
[]T FieldMaker repeated field repeated message len
[x]int8 fixed repeated int repeated sint32 len
[x]int16 fixed repeated int repeated sint32 len
[x]int32 fixed repeated int repeated sint32 len
[x]int64 fixed repeated int repeated sint64 len
[x]uint8 fixed repeated uint repeated uint32 len
[x]uint16 fixed repeated uint repeated uint32 len
[x]uint32 fixed repeated uint repeated uint32 len
[x]uint64 fixed repeated uint repeated uint64 len
[x]int32 fixed repeated fint32 repeated sfixed32 len
[x]uint32 fixed repeated fint32 repeated fixed32 len
[x]int64 fixed repeated fint64 repeated sfixed64 len
[x]uint64 fixed repeated fint64 repeated fixed64 len
[x]bool fixed repeated bool repeated bool len
[x]string fixed repeated string repeated string len
[x][]byte fixed repeated bytes repeated bytes len
[x][y]byte fixed repeated fixed bytes repeated bytes len
[x]T Message fixed repeated value repeated message len
[x]*T Message fixed repeated pointer repeated message len
[x]T FieldMaker fixed repeated field repeated message len
Non-standard encoding

It is valid to define a Field that implements a non-standard format. However, this format should still be canonical and the corresponding Proto file should report opaque bytes.

Why not Proto?

Proto is a fast, compact, encoding format with extensive language support. However, Proto is not canonical.

Proto is designed to be forwards-compatible. Almost by definition, a forwards-compatible serialization format can not be canonical. The format of a field can not validated to be canonical if the expected type of the field is not known during decoding.

Why is being canonical important?

In some cases, non-canonical serialization formats are subtle to work with.

For example, if the hash of the serialized data is important or if the serialized data is cryptographically signed.

In order to ensure that the hash of the serialized data does not change, it is important to carefully avoid re-serializing a message that was previously serialized.

For canonical serialization formats, the hash of the serialized data is guaranteed never to change. Every correct implementation of the format will produce the same hash.

Why be read compatible with Proto?

By being read compatible with Proto, users of the Canoto format inherit some Proto's cross language support.

If an application only needs to read Canoto messages, but not write them, it can simply treat the Canoto message as a Proto message.

Is Canoto Fast?

Canoto is typically more performant for both serialization and deserialization than Proto. However, Proto does not typically validate that fields are canonical. If a field is expensive to inspect, it's possible Canoto can be slightly slower.

Canoto is optimized to perform no unnecessary memory allocations, so careful management to ensure messages are stack allocated can significantly improve performance over Proto.

Is Canoto Forwards Compatible?

No. Canoto chooses to be a canonical serialization format rather than being forwards compatible.

Documentation

Overview

Canoto provides common functionality required for reading and writing the canoto format.

Index

Constants

View Source
const (
	Varint WireType = iota
	I64
	Len

	I32

	// SizeEnum8 indicates either an int8 or uint8.
	SizeEnum8 SizeEnum = 1
	// SizeEnum16 indicates either an int16 or uint16.
	SizeEnum16 SizeEnum = 2
	// SizeEnum32 indicates either an int32 or uint32.
	SizeEnum32 SizeEnum = 3
	// SizeEnum64 indicates either an int64 or uint64.
	SizeEnum64 SizeEnum = 4

	// SizeFint32 is the size of a 32-bit fixed size integer in bytes.
	SizeFint32 = 4
	// SizeFint64 is the size of a 64-bit fixed size integer in bytes.
	SizeFint64 = 8
	// SizeBool is the size of a boolean in bytes.
	SizeBool = 1

	// FieldTypeInt is the field number of an int in the FieldType OneOf.
	FieldTypeInt = 6
	// FieldTypeUint is the field number of a uint in the FieldType OneOf.
	FieldTypeUint = 7
	// FieldTypeFixedInt is the field number of a fixed int in the FieldType
	// OneOf.
	FieldTypeFixedInt = 8
	// FieldTypeFixedUint is the field number of a fixed uint in the FieldType
	// OneOf.
	FieldTypeFixedUint = 9
	// FieldTypeBool is the field number of a bool in the FieldType OneOf.
	FieldTypeBool = 10
	// FieldTypeString is the field number of a string in the FieldType OneOf.
	FieldTypeString = 11
	// FieldTypeBytes is the field number of bytes in the FieldType OneOf.
	FieldTypeBytes = 12
	// FieldTypeFixedBytes is the field number of fixed bytes in the FieldType
	// OneOf.
	FieldTypeFixedBytes = 13
	// FieldTypeRecursive is the field number of a recursive type in the
	// FieldType OneOf.
	FieldTypeRecursive = 14
	// FieldTypeMessage is the field number of a message in the FieldType OneOf.
	FieldTypeMessage = 15

	// MaxFieldNumber is the maximum field number allowed to be used in a Tag.
	MaxFieldNumber = 1<<29 - 1

	// Version is the current version of the canoto library.
	Version = "v0.17.2"
)

Variables

View Source
var (
	// Code is the actual golang code for this library; including this comment.
	//
	// This variable is not used internally, so the compiler is smart enough to
	// omit this value from the binary if the user of this library does not
	// utilize this variable; at least at the time of writing.
	//
	// This can be used during codegen to generate this library.
	//
	//go:embed canoto.go
	Code string

	// GeneratedCode is the actual auto-generated golang code for this library.
	//
	// This variable is not used internally, so the compiler is smart enough to
	// omit this value from the binary if the user of this library does not
	// utilize this variable; at least at the time of writing.
	//
	// This can be used during codegen to generate this library.
	//
	//go:embed canoto.canoto.go
	GeneratedCode string

	ErrInvalidFieldOrder  = errors.New("invalid field order")
	ErrUnexpectedWireType = errors.New("unexpected wire type")
	ErrDuplicateOneOf     = errors.New("duplicate oneof field")
	ErrInvalidLength      = errors.New("decoded length is invalid")
	ErrZeroValue          = errors.New("zero value")
	ErrUnknownField       = errors.New("unknown field")
	ErrPaddedZeroes       = errors.New("padded zeroes")

	ErrInvalidRecursiveDepth = errors.New("invalid recursive depth")
	ErrUnknownFieldType      = errors.New("unknown field type")
	ErrUnexpectedFieldSize   = errors.New("unexpected field size")
	ErrInvalidFieldType      = errors.New("invalid field type")

	ErrOverflow        = errors.New("overflow")
	ErrInvalidWireType = errors.New("invalid wire type")
	ErrInvalidBool     = errors.New("decoded bool is neither true nor false")
	ErrStringNotUTF8   = errors.New("decoded string is not UTF-8")
)

Functions

func Append

func Append[T Bytes](w *Writer, v T)

Append writes unprefixed bytes to the writer.

func AppendBool

func AppendBool[T ~bool](w *Writer, b T)

AppendBool writes a boolean to the writer.

func AppendBytes

func AppendBytes[T Bytes](w *Writer, v T)

AppendBytes writes a length-prefixed byte slice to the writer.

func AppendFint32

func AppendFint32[T Int32](w *Writer, v T)

AppendFint32 writes a 32-bit fixed size integer to the writer.

func AppendFint64

func AppendFint64[T Int64](w *Writer, v T)

AppendFint64 writes a 64-bit fixed size integer to the writer.

func AppendInt

func AppendInt[T Int](w *Writer, v T)

AppendInt writes an integer to the writer as a zigzag encoded varint.

func AppendUint added in v0.13.1

func AppendUint[T Uint](w *Writer, v T)

AppendUint writes an unsigned integer to the writer as a varint.

func CountBytes

func CountBytes[T Bytes](bytes []byte, tag T) (uint64, error)

CountBytes counts the consecutive number of length-prefixed fields with the given tag.

func CountInts

func CountInts(bytes []byte) uint64

CountInts counts the number of varints that are encoded in bytes.

func HasNext

func HasNext(r *Reader) bool

HasNext returns true if there are more bytes to read.

func HasPrefix

func HasPrefix[T Bytes](bytes []byte, prefix T) bool

HasPrefix returns true if the bytes start with the given prefix.

func IsZero

func IsZero[T comparable](v T) bool

IsZero returns true if the value is the zero value for its type.

func MakeEntry added in v0.13.1

func MakeEntry[S ~[]E, E any](_ S) (_ E)

MakeEntry returns the zero value of an element in the provided slice.

This function is useful to use in auto-generated code, when the type of a variable is unknown. For example, if we have a variable `v` which we know to be a slice, but we do not know the type of the elements, we can use this function to leverage golang's type inference to create an element.

func MakeEntryNilPointer added in v0.15.0

func MakeEntryNilPointer[S ~[]E, E any](_ S) *E

MakeEntryNilPointer returns a nil pointer to an element in the provided slice.

This function is useful to use in auto-generated code, when the type of a variable is unknown. For example, if we have a variable `v` which we know to be a slice, but we do not know the type of the elements, we can use this function to leverage golang's type inference to create the pointer.

func MakePointer added in v0.5.0

func MakePointer[T any](_ *T) *T

MakePointer creates a new pointer. It is equivalent to `new(T)`.

This function is useful to use in auto-generated code, when the type of a variable is unknown. For example, if we have a variable `v` which we know to be a pointer, but we do not know the type of the pointer, we can use this function to leverage golang's type inference to create the new pointer.

func MakeSlice

func MakeSlice[T any](_ []T, length uint64) []T

MakeSlice creates a new slice with the given length. It is equivalent to `make([]T, length)`.

This function is useful to use in auto-generated code, when the type of a variable is unknown. For example, if we have a variable `v` which we know to be a slice, but we do not know the type of the elements, we can use this function to leverage golang's type inference to create the new slice.

func Marshal added in v0.13.1

func Marshal(s *Spec, a Any) ([]byte, error)

Marshal marshals the given message into bytes based on the specification.

This function is significantly slower than calling MarshalCanoto on the type directly. This function should only be used when the concrete type can not be known ahead of time.

func ReadBool

func ReadBool[T ~bool](r *Reader, v *T) error

ReadBool reads a boolean from the reader.

func ReadBytes

func ReadBytes[T ~[]byte](r *Reader, v *T) error

ReadBytes reads a byte slice from the reader.

func ReadFint32

func ReadFint32[T Int32](r *Reader, v *T) error

ReadFint32 reads a 32-bit fixed size integer from the reader.

func ReadFint64

func ReadFint64[T Int64](r *Reader, v *T) error

ReadFint64 reads a 64-bit fixed size integer from the reader.

func ReadInt

func ReadInt[T Int](r *Reader, v *T) error

ReadInt reads a zigzag encoded integer from the reader.

func ReadString

func ReadString[T ~string](r *Reader, v *T) error

ReadString reads a string from the reader. The string is verified to be valid UTF-8.

func ReadUint added in v0.13.1

func ReadUint[T Uint](r *Reader, v *T) error

ReadUint reads a varint encoded unsigned integer from the reader.

func SizeBytes

func SizeBytes[T Bytes](v T) uint64

SizeBytes calculates the size the length-prefixed bytes would take if written.

func SizeInt

func SizeInt[T Int](v T) uint64

SizeInt calculates the size of an integer when zigzag encoded as a varint.

func SizeUint added in v0.13.1

func SizeUint[T Uint](v T) uint64

SizeUint calculates the size of an unsigned integer when encoded as a varint.

func Tag

func Tag(fieldNumber uint32, wireType WireType) []byte

Tag calculates the tag for a field number and wire type.

This function should not typically be used during marshaling, as tags can be precomputed.

func ValidString added in v0.14.0

func ValidString[T ~string](v T) bool

ValidString returns true if it is valid to encode the provided string. A string is valid to encode if it is valid UTF-8.

Types

type Any added in v0.13.1

type Any struct {
	Fields []AnyField
}

Any is a generic representation of a Canoto message.

func Unmarshal added in v0.13.1

func Unmarshal(s *Spec, b []byte) (Any, error)

Unmarshal unmarshals the given bytes into a message based on the specification.

This function is significantly slower than calling UnmarshalCanoto on the type directly. This function should only be used when the concrete type can not be known ahead of time.

func (Any) MarshalJSON added in v0.13.1

func (a Any) MarshalJSON() ([]byte, error)

type AnyField added in v0.13.1

type AnyField struct {
	Name string

	// Value is the value of the field.
	//
	// It can be any of the following types:
	//   - int64,  []int64
	//   - uint64, []uint64
	//   - bool,   []bool
	//   - string, []string
	//   - []byte, [][]byte
	//   - Any,    []Any
	Value any
}

AnyField is a generic representation of a field in a Canoto message.

type Bytes

type Bytes interface{ ~string | ~[]byte }

type Field

type Field interface {
	// CanotoSpec returns the specification of this canoto message.
	//
	// If there is not a valid specification of this type, it returns nil.
	CanotoSpec(types ...reflect.Type) *Spec
	// MarshalCanotoInto writes the field into a [Writer] and returns the
	// resulting [Writer].
	//
	// It is assumed that CalculateCanotoCache has been called since the
	// last modification to this field.
	//
	// It is assumed that this field is ValidCanoto.
	MarshalCanotoInto(w Writer) Writer
	// CalculateCanotoCache populates internal caches based on the current
	// values in the struct.
	CalculateCanotoCache()
	// CachedCanotoSize returns the previously calculated size of the Canoto
	// representation from CalculateCanotoCache.
	//
	// If CalculateCanotoCache has not yet been called, or the field has
	// been modified since the last call to CalculateCanotoCache, the
	// returned size may be incorrect.
	CachedCanotoSize() uint64
	// UnmarshalCanotoFrom populates the field from a [Reader].
	UnmarshalCanotoFrom(r Reader) error
	// ValidCanoto validates that the field can be correctly marshaled into
	// the Canoto format.
	ValidCanoto() bool
}

Field defines a type that can be included inside of a Canoto message.

type FieldMaker added in v0.7.0

type FieldMaker[T any] interface {
	Field
	MakeCanoto() T
}

FieldMaker is a Field that can create a new value of type T.

The returned value must be able to be unmarshaled into.

This type can be used when implementing a generic Field. However, if T is an interface, it is possible for generated code to compile and panic at runtime.

type FieldPointer added in v0.4.0

type FieldPointer[T any] interface {
	Field
	*T
}

FieldPointer is a pointer to a concrete Field value T.

This type must be used when implementing a value for a generic Field.

type FieldType added in v0.13.1

type FieldType struct {
	FieldNumber    uint32   `canoto:"uint,1"          json:"fieldNumber"`
	Name           string   `canoto:"string,2"        json:"name"`
	FixedLength    uint64   `canoto:"uint,3"          json:"fixedLength,omitempty"`
	Repeated       bool     `canoto:"bool,4"          json:"repeated,omitempty"`
	OneOf          string   `canoto:"string,5"        json:"oneOf,omitempty"`
	TypeInt        SizeEnum `canoto:"uint,6,Type"     json:"typeInt,omitempty"`        // can be any of 8, 16, 32, or 64.
	TypeUint       SizeEnum `canoto:"uint,7,Type"     json:"typeUint,omitempty"`       // can be any of 8, 16, 32, or 64.
	TypeFixedInt   SizeEnum `canoto:"uint,8,Type"     json:"typeFixedInt,omitempty"`   // can be either 32 or 64.
	TypeFixedUint  SizeEnum `canoto:"uint,9,Type"     json:"typeFixedUint,omitempty"`  // can be either 32 or 64.
	TypeBool       bool     `canoto:"bool,10,Type"    json:"typeBool,omitempty"`       // can only be true.
	TypeString     bool     `canoto:"bool,11,Type"    json:"typeString,omitempty"`     // can only be true.
	TypeBytes      bool     `canoto:"bool,12,Type"    json:"typeBytes,omitempty"`      // can only be true.
	TypeFixedBytes uint64   `canoto:"uint,13,Type"    json:"typeFixedBytes,omitempty"` // length of the fixed bytes.
	TypeRecursive  uint64   `canoto:"uint,14,Type"    json:"typeRecursive,omitempty"`  // depth of the recursion.
	TypeMessage    *Spec    `canoto:"pointer,15,Type" json:"typeMessage,omitempty"`
	// contains filtered or unexported fields
}

FieldType is the specification of a field in a Canoto message.

func FieldTypeFromField added in v0.13.1

func FieldTypeFromField[T Field](
	field T,
	fieldNumber uint32,
	name string,
	fixedLength uint64,
	repeated bool,
	oneOf string,
	types []reflect.Type,
) FieldType

FieldTypeFromField creates a FieldType from a field.

func FieldTypeFromFint added in v0.13.1

func FieldTypeFromFint[T integer](
	field T,
	fieldNumber uint32,
	name string,
	fixedLength uint64,
	repeated bool,
	oneOf string,
) FieldType

FieldTypeFromFint creates a FieldType from a fixed-length integer.

func (*FieldType) CachedCanotoSize added in v0.13.1

func (c *FieldType) CachedCanotoSize() uint64

CachedCanotoSize returns the previously calculated size of the Canoto representation from CalculateCanotoCache.

If CalculateCanotoCache has not yet been called, it will return 0.

If the struct has been modified since the last call to CalculateCanotoCache, the returned size may be incorrect.

func (*FieldType) CachedWhichOneOfType added in v0.13.1

func (c *FieldType) CachedWhichOneOfType() uint32

CachedWhichOneOfType returns the previously calculated field number used to represent Type.

This field is cached by UnmarshalCanoto, UnmarshalCanotoFrom, and CalculateCanotoCache.

If the field has not yet been cached, it will return 0.

If the struct has been modified since the field was last cached, the returned field number may be incorrect.

func (*FieldType) CalculateCanotoCache added in v0.13.1

func (c *FieldType) CalculateCanotoCache()

CalculateCanotoCache populates size and OneOf caches based on the current values in the struct.

It is not safe to copy this struct concurrently.

func (*FieldType) CanotoSpec added in v0.13.1

func (*FieldType) CanotoSpec(types ...reflect.Type) *Spec

CanotoSpec returns the specification of this canoto message.

func (*FieldType) MakeCanoto added in v0.13.1

func (*FieldType) MakeCanoto() *FieldType

MakeCanoto creates a new empty value.

func (*FieldType) MarshalCanoto added in v0.13.1

func (c *FieldType) MarshalCanoto() []byte

MarshalCanoto returns the Canoto representation of this struct.

It is assumed that this struct is ValidCanoto.

It is not safe to copy this struct concurrently.

func (*FieldType) MarshalCanotoInto added in v0.13.1

func (c *FieldType) MarshalCanotoInto(w Writer) Writer

MarshalCanotoInto writes the struct into a Writer and returns the resulting Writer. Most users should just use MarshalCanoto.

It is assumed that CalculateCanotoCache has been called since the last modification to this struct.

It is assumed that this struct is ValidCanoto.

It is not safe to copy this struct concurrently.

func (*FieldType) UnmarshalCanoto added in v0.13.1

func (c *FieldType) UnmarshalCanoto(bytes []byte) error

UnmarshalCanoto unmarshals a Canoto-encoded byte slice into the struct.

During parsing, the canoto cache is saved.

func (*FieldType) UnmarshalCanotoFrom added in v0.13.1

func (c *FieldType) UnmarshalCanotoFrom(r Reader) error

UnmarshalCanotoFrom populates the struct from a Reader. Most users should just use UnmarshalCanoto.

During parsing, the canoto cache is saved.

This function enables configuration of reader options.

func (*FieldType) ValidCanoto added in v0.13.1

func (c *FieldType) ValidCanoto() bool

ValidCanoto validates that the struct can be correctly marshaled into the Canoto format.

Specifically, ValidCanoto ensures: 1. All OneOfs are specified at most once. 2. All strings are valid utf-8. 3. All custom fields are ValidCanoto.

type Int

type Int interface {
	~int8 | ~int16 | ~int32 | ~int64
}

type Int32

type Int32 interface{ ~int32 | ~uint32 }

type Int64

type Int64 interface{ ~int64 | ~uint64 }

type Message

type Message interface {
	Field
	// MarshalCanoto returns the Canoto representation of this message.
	//
	// It is assumed that this message is ValidCanoto.
	MarshalCanoto() []byte
	// UnmarshalCanoto unmarshals a Canoto-encoded byte slice into the
	// message.
	UnmarshalCanoto(bytes []byte) error
}

Message defines a type that can be a stand-alone Canoto message.

type Reader

type Reader struct {
	B      []byte
	Unsafe bool
	// Context is a user-defined value that can be used to pass additional
	// state during the unmarshaling process.
	Context any
}

Reader contains all the state needed to unmarshal a Canoto type.

The functions in this package are not methods on the Reader type to enable the usage of generics.

type SizeEnum added in v0.13.1

type SizeEnum uint8

SizeEnum indicate the size of an integer type in canoto specifications.

func SizeOf added in v0.13.1

func SizeOf[T integer](_ T) SizeEnum

SizeOf returns the size of the integer type.

func (SizeEnum) FixedWireType added in v0.13.1

func (s SizeEnum) FixedWireType() (WireType, bool)

func (SizeEnum) NumBytes added in v0.13.1

func (s SizeEnum) NumBytes() (uint64, bool)

type Spec added in v0.13.1

type Spec struct {
	Name   string      `canoto:"string,1"         json:"name"`
	Fields []FieldType `canoto:"repeated value,2" json:"fields"`
	// contains filtered or unexported fields
}

Spec is the specification of a Canoto message.

Given a message specification, Unmarshal can be used to parse bytes into an Any.

Spec is itself a message, to allow for implementations of universal canoto message interpreters.

func (*Spec) CachedCanotoSize added in v0.13.1

func (c *Spec) CachedCanotoSize() uint64

CachedCanotoSize returns the previously calculated size of the Canoto representation from CalculateCanotoCache.

If CalculateCanotoCache has not yet been called, it will return 0.

If the struct has been modified since the last call to CalculateCanotoCache, the returned size may be incorrect.

func (*Spec) CalculateCanotoCache added in v0.13.1

func (c *Spec) CalculateCanotoCache()

CalculateCanotoCache populates size and OneOf caches based on the current values in the struct.

It is not safe to copy this struct concurrently.

func (*Spec) CanotoSpec added in v0.13.1

func (*Spec) CanotoSpec(types ...reflect.Type) *Spec

CanotoSpec returns the specification of this canoto message.

func (*Spec) MakeCanoto added in v0.13.1

func (*Spec) MakeCanoto() *Spec

MakeCanoto creates a new empty value.

func (*Spec) MarshalCanoto added in v0.13.1

func (c *Spec) MarshalCanoto() []byte

MarshalCanoto returns the Canoto representation of this struct.

It is assumed that this struct is ValidCanoto.

It is not safe to copy this struct concurrently.

func (*Spec) MarshalCanotoInto added in v0.13.1

func (c *Spec) MarshalCanotoInto(w Writer) Writer

MarshalCanotoInto writes the struct into a Writer and returns the resulting Writer. Most users should just use MarshalCanoto.

It is assumed that CalculateCanotoCache has been called since the last modification to this struct.

It is assumed that this struct is ValidCanoto.

It is not safe to copy this struct concurrently.

func (*Spec) UnmarshalCanoto added in v0.13.1

func (c *Spec) UnmarshalCanoto(bytes []byte) error

UnmarshalCanoto unmarshals a Canoto-encoded byte slice into the struct.

During parsing, the canoto cache is saved.

func (*Spec) UnmarshalCanotoFrom added in v0.13.1

func (c *Spec) UnmarshalCanotoFrom(r Reader) error

UnmarshalCanotoFrom populates the struct from a Reader. Most users should just use UnmarshalCanoto.

During parsing, the canoto cache is saved.

This function enables configuration of reader options.

func (*Spec) ValidCanoto added in v0.13.1

func (c *Spec) ValidCanoto() bool

ValidCanoto validates that the struct can be correctly marshaled into the Canoto format.

Specifically, ValidCanoto ensures: 1. All OneOfs are specified at most once. 2. All strings are valid utf-8. 3. All custom fields are ValidCanoto.

type Uint

type Uint interface {
	~uint8 | ~uint16 | ~uint32 | ~uint64
}

type WireType

type WireType byte

WireType represents the Proto wire description of a field. Within Proto it is used to provide forwards compatibility. For Canoto, it exists to provide compatibility with Proto.

func ReadTag

func ReadTag(r *Reader) (uint32, WireType, error)

ReadTag reads the next field number and wire type from the reader.

func (WireType) IsValid

func (w WireType) IsValid() bool

func (WireType) String

func (w WireType) String() string

type Writer

type Writer struct {
	B []byte
}

Writer contains all the state needed to marshal a Canoto type.

The functions in this package are not methods on the Writer type to enable the usage of generics.

Directories

Path Synopsis
Canoto is a command to generate code for reading and writing the canoto format.
Canoto is a command to generate code for reading and writing the canoto format.
cli module
Generate exposes functionality to generate code for reading and writing the canoto format.
Generate exposes functionality to generate code for reading and writing the canoto format.
big
canoto
Canoto provides common functionality required for reading and writing the canoto format.
Canoto provides common functionality required for reading and writing the canoto format.
pb

Jump to

Keyboard shortcuts

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