Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions obj/atlas/atlas.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@ type Atlas struct {
// Mapping of tag ints to atlasEntry for quick lookups when the
// unmarshaller hits a tag. Values are a subset of `mappings`.
tagMappings map[int]*AtlasEntry

// MapMorphism specifies the default map sorting scheme
defaultMapMorphism *MapMorphism
}

func Build(entries ...*AtlasEntry) (Atlas, error) {
atl := Atlas{
mappings: make(map[uintptr]*AtlasEntry),
tagMappings: make(map[int]*AtlasEntry),
mappings: make(map[uintptr]*AtlasEntry),
tagMappings: make(map[int]*AtlasEntry),
defaultMapMorphism: &MapMorphism{KeySortMode_Default},
}
for _, entry := range entries {
rtid := reflect.ValueOf(entry.Type).Pointer()
Expand All @@ -52,6 +56,11 @@ func MustBuild(entries ...*AtlasEntry) Atlas {
return atl
}

func (atl Atlas) WithMapMorphism(m MapMorphism) Atlas {
atl.defaultMapMorphism = &m
return atl
}

// Gets the AtlasEntry for a typeID. Used by obj package, not meant for user facing.
func (atl Atlas) Get(rtid uintptr) (*AtlasEntry, bool) {
ent, ok := atl.mappings[rtid]
Expand All @@ -63,3 +72,8 @@ func (atl Atlas) GetEntryByTag(tag int) (*AtlasEntry, bool) {
ent, ok := atl.tagMappings[tag]
return ent, ok
}

// Gets the default map morphism config. Used by obj package, not meant for user facing.
func (atl Atlas) GetDefaultMapMorphism() *MapMorphism {
return atl.defaultMapMorphism
}
10 changes: 10 additions & 0 deletions obj/atlas/atlasCommon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package atlas

// A type to enumerate key sorting modes.
type KeySortMode string

const (
KeySortMode_Default = KeySortMode("default") // the default mode -- for structs, this is the source-order of the fields; for maps, it's identify to "strings" sort mode.
KeySortMode_Strings = KeySortMode("strings") // lexical sort by strings. this *is* the default for maps; it overrides source-order sorting for structs.
KeySortMode_RFC7049 = KeySortMode("rfc7049") // "Canonical" as proposed by rfc7049 § 3.9 (shorter byte sequences sort to top).
)
4 changes: 1 addition & 3 deletions obj/atlas/atlasExample_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,9 @@ func ExampleAtlasBuilding() {
AddField("FieldName", atlas.StructMapEntry{SerialName: "fn", OmitEmpty: true}).
AddField("Nested.Thing", atlas.StructMapEntry{SerialName: "nt"}).
Complete(),
atlas.BuildEntry(map[string]typeExample1{}).MapMorphism().
SetKeySortMode(atlas.KeySortMode_RFC7049).
Complete(),
// and carry on; this `Build` method takes `AtlasEntry...` as a vararg.
)
atl = atl.WithMapMorphism(atlas.MapMorphism{atlas.KeySortMode_RFC7049})
_, _ = atl, err

// Output:
Expand Down
10 changes: 1 addition & 9 deletions obj/atlas/mapMorphism.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,6 @@ import (
"fmt"
)

// A type to enumerate key sorting modes.
type KeySortMode string

const (
KeySortMode_Default = "default" // e.g. lexical string sort for strings, etc
KeySortMode_RFC7049 = "rfc7049" // "Canonical" as proposed by rfc7049 § 3.9 (shorter byte sequences sort to top)
)

type MapMorphism struct {
KeySortMode KeySortMode
}
Expand All @@ -33,7 +25,7 @@ func (x *BuilderMapMorphism) Complete() *AtlasEntry {

func (x *BuilderMapMorphism) SetKeySortMode(km KeySortMode) *BuilderMapMorphism {
switch km {
case KeySortMode_Default, KeySortMode_RFC7049:
case KeySortMode_Default, KeySortMode_Strings, KeySortMode_RFC7049:
x.entry.MapMorphism.KeySortMode = km
default:
panic(fmt.Errorf("invalid key sort mode %q", km))
Expand Down
37 changes: 32 additions & 5 deletions obj/atlas/structMapAutogen.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@ import (
)

func AutogenerateStructMapEntry(rt reflect.Type) *AtlasEntry {
return AutogenerateStructMapEntryUsingTags(rt, "refmt")
return AutogenerateStructMapEntryUsingTags(rt, "refmt", KeySortMode_Default)
}

func AutogenerateStructMapEntryUsingTags(rt reflect.Type, tagName string) *AtlasEntry {
func AutogenerateStructMapEntryUsingTags(rt reflect.Type, tagName string, sorter KeySortMode) *AtlasEntry {
entry := &AtlasEntry{
Type: rt,
StructMap: &StructMap{Fields: exploreFields(rt, tagName)},
StructMap: &StructMap{Fields: exploreFields(rt, tagName, sorter)},
}
return entry
}

// exploreFields returns a list of fields that StructAtlas should recognize for the given type.
// The algorithm is breadth-first search over the set of structs to include - the top struct
// and then any reachable anonymous structs.
func exploreFields(rt reflect.Type, tagName string) []StructMapEntry {
func exploreFields(rt reflect.Type, tagName string, sorter KeySortMode) []StructMapEntry {
// Anonymous fields to explore at the current level and the next.
current := []StructMapEntry{}
next := []StructMapEntry{{Type: rt}}
Expand Down Expand Up @@ -137,7 +137,17 @@ func exploreFields(rt reflect.Type, tagName string) []StructMapEntry {
}

fields = out
sort.Sort(StructMapEntry_byFieldRoute(fields))
switch sorter {
case KeySortMode_Default:
sort.Sort(StructMapEntry_byFieldRoute(fields))
case KeySortMode_Strings:
//sort.Sort(StructMapEntry_byName(fields))
// it's already in this order, though, so, pass
case KeySortMode_RFC7049:
sort.Sort(StructMapEntry_RFC7049(fields))
default:
panic("invalid struct sorter option")
}

return fields
}
Expand Down Expand Up @@ -216,6 +226,23 @@ func (x StructMapEntry_byName) Less(i, j int) bool {
return StructMapEntry_byFieldRoute(x).Less(i, j)
}

// StructMapEntry_RFC7049 sorts fields as specified in RFC7049,
type StructMapEntry_RFC7049 []StructMapEntry

func (x StructMapEntry_RFC7049) Len() int { return len(x) }
func (x StructMapEntry_RFC7049) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x StructMapEntry_RFC7049) Less(i, j int) bool {
il, jl := len(x[i].SerialName), len(x[j].SerialName)
switch {
case il < jl:
return true
case il > jl:
return false
default:
return x[i].SerialName < x[j].SerialName
}
}

// StructMapEntry_byFieldRoute sorts field by FieldRoute sequence
// (e.g., roughly source declaration order within each type).
type StructMapEntry_byFieldRoute []StructMapEntry
Expand Down
9 changes: 9 additions & 0 deletions obj/atlas/structMapBuilding.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,12 @@ func (x *BuilderStructMap) Autogenerate() *BuilderStructMap {
x.entry.StructMap.Fields = append(x.entry.StructMap.Fields, autoEntry.StructMap.Fields...)
return x
}

/*
Automatically generate mappings using a given struct field sorting scheme
*/
func (x *BuilderStructMap) AutogenerateWithSortingScheme(sorting KeySortMode) *BuilderStructMap {
autoEntry := AutogenerateStructMapEntryUsingTags(x.entry.Type, "refmt", sorting)
x.entry.StructMap.Fields = append(x.entry.StructMap.Fields, autoEntry.StructMap.Fields...)
return x
}
14 changes: 11 additions & 3 deletions obj/marshalMapWildcard.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

type marshalMachineMapWildcard struct {
cfg *atlas.AtlasEntry // set on initialization
morphism *atlas.MapMorphism // set on initialization

target_rv reflect.Value
value_rt reflect.Type
Expand Down Expand Up @@ -47,13 +47,21 @@ func (mach *marshalMachineMapWildcard) Reset(slab *marshalSlab, rv reflect.Value
mach.keys[i].rv = v
mach.keys[i].s = v.String()
}
switch mach.cfg.MapMorphism.KeySortMode {

ksm := atlas.KeySortMode_Default
if mach.morphism != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this branch should ever be reachable. The marshaller always sets it during dispatch.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think thats true... If i take this out i get panics in tests

ksm = mach.morphism.KeySortMode
}

switch ksm {
case atlas.KeySortMode_Default:
sort.Sort(wildcardMapStringyKey_byString(mach.keys))
case atlas.KeySortMode_Strings:
sort.Sort(wildcardMapStringyKey_byString(mach.keys))
case atlas.KeySortMode_RFC7049:
sort.Sort(wildcardMapStringyKey_RFC7049(mach.keys))
default:
panic(fmt.Errorf("unknown map key sort mode %q", mach.cfg.MapMorphism.KeySortMode))
panic(fmt.Errorf("unknown map key sort mode %q", ksm))
}

mach.index = -1
Expand Down
10 changes: 2 additions & 8 deletions obj/marshalSlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,6 @@ func (slab *marshalSlab) requisitionMachine(rt reflect.Type) MarshalMachine {
return &row.ptrDerefDelegateMarshalMachine
}

var defaultCfg = &atlas.AtlasEntry{
MapMorphism: &atlas.MapMorphism{
atlas.KeySortMode_Default,
},
}

func _yieldMarshalMachinePtr(row *marshalSlabRow, atl atlas.Atlas, rt reflect.Type) MarshalMachine {
rtid := reflect.ValueOf(rt).Pointer()

Expand Down Expand Up @@ -119,7 +113,7 @@ func _yieldMarshalMachinePtr(row *marshalSlabRow, atl atlas.Atlas, rt reflect.Ty
row.marshalMachineStructAtlas.cfg = entry
return &row.marshalMachineStructAtlas
case entry.MapMorphism != nil:
row.marshalMachineMapWildcard.cfg = entry
row.marshalMachineMapWildcard.morphism = entry.MapMorphism
return &row.marshalMachineMapWildcard
default:
panic("invalid atlas entry")
Expand All @@ -145,7 +139,7 @@ func _yieldMarshalMachinePtr(row *marshalSlabRow, atl atlas.Atlas, rt reflect.Ty
case reflect.Array:
return &row.marshalMachineSliceWildcard.marshalMachineArrayWildcard
case reflect.Map:
row.marshalMachineMapWildcard.cfg = defaultCfg
row.marshalMachineMapWildcard.morphism = atl.GetDefaultMapMorphism()
return &row.marshalMachineMapWildcard
case reflect.Struct:
// TODO here we could also invoke automatic atlas autogen, if configured to be permitted
Expand Down
57 changes: 51 additions & 6 deletions obj/objFixtures_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ type t5b struct {
K5 tObjStr
}

type tFieldSort1 struct {
Aaaaa string
Bbbb string
Ddd string
Ccc string
Eee string
Ff string
G string
}

var objFixtures = []struct {
title string

Expand Down Expand Up @@ -349,9 +359,7 @@ var objFixtures = []struct {
// Map walks *are* biased towards their declaration order though, as far as I can tell,
// so it's less than a 1/4 chance here.
sequence: fixtures.SequenceMap["quad map default order"],
atlas: atlas.MustBuild(
atlas.BuildEntry(map[string]string{}).MapMorphism().SetKeySortMode(atlas.KeySortMode_Default).Complete(),
),
atlas: atlas.MustBuild().WithMapMorphism(atlas.MapMorphism{atlas.KeySortMode_Default}),
marshalResults: []marshalResults{
{title: "from map",
valueFn: func() interface{} {
Expand All @@ -369,9 +377,7 @@ var objFixtures = []struct {
// Map walks *are* biased towards their declaration order though, as far as I can tell,
// so it's less than a 1/4 chance here.
sequence: fixtures.SequenceMap["quad map rfc7049 order"],
atlas: atlas.MustBuild(
atlas.BuildEntry(map[string]string{}).MapMorphism().SetKeySortMode(atlas.KeySortMode_RFC7049).Complete(),
),
atlas: atlas.MustBuild().WithMapMorphism(atlas.MapMorphism{atlas.KeySortMode_RFC7049}),
marshalResults: []marshalResults{
{title: "from map",
valueFn: func() interface{} {
Expand All @@ -384,6 +390,45 @@ var objFixtures = []struct {
}},
},
},
{title: "object with 10 string fields, with atlas entry (rfc7049 key ordering), marshals ordered correctly",
sequence: fixtures.SequenceMap["10 map rfc7049 order"],
atlas: atlas.MustBuild().WithMapMorphism(atlas.MapMorphism{atlas.KeySortMode_RFC7049}),
marshalResults: []marshalResults{
{title: "from map",
valueFn: func() interface{} {
return map[string]string{
"hello": "9",
"d": "4",
"b": "3",
"bc": "6",
"1": "1",
"bccccc": "10",
"2": "2",
"11": "5",
"hell": "8",
"he": "7",
}
}},
},
},
{title: "struct with fields in different than expected order (rfc7049 expected)",
sequence: fixtures.SequenceMap["7 struct rfc7049 order"],
atlas: atlas.MustBuild(atlas.BuildEntry(tFieldSort1{}).StructMap().AutogenerateWithSortingScheme(atlas.KeySortMode_RFC7049).Complete()),
marshalResults: []marshalResults{
{title: "from struct",
valueFn: func() interface{} {
return tFieldSort1{
G: "1",
Ff: "2",
Ccc: "3",
Ddd: "4",
Eee: "5",
Bbbb: "6",
Aaaaa: "7",
}
}},
},
},
{title: "empty primitive arrays",
sequence: fixtures.SequenceMap["empty array"],
marshalResults: []marshalResults{
Expand Down
29 changes: 29 additions & 0 deletions tok/fixtures/fixtures.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,35 @@ var Sequences = []Sequence{
{Type: TMapClose},
},
},
{"10 map rfc7049 order",
[]Token{
{Type: TMapOpen, Length: 10},
TokStr("1"), TokStr("1"),
TokStr("2"), TokStr("2"),
TokStr("b"), TokStr("3"),
TokStr("d"), TokStr("4"),
TokStr("11"), TokStr("5"),
TokStr("bc"), TokStr("6"),
TokStr("he"), TokStr("7"),
TokStr("hell"), TokStr("8"),
TokStr("hello"), TokStr("9"),
TokStr("bccccc"), TokStr("10"),
{Type: TMapClose},
},
},
{"7 struct rfc7049 order",
[]Token{
{Type: TMapOpen, Length: 7},
TokStr("g"), TokStr("1"),
TokStr("ff"), TokStr("2"),
TokStr("ccc"), TokStr("3"),
TokStr("ddd"), TokStr("4"),
TokStr("eee"), TokStr("5"),
TokStr("bbbb"), TokStr("6"),
TokStr("aaaaa"), TokStr("7"),
{Type: TMapClose},
},
},

// Arrays.
{"empty array",
Expand Down