Integrate BACKBEAT SDK and resolve KACHING license validation

Major integrations and fixes:
- Added BACKBEAT SDK integration for P2P operation timing
- Implemented beat-aware status tracking for distributed operations
- Added Docker secrets support for secure license management
- Resolved KACHING license validation via HTTPS/TLS
- Updated docker-compose configuration for clean stack deployment
- Disabled rollback policies to prevent deployment failures
- Added license credential storage (CHORUS-DEV-MULTI-001)

Technical improvements:
- BACKBEAT P2P operation tracking with phase management
- Enhanced configuration system with file-based secrets
- Improved error handling for license validation
- Clean separation of KACHING and CHORUS deployment stacks

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-09-06 07:56:26 +10:00
parent 543ab216f9
commit 9bdcbe0447
4730 changed files with 1480093 additions and 1916 deletions

79
vendor/github.com/polydawn/refmt/obj/atlas/atlas.go generated vendored Normal file
View File

@@ -0,0 +1,79 @@
package atlas
import (
"fmt"
"reflect"
)
type Atlas struct {
// Map typeinfo to a static description of how that type should be handled.
// (The internal machinery that will wield this information, and has memory of
// progress as it does so, is configured using the AtlasEntry, but allocated separately.
// The machinery is stateful and mutable; the AtlasEntry is not.)
//
// We use 'var rtid uintptr = reflect.ValueOf(rt).Pointer()' -- pointer of the
// value of the reflect.Type info -- as an index.
// This is both unique and correctly converges when recomputed, and much
// faster to compare against than reflect.Type (which is an interface that
// tends to contain fairly large structures).
mappings map[uintptr]*AtlasEntry
// 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),
defaultMapMorphism: &MapMorphism{KeySortMode_Default},
}
for _, entry := range entries {
rtid := reflect.ValueOf(entry.Type).Pointer()
if _, exists := atl.mappings[rtid]; exists {
return Atlas{}, fmt.Errorf("repeated entry for type %v", entry.Type)
}
atl.mappings[rtid] = entry
if entry.Tagged == true {
if prev, exists := atl.tagMappings[entry.Tag]; exists {
return Atlas{}, fmt.Errorf("repeated tag %v on type %v (already mapped to type %v)", entry.Tag, entry.Type, prev.Type)
}
atl.tagMappings[entry.Tag] = entry
}
}
return atl, nil
}
func MustBuild(entries ...*AtlasEntry) Atlas {
atl, err := Build(entries...)
if err != nil {
panic(err)
}
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]
return ent, ok
}
// Gets the AtlasEntry for a tag int. Used by obj package, not meant for user facing.
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
}

View File

@@ -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).
)

View File

@@ -0,0 +1,150 @@
package atlas
import (
"reflect"
)
/*
The AtlasEntry is a declarative roadmap of what we should do for
marshal and unmarshal of a single object, keyed by type.
There are a lot of paths your mappings might want to take:
- For a struct type, you may simply want to specify some alternate keys, or some to leave out, etc.
- For an interface type, you probably want to specify one of our interface muxing strategies
with a mapping between enumstr:typeinfo (and, what to do if we get a struct we don't recognize).
- For a string, int, or other primitive, you don't need to say anything: defaults will DTRT.
- For a typedef'd string, int, or other primitive, you *still* don't need to say anything: but,
if you want custom behavior (say, transform the string to an int at the last second, and back again),
you can specify transformer functions for that.
- For a struct type that you want to turn into a whole different kind (like a string): use
those same transform functions. (You'll no longer need a FieldMap.)
- For the most esoteric needs, you can fall all the way back to providing a custom MarshalMachine
(but avoid that; it's a lot of work, and one of these other transform methods should suffice).
*/
type AtlasEntry struct {
// The reflect info of the type this morphism is regarding.
Type reflect.Type
// --------------------------------------------------------
// The big escape valves: wanna map to some other kind completely?
// --------------------------------------------------------
// Transforms the value we reached by walking (the 'live' value -- which
// must be of `this.Type`) into another value (the 'serialable' value --
// which will be of `this.MarshalTransformTargetType`).
//
// The target type may be anything, even of a completely different Kind!
//
// This transform func runs first, then the resulting value is
// serialized (by running through the path through Atlas again, so
// chaining of transform funcs is supported, though not recommended).
MarshalTransformFunc MarshalTransformFunc
// The type of value we expect after using the MarshalTransformFunc.
//
// The match between transform func and target type should be checked
// during construction of this AtlasEntry.
MarshalTransformTargetType reflect.Type
// Expects a different type (the 'serialable' value -- which will be of
// 'this.UnmarshalTransformTargetType') than the value we reached by
// walking (the 'live' value -- which must be of `this.Type`).
//
// The target type may be anything, even of a completely different Kind!
//
// The unmarshal of that target type will be run first, then the
// resulting value is fed through this function to produce the real value,
// which is then placed correctly into bigger mid-unmarshal object tree.
//
// For non-primitives, unmarshal of the target type will always target
// an empty pointer or empty slice, roughly as per if it was
// operating on a value produced by `TargetType.New()`.
UnmarshalTransformFunc UnmarshalTransformFunc
// The type of value we will manufacture an instance of and unmarshal
// into, then when done provide to the UnmarshalTransformFunc.
//
// The match between transform func and target type should be checked
// during construction of this AtlasEntry.
UnmarshalTransformTargetType reflect.Type
// --------------------------------------------------------
// Standard options for how to map (varies by Kind)
// --------------------------------------------------------
// A "tag" to emit when marshalling this type of value;
// and when unmarshalling, this tag will cause unmarshal to pick
// this atlas (and if there's conflicting type info, error).
Tag int
// Flag for whether the Tag feature should be used (zero is a valid tag).
Tagged bool
// A mapping of fields in a struct to serial keys.
// Only valid if `this.Type.Kind() == Struct`.
StructMap *StructMap
// Configuration for how to traverse a map kind.
// Only valid if `this.Type.Kind() == Map`.
MapMorphism *MapMorphism
// Configuration for how to pick concrete types to fill a union interface.
// Only valid if `this.Type.Kind() == Interface`.
UnionKeyedMorphism *UnionKeyedMorphism
// FUTURE: enum-ish primitives, multiplexers for interfaces,
// lots of such things will belong here.
// --------------------------------------------------------
// Hooks, validate helpers
// --------------------------------------------------------
// A validation function which will be called for the whole value
// after unmarshalling reached the end of the object.
// If it returns an error, the entire unmarshal will error.
//
// Not used in marshalling.
// Not reachable if an UnmarshalTransform is set.
ValidateFn func(v interface{}) error
}
func BuildEntry(typeHintObj interface{}) *BuilderCore {
rt := reflect.TypeOf(typeHintObj)
if rt.Kind() == reflect.Ptr {
if rt.Elem().Kind() == reflect.Interface {
rt = rt.Elem()
} else {
panic("invalid atlas build: use the bare object, not a pointer (refmt will handle pointers automatically)")
}
}
return &BuilderCore{
&AtlasEntry{Type: rt},
}
}
/*
Intermediate step in building an AtlasEntry: use `BuildEntry` to
get one of these to start with, then call one of the methods
on this type to get a specialized builder which has the methods
relevant for setting up that specific kind of mapping.
One full example of using this builder may look like the following:
atlas.BuildEntry(Formula{}).StructMap().Autogenerate().Complete()
Some intermediate manipulations may be performed on this object,
for example setting the "tag" (if you want to use cbor tagging),
before calling the specializer method.
In this case, just keep chaining the configuration calls like so:
atlas.BuildEntry(Formula{}).UseTag(4000)
.StructMap().Autogenerate().Complete()
*/
type BuilderCore struct {
entry *AtlasEntry
}
func (x *BuilderCore) UseTag(tag int) *BuilderCore {
x.entry.Tagged = true
x.entry.Tag = tag
return x
}

45
vendor/github.com/polydawn/refmt/obj/atlas/doc.go generated vendored Normal file
View File

@@ -0,0 +1,45 @@
/*
Atlas types are used to define how to map Go values into refmt token streams.
Atlas information may be autogenerated based on struct tags automatically,
but you can also specify custom AtlasEntry info to use advanced features
and define custom transformations.
An Atlas is a collection of AtlasEntry (plus some internal indexing).
Typical usage is to declare an AtlasEntry for your structs (often near by the
struct definition), then
Building an AtlasEntry for some type called `Formula` looks like this:
atlas.BuildEntry(Formula{}).StructMap().Autogenerate().Complete()
Building an AtlasEntry always starts with `atlas.BuildEntry(x)` where `x` is
a dummy object used to convey type information.
The next function in the chain declares what kind of behavior we're going
to use to turn that type of object into its serial form.
(In the above example, we're declaring that we want refmt to see the `Formula`
type as a struct and traverse its fields. There are many other options!)
Subsequent functions are specific to what kind of walking and mapping we've
chosen. For struct walking, this may involve declaring fields and custom serial
names to map them to; for a "Transform" we'd instead have to provide callbacks
to do the transformation from the `Formula` type to some other type; etcetera.
The final function in the chain is always called `Complete`, and returns
a ready-to-use AtlasEntry.
Building a complete Atlas for a whole suite of serializable types is as
easy as putting a bunch of them together:
atlas.Build(
atlas.BuildEntry(Foo{}).StructMap().Autogenerate().Complete(),
atlas.BuildEntry(Bar{}).StructMap().Autogenerate().Complete(),
atlas.BuildEntry(Baz{}).StructMap().Autogenerate().Complete(),
)
You can put your entire protocol into one Atlas.
It's also possible to build several different Atlases each with different
sets of AtlasEntry. This may be useful if you have a protocol where some
messages are not valid during some phases of communication, and you would
like to use the Atlas as a form of whitelisting for what can be
marshalled/unmarshalled.
*/
package atlas

14
vendor/github.com/polydawn/refmt/obj/atlas/errors.go generated vendored Normal file
View File

@@ -0,0 +1,14 @@
package atlas
// Error type raised when initializing an Atlas, and field entries do
// not resolve against the type.
// (If you recently refactored names of fields in your types, check
// to make sure you updated any references to those fields by name to match!)
type ErrStructureMismatch struct {
TypeName string
Reason string
}
func (e ErrStructureMismatch) Error() string {
return "structure mismatch: " + e.TypeName + " " + e.Reason
}

View File

@@ -0,0 +1,38 @@
package atlas
import (
"fmt"
"reflect"
)
type MapMorphism struct {
KeySortMode KeySortMode
}
func (x *BuilderCore) MapMorphism() *BuilderMapMorphism {
if x.entry.Type.Kind() != reflect.Map {
panic(fmt.Errorf("cannot use mapMorphism for type %q, which is kind %s", x.entry.Type, x.entry.Type.Kind()))
}
x.entry.MapMorphism = &MapMorphism{
KeySortMode_Default,
}
return &BuilderMapMorphism{x.entry}
}
type BuilderMapMorphism struct {
entry *AtlasEntry
}
func (x *BuilderMapMorphism) Complete() *AtlasEntry {
return x.entry
}
func (x *BuilderMapMorphism) SetKeySortMode(km KeySortMode) *BuilderMapMorphism {
switch km {
case KeySortMode_Default, KeySortMode_Strings, KeySortMode_RFC7049:
x.entry.MapMorphism.KeySortMode = km
default:
panic(fmt.Errorf("invalid key sort mode %q", km))
}
return x
}

View File

@@ -0,0 +1,46 @@
package atlas
import "reflect"
type StructMap struct {
// A slice of descriptions of each field in the type.
// Each entry specifies the name by which each field should be referenced
// when serialized, and defines a way to get an address to the field.
Fields []StructMapEntry
}
type StructMapEntry struct {
// The field name; will be emitted as token during marshal, and used for
// lookup during unmarshal. Required.
SerialName string
// If true, a key token with this SerialName will be ignored during unmarshal.
// (By default, if there's no StructMapEntry for a key token, it's an error.)
// If true, the ReflectRoute, Type, etc fields are irrelevant and may be nil.
Ignore bool
ReflectRoute ReflectRoute // reflection generates these.
Type reflect.Type // type to expect on the far side of the ReflectRoute.
tagged bool // used during autogen.
// Theoretical feature which would be alternative to ReflectRoute. Support dropped for the moment.
//addrFunc func(interface{}) interface{} // custom user function.
// If true, marshalling will skip this field if it's the zero value.
OmitEmpty bool
}
type ReflectRoute []int
func (rr ReflectRoute) TraverseToValue(v reflect.Value) reflect.Value {
for _, i := range rr {
if v.Kind() == reflect.Ptr {
if v.IsNil() {
return reflect.Value{}
}
v = v.Elem()
}
v = v.Field(i)
}
return v
}

View File

@@ -0,0 +1,320 @@
package atlas
import (
"fmt"
"reflect"
"sort"
"strings"
"unicode"
)
func AutogenerateStructMapEntry(rt reflect.Type) *AtlasEntry {
return AutogenerateStructMapEntryUsingTags(rt, "refmt", KeySortMode_Default)
}
func AutogenerateStructMapEntryUsingTags(rt reflect.Type, tagName string, sorter KeySortMode) *AtlasEntry {
if rt.Kind() != reflect.Struct {
panic(fmt.Errorf("cannot use structMap for type %q, which is kind %s", rt, rt.Kind()))
}
entry := &AtlasEntry{
Type: rt,
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, sorter KeySortMode) []StructMapEntry {
// Anonymous fields to explore at the current level and the next.
current := []StructMapEntry{}
next := []StructMapEntry{{Type: rt}}
// Count of queued names for current level and the next.
count := map[reflect.Type]int{}
nextCount := map[reflect.Type]int{}
// Types already visited at an earlier level.
visited := map[reflect.Type]bool{}
// Fields found.
var fields []StructMapEntry
for len(next) > 0 {
current, next = next, current[:0]
count, nextCount = nextCount, map[reflect.Type]int{}
for _, f := range current {
if visited[f.Type] {
continue
}
visited[f.Type] = true
// Scan f.Type for fields to include.
for i := 0; i < f.Type.NumField(); i++ {
sf := f.Type.Field(i)
if sf.PkgPath != "" && !sf.Anonymous { // unexported
continue
}
tag := sf.Tag.Get(tagName)
if tag == "-" {
continue
}
name, opts := parseTag(tag)
if !isValidTag(name) {
name = ""
}
route := make([]int, len(f.ReflectRoute)+1)
copy(route, f.ReflectRoute)
route[len(f.ReflectRoute)] = i
ft := sf.Type
if ft.Name() == "" && ft.Kind() == reflect.Ptr {
// Follow pointer.
ft = ft.Elem()
}
// Record found field and index sequence.
if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
tagged := name != ""
if name == "" {
name = downcaseFirstLetter(sf.Name)
}
fields = append(fields, StructMapEntry{
SerialName: name,
ReflectRoute: route,
Type: sf.Type,
tagged: tagged,
OmitEmpty: opts.Contains("omitempty"),
})
if count[f.Type] > 1 {
// If there were multiple instances, add a second,
// so that the annihilation code will see a duplicate.
// It only cares about the distinction between 1 or 2,
// so don't bother generating any more copies.
fields = append(fields, fields[len(fields)-1])
}
continue
}
// Record new anonymous struct to explore in next round.
nextCount[ft]++
if nextCount[ft] == 1 {
next = append(next, StructMapEntry{
ReflectRoute: route,
Type: ft,
})
}
}
}
}
sort.Sort(StructMapEntry_byName(fields))
// Delete all fields that are hidden by the Go rules for embedded fields,
// except that fields with JSON tags are promoted.
// The fields are sorted in primary order of name, secondary order
// of field index length. Loop over names; for each name, delete
// hidden fields by choosing the one dominant field that survives.
out := fields[:0]
for advance, i := 0, 0; i < len(fields); i += advance {
// One iteration per name.
// Find the sequence of fields with the name of this first field.
fi := fields[i]
name := fi.SerialName
for advance = 1; i+advance < len(fields); advance++ {
fj := fields[i+advance]
if fj.SerialName != name {
break
}
}
if advance == 1 { // Only one field with this name
out = append(out, fi)
continue
}
dominant, ok := dominantField(fields[i : i+advance])
if ok {
out = append(out, dominant)
}
}
fields = out
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
}
// If the first character of the string is uppercase, return a string
// where it is switched to lowercase.
// We use this to make go field names look more like what everyone else
// in the universe expects their json to look like by default: snakeCase.
func downcaseFirstLetter(s string) string {
if s == "" {
return ""
}
r := rune(s[0]) // if multibyte chars: you're left alone.
if !unicode.IsUpper(r) {
return s
}
return string(unicode.ToLower(r)) + s[1:]
}
// dominantField looks through the fields, all of which are known to
// have the same name, to find the single field that dominates the
// others using Go's embedding rules, modified by the presence of
// JSON tags. If there are multiple top-level fields, the boolean
// will be false: This condition is an error in Go and we skip all
// the fields.
func dominantField(fields []StructMapEntry) (StructMapEntry, bool) {
// The fields are sorted in increasing index-length order. The winner
// must therefore be one with the shortest index length. Drop all
// longer entries, which is easy: just truncate the slice.
length := len(fields[0].ReflectRoute)
tagged := -1 // Index of first tagged field.
for i, f := range fields {
if len(f.ReflectRoute) > length {
fields = fields[:i]
break
}
if f.tagged {
if tagged >= 0 {
// Multiple tagged fields at the same level: conflict.
// Return no field.
return StructMapEntry{}, false
}
tagged = i
}
}
if tagged >= 0 {
return fields[tagged], true
}
// All remaining fields have the same length. If there's more than one,
// we have a conflict (two fields named "X" at the same level) and we
// return no field.
if len(fields) > 1 {
return StructMapEntry{}, false
}
return fields[0], true
}
// StructMapEntry_byName sorts field by name,
// breaking ties with depth,
// then breaking ties with "name came from tag",
// then breaking ties with FieldRoute sequence.
type StructMapEntry_byName []StructMapEntry
func (x StructMapEntry_byName) Len() int { return len(x) }
func (x StructMapEntry_byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x StructMapEntry_byName) Less(i, j int) bool {
if x[i].SerialName != x[j].SerialName {
return x[i].SerialName < x[j].SerialName
}
if len(x[i].ReflectRoute) != len(x[j].ReflectRoute) {
return len(x[i].ReflectRoute) < len(x[j].ReflectRoute)
}
if x[i].tagged != x[j].tagged {
return x[i].tagged
}
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
func (x StructMapEntry_byFieldRoute) Len() int { return len(x) }
func (x StructMapEntry_byFieldRoute) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x StructMapEntry_byFieldRoute) Less(i, j int) bool {
for k, xik := range x[i].ReflectRoute {
if k >= len(x[j].ReflectRoute) {
return false
}
if xik != x[j].ReflectRoute[k] {
return xik < x[j].ReflectRoute[k]
}
}
return len(x[i].ReflectRoute) < len(x[j].ReflectRoute)
}
// tagOptions is the string following a comma in a struct field's
// tag, or the empty string. It does not include the leading comma.
type tagOptions string
// parseTag splits a struct field's tag into its name and
// comma-separated options.
func parseTag(tag string) (string, tagOptions) {
if idx := strings.Index(tag, ","); idx != -1 {
return tag[:idx], tagOptions(tag[idx+1:])
}
return tag, tagOptions("")
}
// Contains reports whether a comma-separated list of options
// contains a particular substr flag. substr must be surrounded by a
// string boundary or commas.
func (o tagOptions) Contains(optionName string) bool {
if len(o) == 0 {
return false
}
s := string(o)
for s != "" {
var next string
i := strings.Index(s, ",")
if i >= 0 {
s, next = s[:i], s[i+1:]
}
if s == optionName {
return true
}
s = next
}
return false
}
func isValidTag(s string) bool {
if s == "" {
return false
}
for _, c := range s {
switch {
case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
// Backslash and quote chars are reserved, but
// otherwise any punctuation chars are allowed
// in a tag name.
default:
if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
return false
}
}
}
return true
}

View File

@@ -0,0 +1,96 @@
package atlas
import (
"fmt"
"reflect"
"strings"
)
func (x *BuilderCore) StructMap() *BuilderStructMap {
if x.entry.Type.Kind() != reflect.Struct {
panic(fmt.Errorf("cannot use structMap for type %q, which is kind %s", x.entry.Type, x.entry.Type.Kind()))
}
x.entry.StructMap = &StructMap{}
return &BuilderStructMap{x.entry}
}
type BuilderStructMap struct {
entry *AtlasEntry
}
func (x *BuilderStructMap) Complete() *AtlasEntry {
return x.entry
}
/*
Add a field to the mapping based on its name.
Given a struct:
struct{
X int
Y struct{ Z int }
}
`AddField("X", {"x", ...}) will cause that field to be serialized as key "x";
`AddField("Y.Z", {"z", ...})` will cause that *nested* field to be serialized
as key "z" in the same object (e.g. "x" and "z" will be siblings).
Returns the mutated builder for convenient call chaining.
If the fieldName string doesn't map onto the structure type info,
a panic will be raised.
*/
func (x *BuilderStructMap) AddField(fieldName string, mapping StructMapEntry) *BuilderStructMap {
fieldNameSplit := strings.Split(fieldName, ".")
rr, rt, err := fieldNameToReflectRoute(x.entry.Type, fieldNameSplit)
if err != nil {
panic(err) // REVIEW: now that we have the builder obj, we could just curry these into it until 'Complete' is called (or, thus, 'MustComplete'!).
}
mapping.ReflectRoute = rr
mapping.Type = rt
x.entry.StructMap.Fields = append(x.entry.StructMap.Fields, mapping)
return x
}
func (x *BuilderStructMap) IgnoreKey(serialKeyName string) *BuilderStructMap {
x.entry.StructMap.Fields = append(x.entry.StructMap.Fields, StructMapEntry{
SerialName: serialKeyName,
Ignore: true,
})
return x
}
func fieldNameToReflectRoute(rt reflect.Type, fieldNameSplit []string) (rr ReflectRoute, _ reflect.Type, _ error) {
for _, fn := range fieldNameSplit {
rf, ok := rt.FieldByName(fn)
if !ok {
return nil, nil, ErrStructureMismatch{rt.Name(), "does not have field named " + fn}
}
rr = append(rr, rf.Index...)
rt = rf.Type
}
return rr, rt, nil
}
/*
Automatically generate mappings by looking at the struct type info,
taking any hints from tags, and appending that to the builder.
You may use autogeneration in concert with manually adding field mappings,
though if doing so be mindful not to map the same fields twice.
*/
func (x *BuilderStructMap) Autogenerate() *BuilderStructMap {
autoEntry := AutogenerateStructMapEntry(x.entry.Type)
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
}

View File

@@ -0,0 +1,30 @@
package atlas
import (
"reflect"
)
func (x *BuilderCore) Transform() *BuilderTransform {
// no checks on x.entry.Type.Kind() here -- transforms can be pretty much any<->any
return &BuilderTransform{x.entry}
}
type BuilderTransform struct {
entry *AtlasEntry
}
func (x *BuilderTransform) Complete() *AtlasEntry {
return x.entry
}
func (x *BuilderTransform) TransformMarshal(trFunc MarshalTransformFunc, toType reflect.Type) *BuilderTransform {
x.entry.MarshalTransformFunc = trFunc
x.entry.MarshalTransformTargetType = toType
return x
}
func (x *BuilderTransform) TransformUnmarshal(trFunc UnmarshalTransformFunc, toType reflect.Type) *BuilderTransform {
x.entry.UnmarshalTransformFunc = trFunc
x.entry.UnmarshalTransformTargetType = toType
return x
}

View File

@@ -0,0 +1,68 @@
package atlas
import "reflect"
type MarshalTransformFunc func(liveForm reflect.Value) (serialForm reflect.Value, err error)
type UnmarshalTransformFunc func(serialForm reflect.Value) (liveForm reflect.Value, err error)
var err_rt = reflect.TypeOf((*error)(nil)).Elem()
/*
Takes a wildcard object which must be `func (live T1) (serialable T2, error)`
and returns a MarshalTransformFunc and the typeinfo of T2.
*/
func MakeMarshalTransformFunc(fn interface{}) (MarshalTransformFunc, reflect.Type) {
fn_rv := reflect.ValueOf(fn)
if fn_rv.Kind() != reflect.Func {
panic("no")
}
fn_rt := fn_rv.Type()
if fn_rt.NumIn() != 1 {
panic("no")
}
if fn_rt.NumOut() != 2 {
panic("no")
}
if !fn_rt.Out(1).AssignableTo(err_rt) {
panic("no")
}
// nothing to do for `fn_rt.In(0)` -- whatever type it is... TODO redesign to make less sketchy; we should most certainly be able to check this in the builder
out_rt := fn_rt.Out(0)
return func(liveForm reflect.Value) (serialForm reflect.Value, err error) {
results := fn_rv.Call([]reflect.Value{liveForm})
if results[1].IsNil() {
return results[0], nil
}
return results[0], results[1].Interface().(error)
}, out_rt
}
/*
Takes a wildcard object which must be `func (serialable T1) (live T2, error)`
and returns a UnmarshalTransformFunc and the typeinfo of T1.
*/
func MakeUnmarshalTransformFunc(fn interface{}) (UnmarshalTransformFunc, reflect.Type) {
fn_rv := reflect.ValueOf(fn)
if fn_rv.Kind() != reflect.Func {
panic("no")
}
fn_rt := fn_rv.Type()
if fn_rt.NumIn() != 1 {
panic("no")
}
if fn_rt.NumOut() != 2 {
panic("no")
}
if !fn_rt.Out(1).AssignableTo(err_rt) {
panic("no")
}
// nothing to do for checking `fn_rf.Out(0)` -- because we don't know what entry we're about to be used for. TODO redesign to make less sketchy.
in_rt := fn_rt.In(0)
return func(serialForm reflect.Value) (liveForm reflect.Value, err error) {
results := fn_rv.Call([]reflect.Value{serialForm})
if results[1].IsNil() {
return results[0], nil
}
return results[0], results[1].Interface().(error)
}, in_rt
}

View File

@@ -0,0 +1,45 @@
package atlas
import (
"fmt"
"reflect"
"sort"
)
type UnionKeyedMorphism struct {
// Mapping of typehint key strings to atlasEntry that should be delegated to.
Elements map[string]*AtlasEntry
// Mapping of rtid to string (roughly the dual of the Elements map).
Mappings map[uintptr]string
// Purely to have in readiness for error messaging.
KnownMembers []string
}
func (x *BuilderCore) KeyedUnion() *BuilderUnionKeyedMorphism {
if x.entry.Type.Kind() != reflect.Interface {
panic(fmt.Errorf("cannot use union morphisms for type %q, which is kind %s", x.entry.Type, x.entry.Type.Kind()))
}
x.entry.UnionKeyedMorphism = &UnionKeyedMorphism{
Elements: make(map[string]*AtlasEntry),
Mappings: make(map[uintptr]string),
}
return &BuilderUnionKeyedMorphism{x.entry}
}
type BuilderUnionKeyedMorphism struct {
entry *AtlasEntry
}
func (x *BuilderUnionKeyedMorphism) Of(elements map[string]*AtlasEntry) *AtlasEntry {
cfg := x.entry.UnionKeyedMorphism
for hint, ent := range elements {
// FIXME: check that all the delegates are... well struct or map machines really, but definitely blacklisting other delegating machinery.
// FIXME: and sanity check that they can all be assigned to the interface ffs.
cfg.Elements[hint] = ent
cfg.Mappings[reflect.ValueOf(ent.Type).Pointer()] = hint
cfg.KnownMembers = append(cfg.KnownMembers, hint)
}
sort.Strings(cfg.KnownMembers)
return x.entry
}