Skip to content

Commit

Permalink
Implementation for field.Field (#279)
Browse files Browse the repository at this point in the history
This is a linked-list-based implementation. This is one of many possible approaches.

The approach attempts to prevent/limit the mutability of the fields outside the `field` package. Also, in the package the contents of `linkedField struct` are not modified after the struct has been created.

Include example usage (tests) to field package.

See #273
  • Loading branch information
julio-lopez committed Sep 11, 2019
1 parent 7d56f3c commit 6c70295
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 4 deletions.
4 changes: 2 additions & 2 deletions pkg/field/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ func FromContext(ctx context.Context) Fields {
return nil
}

// AddToContext returns a new context that has ctx as its parent context and
// Context returns a new context that has ctx as its parent context and
// has a Field with the given key and value.
func AddToContext(ctx context.Context, key string, v interface{}) context.Context {
func Context(ctx context.Context, key string, v interface{}) context.Context {
return context.WithValue(ctx, ctxKey, Add(FromContext(ctx), key, v))
}

Expand Down
3 changes: 1 addition & 2 deletions pkg/field/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ func New(key string, value interface{}) Fields {
// Add returns a collection with all the fields in s plus a new field with the
// given key and value. Duplicates are not eliminated.
func Add(s Fields, key string, value interface{}) Fields {
// TODO: implement
return nil
return newField(s, key, value)
}

// M contains fields with unique keys. Used to facilitate adding multiple
Expand Down
47 changes: 47 additions & 0 deletions pkg/field/field_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package field_test

import (
"context"
"fmt"

. "gopkg.in/check.v1"

"github.com/kanisterio/kanister/pkg/field"
)

type FieldSuite struct{}

var _ = Suite(&FieldSuite{})

func ExampleNew() {
f := field.New("foo", "bar")
fmt.Print(f)
// Output: ["foo":"bar"]
}

func ExampleAdd() {
f := field.New("foo", "bar")
f = field.Add(f, "baz", "x")
fmt.Print(f)
// Output: ["foo":"bar","baz":"x"]
}

type M = field.M

func ExampleContext() {
ctx := field.Context(context.Background(), "foo", "bar")
fmt.Print(field.FromContext(ctx))
// Output: ["foo":"bar"]
}

func ExampleAddMapToContext() {
ctx := field.AddMapToContext(context.Background(), M{"foo": "bar"})
fmt.Print(field.FromContext(ctx))
// Output: ["foo":"bar"]
}

func ExampleAddMapToContext_multiple() {
ctx := field.AddMapToContext(context.Background(), M{"foo": "bar", "x": "y"})
// Output is not specified because the order of the fields in 'M' is non-deterministic
fmt.Print(field.FromContext(ctx))
}
87 changes: 87 additions & 0 deletions pkg/field/linked_field.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package field

import (
"fmt"
"strings"
)

// Fields are exposed as an interface instead of directly as pointer to this
// struct. That enforces the immutability of fields outside this package. Once
// a field is created, its contents cannot be changed or overwritten outside
// this package by simply assigning one field to another, via
// f1 = f2 or *fp1 = *fp2.
type field struct {
key string
value interface{}
}

var _ fmt.Stringer = field{}

func (f field) Key() string {
return f.key
}

func (f field) Value() interface{} {
return f.value
}

func (f field) String() string {
return fmt.Sprintf("%q:%q", f.key, f.value)
}

type linkedField struct {
field
prev *linkedField
}

var _ fmt.Stringer = (*linkedField)(nil)

func newField(prev Fields, key string, value interface{}) *linkedField {
return &linkedField{prev: asLinkedFields(prev), field: field{key: key, value: value}}
}

func (f *linkedField) Fields() []Field {
return f.fields(0)
}

func (f *linkedField) fields(n int) []Field {
if f != nil {
return append(f.prev.fields(n+1), f.field)
}
return make([]Field, 0, n)
}

func (f *linkedField) String() string {
var b strings.Builder
b.WriteByte('[')
if f != nil {
f.buildString(&b)
}
b.WriteByte(']')
return b.String()
}

func (f *linkedField) buildString(b *strings.Builder) {
if f.prev != nil {
f.prev.buildString(b)
b.WriteByte(',')
}
fmt.Fprintf(b, "%q:%q", f.key, f.value)
}

func asLinkedFields(fs Fields) *linkedField {
if f, ok := fs.(*linkedField); ok {
return f
}
return toLinkedFields(nil, fs)
}

func toLinkedFields(prev *linkedField, fs Fields) *linkedField {
if fs == nil {
return prev
}
for _, o := range fs.Fields() {
prev = &linkedField{prev: prev, field: field{key: o.Key(), value: o.Value()}}
}
return prev
}

0 comments on commit 6c70295

Please sign in to comment.