Skip to content

Commit

Permalink
Add local mode and nonlocal field tag
Browse files Browse the repository at this point in the history
Some of the fields may only be needed for over-the-network transfer,
but can be skipped when encoding the object locally. For instance, if
the object is stored in a blob field in the database, but some parts
of it also are stored in other columns in the same row, we can save
some space by omitting these fields in the blobs.

This is achieved by adding `nonlocal` field tag, `WithEncodeLocal()`
encoder option and `WithDecodeLocal()` decoder option. The encoder and
the decoder skip fields with `nonlocal` tag when they're in the local
mode.
  • Loading branch information
ivan4th committed Jun 28, 2024
1 parent 4956740 commit 9fcc6f2
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 23 deletions.
18 changes: 16 additions & 2 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,15 @@ func WithDecodeMaxNested(nested uint) decoderOpts {
// WithDecodeMaxElements sets the maximum number of elements allowed in a collection.
// The default value is 1 << 20.
func WithDecodeMaxElements(elements uint32) decoderOpts {
return func(e *Decoder) {
e.maxElements = elements
return func(d *Decoder) {
d.maxElements = elements

Check warning on line 48 in decoder.go

View check run for this annotation

Codecov / codecov/patch

decoder.go#L47-L48

Added lines #L47 - L48 were not covered by tests
}
}

// WithDecodeLocal instructs the decoder to skip decoding of fields with the `nonlocal` tag.
func WithDecodeLocal() decoderOpts {
return func(d *Decoder) {
d.local = true

Check warning on line 55 in decoder.go

View check run for this annotation

Codecov / codecov/patch

decoder.go#L53-L55

Added lines #L53 - L55 were not covered by tests
}
}

Expand All @@ -67,6 +74,7 @@ type Decoder struct {
scratch [9]byte
maxNested uint
maxElements uint32
local bool
}

func (d *Decoder) enterNested() error {
Expand All @@ -85,6 +93,12 @@ func (d *Decoder) read(buf []byte) (int, error) {
return io.ReadFull(d.r, buf)
}

// Local returns true if the decoder is using local mode, that is, skipping fields with
// the `nonlocal` tag.
func (d *Decoder) Local() bool {
return d.local

Check warning on line 99 in decoder.go

View check run for this annotation

Codecov / codecov/patch

decoder.go#L98-L99

Added lines #L98 - L99 were not covered by tests
}

func DecodeByte(d *Decoder) (byte, int, error) {
n, err := d.read(d.scratch[:1])
if err != nil {
Expand Down
14 changes: 14 additions & 0 deletions encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ func WithEncodeMaxElements(elements uint32) encoderOpts {
}
}

// WithEncodeLocal instructs the encoder to skip encoding of fields with the `nonlocal` tag.
func WithEncodeLocal() encoderOpts {
return func(e *Encoder) {
e.local = true

Check warning on line 58 in encoder.go

View check run for this annotation

Codecov / codecov/patch

encoder.go#L56-L58

Added lines #L56 - L58 were not covered by tests
}
}

// NewEncoder returns a new encoder that writes to w.
// If w implements io.StringWriter, the returned encoder will be more efficient in encoding strings.
func NewEncoder(w io.Writer, opts ...encoderOpts) *Encoder {
Expand All @@ -71,6 +78,7 @@ type Encoder struct {
scratch [9]byte
maxNested uint
maxElements uint32
local bool
}

func (e *Encoder) enterNested() error {
Expand All @@ -85,6 +93,12 @@ func (e *Encoder) leaveNested() {
e.maxNested++
}

// Local returns true if the encoder is using local mode, that is, skipping fields with
// the `nonlocal` tag.
func (e *Encoder) Local() bool {
return e.local

Check warning on line 99 in encoder.go

View check run for this annotation

Codecov / codecov/patch

encoder.go#L98-L99

Added lines #L98 - L99 were not covered by tests
}

func EncodeByteSlice(e *Encoder, value []byte) (int, error) {
return EncodeByteSliceWithLimit(e, value, e.maxElements)
}
Expand Down
8 changes: 8 additions & 0 deletions examples/nonlocal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package examples

//go:generate scalegen

type StructWithNonLocalField struct {
Name string `scale:"max=20"`
SomeID string `scale:"nonlocal, max=20"`
}
46 changes: 46 additions & 0 deletions examples/nonlocal_scale.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 43 additions & 0 deletions examples/nonlocal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package examples

import (
"bytes"
"testing"

"github.com/stretchr/testify/require"

"github.com/spacemeshos/go-scale"
)

func TestNonLocal(t *testing.T) {
s := StructWithNonLocalField{
Name: "foo",
SomeID: "bar",
}

buf := bytes.NewBuffer(nil)
encoder := scale.NewEncoder(buf)
n, err := s.EncodeScale(encoder)
require.NoError(t, err)
require.Equal(t, 8, n)

decoder := scale.NewDecoder(bytes.NewReader(buf.Bytes()))
var s1 StructWithNonLocalField
n, err = s1.DecodeScale(decoder)
require.NoError(t, err)
require.Equal(t, 8, n)
require.Equal(t, s, s1)

buf = bytes.NewBuffer(nil)
encoder = scale.NewEncoder(buf, scale.WithEncodeLocal())
n, err = s.EncodeScale(encoder)
require.NoError(t, err)
require.Equal(t, 4, n)

decoder = scale.NewDecoder(bytes.NewReader(buf.Bytes()), scale.WithDecodeLocal())
var s2 StructWithNonLocalField
n, err = s2.DecodeScale(decoder)
require.NoError(t, err)
require.Equal(t, 4, n)
require.Equal(t, StructWithNonLocalField{Name: "foo"}, s2)
}
32 changes: 29 additions & 3 deletions generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ type temp struct {
decode string
}

type action int

const (
encode = iota
encode action = iota
decode
)

Expand Down Expand Up @@ -119,7 +121,7 @@ var (
}
)

func getAction(tm temp, action int) string {
func getAction(tm temp, action action) string {

Check warning on line 124 in generate.go

View check run for this annotation

Codecov / codecov/patch

generate.go#L124

Added line #L124 was not covered by tests
switch action {
case encode:
return tm.encode
Expand Down Expand Up @@ -266,6 +268,7 @@ type scaleType struct {
Args string
EncodeModifier string
DecodeModifier string
NonLocal bool
}

func getDecodeModifier(parentType reflect.Type, field reflect.StructField) string {
Expand All @@ -277,6 +280,18 @@ func getDecodeModifier(parentType reflect.Type, field reflect.StructField) strin
}

func getScaleType(parentType reflect.Type, field reflect.StructField) (scaleType, error) {
st, err := getScaleTypeInner(parentType, field)
if err != nil {
return scaleType{}, err

Check warning on line 285 in generate.go

View check run for this annotation

Codecov / codecov/patch

generate.go#L285

Added line #L285 was not covered by tests
}
st.NonLocal, err = nonLocal(field.Tag)
if err != nil {
return scaleType{}, fmt.Errorf("getting tags: %w", err)

Check warning on line 289 in generate.go

View check run for this annotation

Codecov / codecov/patch

generate.go#L289

Added line #L289 was not covered by tests
}
return st, nil
}

func getScaleTypeInner(parentType reflect.Type, field reflect.StructField) (scaleType, error) {
decodeModifier := getDecodeModifier(parentType, field)
encodableType := reflect.TypeOf((*Encodable)(nil)).Elem()

Expand Down Expand Up @@ -374,7 +389,7 @@ func getTemplate(stype scaleType) temp {
}
}

func executeAction(action int, w io.Writer, tc *typeContext) error {
func executeAction(action action, w io.Writer, tc *typeContext) error {

Check warning on line 392 in generate.go

View check run for this annotation

Codecov / codecov/patch

generate.go#L392

Added line #L392 was not covered by tests
typ := tc.Type

tpl, err := template.New("").Parse(getAction(start, action))
Expand All @@ -396,6 +411,17 @@ func executeAction(action int, w io.Writer, tc *typeContext) error {
return fmt.Errorf("getting scale type for %s: %w", typ, err)
}

if scaleType.NonLocal {
switch action {
case encode:
w.Write([]byte("if !enc.Local() "))
case decode:
w.Write([]byte("if !dec.Local() "))
default:
panic("BUG: bad action")

Check warning on line 421 in generate.go

View check run for this annotation

Codecov / codecov/patch

generate.go#L414-L421

Added lines #L414 - L421 were not covered by tests
}
}

tctx := &typeContext{
Name: field.Name,
Type: field.Type,
Expand Down
Loading

0 comments on commit 9fcc6f2

Please sign in to comment.