Skip to content

Commit 9f809b7

Browse files
committed
Add local mode and nonlocal field tag
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.
1 parent 4956740 commit 9f809b7

11 files changed

Lines changed: 269 additions & 23 deletions

File tree

decoder.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,15 @@ func WithDecodeMaxNested(nested uint) decoderOpts {
4444
// WithDecodeMaxElements sets the maximum number of elements allowed in a collection.
4545
// The default value is 1 << 20.
4646
func WithDecodeMaxElements(elements uint32) decoderOpts {
47-
return func(e *Decoder) {
48-
e.maxElements = elements
47+
return func(d *Decoder) {
48+
d.maxElements = elements
49+
}
50+
}
51+
52+
// WithDecodeLocal instructs the decoder to skip decoding of fields with the `nonlocal` tag.
53+
func WithDecodeLocal() decoderOpts {
54+
return func(d *Decoder) {
55+
d.local = true
4956
}
5057
}
5158

@@ -67,6 +74,7 @@ type Decoder struct {
6774
scratch [9]byte
6875
maxNested uint
6976
maxElements uint32
77+
local bool
7078
}
7179

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

96+
// Local returns true if the decoder is using local mode, that is, skipping fields with
97+
// the `nonlocal` tag.
98+
func (d *Decoder) Local() bool {
99+
return d.local
100+
}
101+
88102
func DecodeByte(d *Decoder) (byte, int, error) {
89103
n, err := d.read(d.scratch[:1])
90104
if err != nil {

encoder.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ func WithEncodeMaxElements(elements uint32) encoderOpts {
5252
}
5353
}
5454

55+
// WithEncodeLocal instructs the encoder to skip encoding of fields with the `nonlocal` tag.
56+
func WithEncodeLocal() encoderOpts {
57+
return func(e *Encoder) {
58+
e.local = true
59+
}
60+
}
61+
5562
// NewEncoder returns a new encoder that writes to w.
5663
// If w implements io.StringWriter, the returned encoder will be more efficient in encoding strings.
5764
func NewEncoder(w io.Writer, opts ...encoderOpts) *Encoder {
@@ -71,6 +78,7 @@ type Encoder struct {
7178
scratch [9]byte
7279
maxNested uint
7380
maxElements uint32
81+
local bool
7482
}
7583

7684
func (e *Encoder) enterNested() error {
@@ -85,6 +93,12 @@ func (e *Encoder) leaveNested() {
8593
e.maxNested++
8694
}
8795

96+
// Local returns true if the encoder is using local mode, that is, skipping fields with
97+
// the `nonlocal` tag.
98+
func (e *Encoder) Local() bool {
99+
return e.local
100+
}
101+
88102
func EncodeByteSlice(e *Encoder, value []byte) (int, error) {
89103
return EncodeByteSliceWithLimit(e, value, e.maxElements)
90104
}

examples/nonlocal.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package examples
2+
3+
//go:generate scalegen
4+
5+
type StructWithNonLocalField struct {
6+
Name string `scale:"max=20"`
7+
SomeID string `scale:"nonlocal,max=20"`
8+
}

examples/nonlocal_scale.go

Lines changed: 46 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/nonlocal_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package examples
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
9+
"github.com/spacemeshos/go-scale"
10+
)
11+
12+
func TestNonLocal(t *testing.T) {
13+
s := StructWithNonLocalField{
14+
Name: "foo",
15+
SomeID: "bar",
16+
}
17+
18+
buf := bytes.NewBuffer(nil)
19+
encoder := scale.NewEncoder(buf)
20+
n, err := s.EncodeScale(encoder)
21+
require.NoError(t, err)
22+
require.Equal(t, 8, n)
23+
24+
decoder := scale.NewDecoder(bytes.NewReader(buf.Bytes()))
25+
var s1 StructWithNonLocalField
26+
n, err = s1.DecodeScale(decoder)
27+
require.NoError(t, err)
28+
require.Equal(t, 8, n)
29+
require.Equal(t, s, s1)
30+
31+
buf = bytes.NewBuffer(nil)
32+
encoder = scale.NewEncoder(buf, scale.WithEncodeLocal())
33+
n, err = s.EncodeScale(encoder)
34+
require.NoError(t, err)
35+
require.Equal(t, 4, n)
36+
37+
decoder = scale.NewDecoder(bytes.NewReader(buf.Bytes()), scale.WithDecodeLocal())
38+
var s2 StructWithNonLocalField
39+
n, err = s2.DecodeScale(decoder)
40+
require.NoError(t, err)
41+
require.Equal(t, 4, n)
42+
require.Equal(t, StructWithNonLocalField{Name: "foo"}, s2)
43+
}

generate.go

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@ type temp struct {
3131
decode string
3232
}
3333

34+
type action int
35+
3436
const (
35-
encode = iota
37+
encode action = iota
3638
decode
3739
)
3840

@@ -119,7 +121,7 @@ var (
119121
}
120122
)
121123

122-
func getAction(tm temp, action int) string {
124+
func getAction(tm temp, action action) string {
123125
switch action {
124126
case encode:
125127
return tm.encode
@@ -266,6 +268,7 @@ type scaleType struct {
266268
Args string
267269
EncodeModifier string
268270
DecodeModifier string
271+
NonLocal bool
269272
}
270273

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

279282
func getScaleType(parentType reflect.Type, field reflect.StructField) (scaleType, error) {
283+
st, err := getScaleTypeInner(parentType, field)
284+
if err != nil {
285+
return scaleType{}, err
286+
}
287+
st.NonLocal, err = nonLocal(field.Tag)
288+
if err != nil {
289+
return scaleType{}, fmt.Errorf("getting tags: %w", err)
290+
}
291+
return st, nil
292+
}
293+
294+
func getScaleTypeInner(parentType reflect.Type, field reflect.StructField) (scaleType, error) {
280295
decodeModifier := getDecodeModifier(parentType, field)
281296
encodableType := reflect.TypeOf((*Encodable)(nil)).Elem()
282297

@@ -374,7 +389,7 @@ func getTemplate(stype scaleType) temp {
374389
}
375390
}
376391

377-
func executeAction(action int, w io.Writer, tc *typeContext) error {
392+
func executeAction(action action, w io.Writer, tc *typeContext) error {
378393
typ := tc.Type
379394

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

414+
if scaleType.NonLocal {
415+
switch action {
416+
case encode:
417+
w.Write([]byte("if !enc.Local() "))
418+
case decode:
419+
w.Write([]byte("if !dec.Local() "))
420+
default:
421+
panic("BUG: bad action")
422+
}
423+
}
424+
399425
tctx := &typeContext{
400426
Name: field.Name,
401427
Type: field.Type,

0 commit comments

Comments
 (0)