codec

package
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2024 License: MIT Imports: 15 Imported by: 8

Documentation

Overview

Example

Example demonstrates how to use the codec package. It will make use of each Modifier provided in the package, along with their config.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"math"
	"time"

	"github.com/smartcontractkit/chainlink-common/pkg/codec"
	"github.com/smartcontractkit/chainlink-common/pkg/types"
)

// This example demonstrates how you can have config for itemTypes with one codec,
// one that is modified before encoding for on-chain and modified after decoding for off-chain
// the other is left unmodified during encoding and decoding.
const (
	anyUnmodifiedTypeName     = "Unmodified"
	anyModifiedStructTypeName = "SecondItem"
	anyExtractorTypeName      = "ExtractProperty"
)

var _ types.RemoteCodec = &ExampleStructJSONCodec{}

type ExampleStructJSONCodec struct{}

func (ExampleStructJSONCodec) Encode(_ context.Context, item any, _ string) ([]byte, error) {
	return json.Marshal(item)
}

func (ExampleStructJSONCodec) GetMaxEncodingSize(_ context.Context, n int, _ string) (int, error) {
	// not used in the example, and not really valid for json.
	return math.MaxInt32, nil
}

func (ExampleStructJSONCodec) Decode(_ context.Context, raw []byte, into any, _ string) error {
	err := json.Unmarshal(raw, into)
	if err != nil {
		return fmt.Errorf("%w: %s", types.ErrInvalidType, err)
	}
	return nil
}

func (ExampleStructJSONCodec) GetMaxDecodingSize(ctx context.Context, n int, _ string) (int, error) {
	// not used in the example, and not really valid for json.
	return math.MaxInt32, nil
}

func (ExampleStructJSONCodec) CreateType(_ string, _ bool) (any, error) {
	// parameters here are unused in the example, but can be used to determine what type to expect.
	// this allows remote execution to know how to decode the incoming message
	// and for [codec.NewModifierCodec] to know what type to expect for intermediate phases.
	return &OnChainStruct{}, nil
}

type OnChainStruct struct {
	Aa int64
	Bb string
	Cc bool
	Dd string
	Ee int64
	Ff string
}

const config = `
[
  { "Type" : "drop", "Fields" :  ["Bb"] },
  { "Type" : "hard code", "OnChainValues" : {"Ff" :  "dog", "Bb" : "bb"}, "OffChainValues" : {"Zz" : "foo"}},
  { "Type" : "rename", "Fields" :  {"Aa" :  "Bb"}},
  { "Type" : "extract element", "Extractions" :  {"Dd" :  "middle"}},
  { "Type" : "epoch to time", "Fields" :  ["Ee"]}
]
`
const extractConfig = `[
  { "Type" : "extract property", "FieldName" : "Bb" }
]`

// config converts the OnChainStruct to this structure
type OffChainStruct struct {
	Bb int64
	Cc bool
	Dd []string
	Ee *time.Time
	Zz string
}

// Example demonstrates how to use the codec package.
// It will make use of each [Modifier] provided in the package, along with their config.
func main() {
	mods, err := createModsFromConfig()
	if err != nil {
		fmt.Println(err)
		return
	}

	c, err := codec.NewModifierCodec(&ExampleStructJSONCodec{}, mods)
	if err != nil {
		fmt.Println(err)
		return
	}

	input := &OnChainStruct{
		Aa: 10,
		Bb: "20",
		Cc: true,
		Dd: "great example",
		Ee: 631515600,
		Ff: "dog",
	}

	ctx := context.Background()
	b, err := c.Encode(ctx, input, anyUnmodifiedTypeName)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println("Encoded: " + string(b))

	output := &OnChainStruct{}
	if err = c.Decode(ctx, b, output, anyUnmodifiedTypeName); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("Decoded: %+v\n", output)

	anyTimeEpoch := int64(631515600)
	t := time.Unix(anyTimeEpoch, 0)
	modifedInput := &OffChainStruct{
		Bb: 10,
		Cc: true,
		Dd: []string{"terrible example", "great example", "not this one :("},
		Ee: &t,
		Zz: "foo",
	}

	b, err = c.Encode(ctx, modifedInput, anyModifiedStructTypeName)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("Encoded with modifications: " + string(b))

	output2 := &OffChainStruct{}
	if err = c.Decode(ctx, b, output2, anyModifiedStructTypeName); err != nil {
		fmt.Println(err)
		return
	}

	fmt.Printf("Decoded with modifications: %+v\n", output2)

	// using Encode for the extractor is a lossy operation and should not be used on writes
	if b, err = c.Encode(ctx, "test", anyExtractorTypeName); err != nil {
		fmt.Println(err)
		return
	}

	fmt.Printf("Encoded for Extraction: %+v\n", string(b))

	var extractedVal string
	if err = c.Decode(ctx, b, &extractedVal, anyExtractorTypeName); err != nil {
		fmt.Println(err)
		return
	}

	fmt.Printf("Decoded to extracted value: %+v\n", extractedVal)
}

func createModsFromConfig() (codec.Modifier, error) {
	modifierConfig := &codec.ModifiersConfig{}
	if err := json.Unmarshal([]byte(config), modifierConfig); err != nil {
		return nil, err
	}

	mod, err := modifierConfig.ToModifier()
	if err != nil {
		return nil, err
	}

	extractorConfig := &codec.ModifiersConfig{}
	if err = json.Unmarshal([]byte(extractConfig), extractorConfig); err != nil {
		return nil, err
	}

	exMod, err := extractorConfig.ToModifier()
	if err != nil {
		return nil, err
	}

	modByItemType := map[string]codec.Modifier{
		anyModifiedStructTypeName: mod,
		anyUnmodifiedTypeName:     codec.MultiModifier{},
		anyExtractorTypeName:      exMod,
	}

	return codec.NewByItemTypeModifier(modByItemType)
}
Output:

Encoded: {"Aa":10,"Bb":"20","Cc":true,"Dd":"great example","Ee":631515600,"Ff":"dog"}
Decoded: &{Aa:10 Bb:20 Cc:true Dd:great example Ee:631515600 Ff:dog}
Encoded with modifications: {"Aa":10,"Bb":"","Cc":true,"Dd":"great example","Ee":631515600,"Ff":"dog"}
Decoded with modifications: &{Bb:10 Cc:true Dd:[great example] Ee:1990-01-05 05:00:00 +0000 UTC Zz:foo}
Encoded for Extraction: {"Aa":0,"Bb":"test","Cc":false,"Dd":"","Ee":0,"Ff":""}
Decoded to extracted value: test

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func BigIntHook

func BigIntHook(_, to reflect.Type, data any) (any, error)

BigIntHook is a mapstructure hook that converts number types to *math/big.Int and vice versa. Float values are cast to an int64 before being converted to a *math/big.Int.

func Convert

func Convert(from, to reflect.Value, hook mapstructure.DecodeHookFunc) error

Convert uses mapstructure and the hook provided to convert from into to. Note that the use of mapstructure is avoided if to and from are the same type, and are both pointers, or if to is a pointer to the type from is. In those cases, to is simply set to from, or to point to it. Arrays and slices are converted by converting each element using mapstructure.

func EpochToTimeHook

func EpochToTimeHook(from reflect.Type, to reflect.Type, data any) (any, error)

EpochToTimeHook is a mapstructure hook that converts a unix epoch to a time.Time and vice versa. To do this, time.Unix and time.Time.Unix are used.

func FitsInNBitsSigned

func FitsInNBitsSigned(n int, bi *big.Int) bool

FitsInNBitsSigned returns if the *math/big.Int can fit in n bits as a signed integer. Namely, if it's in the range [-2^(n-1), 2^(n-1) - 1]

func NewModifierCodec

func NewModifierCodec(codec types.RemoteCodec, modifier Modifier, hooks ...mapstructure.DecodeHookFunc) (types.RemoteCodec, error)

NewModifierCodec returns a codec that calls the modifier before calling codec functions. hooks are applied to the mapstructure decoding when Encode or Decode is called.

func NumberHook

func NumberHook(from reflect.Type, to reflect.Type, val any) (any, error)

func SliceToArrayVerifySizeHook

func SliceToArrayVerifySizeHook(from reflect.Type, to reflect.Type, data any) (any, error)

SliceToArrayVerifySizeHook is a mapstructure hook that verifies if a slice is being assigned to an array, it will have the same number of elements. The default in mapstructure is to allow the slice to be smaller and will zero out the remaining elements in that case.

Types

type DropModifierConfig

type DropModifierConfig struct {
	Fields []string
}

DropModifierConfig drops all fields listed. The casing of the first character is ignored to allow compatibility. Note that unused fields are ignored by types.Codec. This is only required if you want to rename a field to an already used name. For example, if a struct has fields A and B, and you want to rename A to B, then you need to either also rename B or drop it.

func (*DropModifierConfig) MarshalJSON

func (d *DropModifierConfig) MarshalJSON() ([]byte, error)

func (*DropModifierConfig) ToModifier

type ElementExtractorLocation

type ElementExtractorLocation int

ElementExtractorLocation is used to determine which element to extract from a slice or array. The default is ElementExtractorLocationMiddle, which will extract the middle element. valid json values are "first", "middle", and "last".

const (
	ElementExtractorLocationFirst ElementExtractorLocation = iota
	ElementExtractorLocationMiddle
	ElementExtractorLocationLast
	ElementExtractorLocationDefault = ElementExtractorLocationMiddle
)

func (ElementExtractorLocation) MarshalJSON

func (e ElementExtractorLocation) MarshalJSON() ([]byte, error)

func (*ElementExtractorLocation) UnmarshalJSON

func (e *ElementExtractorLocation) UnmarshalJSON(b []byte) error

type ElementExtractorModifierConfig

type ElementExtractorModifierConfig struct {
	// Key is the name of the field to extract from and the value is which element to extract.
	Extractions map[string]*ElementExtractorLocation
}

ElementExtractorModifierConfig is used to extract an element from a slice or array

func (*ElementExtractorModifierConfig) MarshalJSON

func (e *ElementExtractorModifierConfig) MarshalJSON() ([]byte, error)

func (*ElementExtractorModifierConfig) ToModifier

type EpochToTimeModifierConfig

type EpochToTimeModifierConfig struct {
	Fields []string
}

EpochToTimeModifierConfig is used to convert epoch seconds as uint64 fields on-chain to time.Time

func (*EpochToTimeModifierConfig) MarshalJSON

func (e *EpochToTimeModifierConfig) MarshalJSON() ([]byte, error)

func (*EpochToTimeModifierConfig) ToModifier

type HardCodeModifierConfig

type HardCodeModifierConfig struct {
	OnChainValues  map[string]any
	OffChainValues map[string]any
}

HardCodeModifierConfig is used to hard code values into the map. Note that hard-coding values will override other values.

func (*HardCodeModifierConfig) MarshalJSON

func (h *HardCodeModifierConfig) MarshalJSON() ([]byte, error)

func (*HardCodeModifierConfig) ToModifier

func (h *HardCodeModifierConfig) ToModifier(onChainHooks ...mapstructure.DecodeHookFunc) (Modifier, error)

type Modifier

type Modifier interface {
	RetypeToOffChain(onChainType reflect.Type, itemType string) (reflect.Type, error)

	// TransformToOnChain transforms a type returned from AdjustForInput into the outputType.
	// You may also pass a pointer to the type returned by AdjustForInput to get a pointer to outputType.
	TransformToOnChain(offChainValue any, itemType string) (any, error)

	// TransformToOffChain is the reverse of TransformForOnChain input.
	// It is used to send back the object after it has been decoded
	TransformToOffChain(onChainValue any, itemType string) (any, error)
}

Modifier allows you to modify the off-chain type to be used on-chain, and vice-versa. A modifier is set up by retyping the on-chain type to a type used off-chain.

func NewByItemTypeModifier

func NewByItemTypeModifier(modByItemType map[string]Modifier) (Modifier, error)

NewByItemTypeModifier returns a Modifier that uses modByItemType to determine which Modifier to use for a given itemType.

func NewElementExtractor

func NewElementExtractor(fields map[string]*ElementExtractorLocation) Modifier

NewElementExtractor creates a modifier that extracts an element from a slice or array. fields is used to determine which fields to extract elements from and which element to extract. This modifier is lossy, as TransformToOffChain will always return a slice of length 1 with the single element, so calling TransformToOnChain, then TransformToOffChain will not return the original value, if it has multiple elements.

func NewEpochToTimeModifier

func NewEpochToTimeModifier(fields []string) Modifier

NewEpochToTimeModifier converts all fields from time.Time off-chain to int64.

func NewHardCoder

func NewHardCoder(onChain map[string]any, offChain map[string]any, hooks ...mapstructure.DecodeHookFunc) (Modifier, error)

NewHardCoder creates a modifier that will hard-code values for on-chain and off-chain types The modifier will override any values of the same name, if you need an overwritten value to be used in a different field, NewRenamer must be used before NewHardCoder.

func NewPropertyExtractor

func NewPropertyExtractor(fieldName string) Modifier

NewPropertyExtractor creates a modifier that will extract a single property from a struct. This modifier is lossy, as TransformToOffchain will discard unwanted struct properties and return a single element. Calling TransformToOnchain will result in unset properties.

func NewRenamer

func NewRenamer(fields map[string]string) Modifier

type ModifierConfig

type ModifierConfig interface {
	ToModifier(onChainHooks ...mapstructure.DecodeHookFunc) (Modifier, error)
}

type ModifierType

type ModifierType string
const (
	ModifierRename          ModifierType = "rename"
	ModifierDrop            ModifierType = "drop"
	ModifierHardCode        ModifierType = "hard code"
	ModifierExtractElement  ModifierType = "extract element"
	ModifierEpochToTime     ModifierType = "epoch to time"
	ModifierExtractProperty ModifierType = "extract property"
)

type ModifiersConfig

type ModifiersConfig []ModifierConfig

ModifiersConfig unmarshalls as a list of ModifierConfig by using a field called Type The values available for Type are case-insensitive and the config they require are below: - rename -> RenameModifierConfig - drop -> DropModifierConfig - hard code -> HardCodeModifierConfig - extract element -> ElementExtractorModifierConfig - epoch to time -> EpochToTimeModifierConfig

func (*ModifiersConfig) ToModifier

func (m *ModifiersConfig) ToModifier(onChainHooks ...mapstructure.DecodeHookFunc) (Modifier, error)

func (*ModifiersConfig) UnmarshalJSON

func (m *ModifiersConfig) UnmarshalJSON(data []byte) error

type MultiModifier

type MultiModifier []Modifier

MultiModifier is a Modifier that applies each element for the slice in-order (reverse order for TransformForOnChain).

func (MultiModifier) RetypeToOffChain

func (c MultiModifier) RetypeToOffChain(onChainType reflect.Type, itemType string) (reflect.Type, error)

func (MultiModifier) TransformToOffChain

func (c MultiModifier) TransformToOffChain(onChainValue any, itemType string) (any, error)

func (MultiModifier) TransformToOnChain

func (c MultiModifier) TransformToOnChain(offChainValue any, itemType string) (any, error)

type Number

type Number string

func (Number) Float64

func (n Number) Float64() (float64, error)

func (Number) Int64

func (n Number) Int64() (int64, error)

func (Number) MarshalCBOR

func (n Number) MarshalCBOR() ([]byte, error)

func (Number) MarshalJSON

func (n Number) MarshalJSON() ([]byte, error)

func (*Number) UnmarshalCBOR

func (n *Number) UnmarshalCBOR(data []byte) error

func (*Number) UnmarshalJSON

func (n *Number) UnmarshalJSON(data []byte) error

type PathMappingError

type PathMappingError struct {
	Err  error
	Path string
}

func (PathMappingError) Cause

func (e PathMappingError) Cause() error

func (PathMappingError) Error

func (e PathMappingError) Error() string

type PropertyExtractorConfig

type PropertyExtractorConfig struct {
	FieldName string
}

func (*PropertyExtractorConfig) MarshalJSON

func (c *PropertyExtractorConfig) MarshalJSON() ([]byte, error)

func (*PropertyExtractorConfig) ToModifier

type RenameModifierConfig

type RenameModifierConfig struct {
	Fields map[string]string
}

RenameModifierConfig renames all fields in the map from the key to the value The casing of the first character is ignored to allow compatibility of go convention for public fields and on-chain names.

func (*RenameModifierConfig) MarshalJSON

func (r *RenameModifierConfig) MarshalJSON() ([]byte, error)

func (*RenameModifierConfig) ToModifier

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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