kochava / envi Goto Github PK
View Code? Open in Web Editor NEWLibrary for unmarshaling from semi-structured key-value pairs
Home Page: https://godoc.org/github.com/Kochava/envi
License: BSD 3-Clause "New" or "Revised" License
Library for unmarshaling from semi-structured key-value pairs
Home Page: https://godoc.org/github.com/Kochava/envi
License: BSD 3-Clause "New" or "Revised" License
Depth counters should be reset if a struct field is found to be set, even if there are no non-struct fields assigned in that struct.
This may require re-parsing previously skipped fields with the reset depth value.
Example test to demonstrate a case where a struct field is set but the counter isn't re-set for other struct fields:
func TestCyclicalTypesAdjacent(t *testing.T) {
type P struct {
V int
L, R *P
}
runner := func(max int, want *P, env Env) func(*testing.T) {
return func(t *testing.T) {
dest := new(P)
r := Reader{Source: env, Sep: "_", MaxDepth: max}
err := r.Getenv(dest, "Cycle")
if err != nil && !IsNoValue(err) {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(dest, want) {
// Encode as JSON just to make it easier to visualize
jsondest, _ := json.Marshal(dest)
jsonwant, _ := json.Marshal(want)
t.Fatalf("got\t%s\nwant\t%s",
jsondest,
jsonwant,
)
}
}
}
t.Run("ResetCounterOnStructMatch", runner(
2, // max depth
&P{
R: &P{V: 2},
L: &P{L: &P{V: 4}},
},
Values{ // 1 2 3 4 5 6 7 7
"Cycle_R_V": {"2"},
"Cycle_L_L_V": {"4"},
},
))
t.Run("NoMatchOutsideDepth", runner(
2, // max depth
&P{},
Values{ // 1 2 3 4 5 6 7 7
"Cycle_L_L_V": {"4"},
},
))
}
Cyclical types, namely those that refer to themselves where the unmarshaller can see it, such as the Cycle type in the example below, cannot be unmarshalled. They result in recursion that will eventually kill a Go program. It doesn't matter if the cycle is direct or indirect (i.e., a sequence of types that eventually forms a cycle), it's just guaranteed to wreck things.
func TestCyclicalTypes(t *testing.T) {
type Cycle struct {
Value int
Next *Cycle
}
dest := new(Cycle)
env := Values{"Cycle_Value": {"1"}}
r := Reader{Source: env}
err := r.Getenv(dest, "Cycle")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
As in the example above, this doesn't require that there is even a way to unmarshal the Next
value -- since envi begins unmarshalling before it checks for keys' existence, it doesn't know whether Next
can have a value until it iterates over its fields. By the time it hits Next.Next
it has to then ask whether that has any fields assigned, since the absence of fields for its predecessor has no bearing on whether it would have fields.
It feels like depth needs to be capped for unchecked recursion and possibly an optional interface added to make it easier to determine if there are keys ahead of time. For OS environments and envi.Values, this is fairly easy -- check prefixes on keys. For other Env implementations where the keyset is not known (for whatever reason), tracking depth on unmarshalling seems like the way to go.
Test output:
$ go test -run TestCyclical
signal: killed
FAIL github.com/Kochava/envi 102.353s
At some point this broke, so it's no longer possible to unmarshal a type with a pointer field. For example:
func TestPointerFields(t *testing.T) {
runner := func(dest interface{}) func(*testing.T) {
return func(t *testing.T) {
env := Values{"Data_Value_Size": {"16"}}
r := Reader{Source: env, Sep: "_"}
err := r.Getenv(dest, "Data")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
}
t.Run("StructValue", runner(new(
struct {
Value struct {
Size int
}
},
)))
t.Run("StructPointer", runner(new(
struct {
Value *struct {
Size int
}
},
)))
}
The first subtest, StructValue, succeeds. The second, StructPointer, fails. This is supported in the internal version of envi, so I introduced a new bug between v0 and v1 during the refactoring. Amazingly, none of the existing tests actually covers this.
Stack trace:
panic: reflect.Set: value of type *struct { Size int } is not assignable to type struct { Size int } [recovered]
panic: reflect.Set: value of type *struct { Size int } is not assignable to type struct { Size int }
goroutine 237 [running]:
testing.tRunner.func1(0xc42011db30)
/usr/local/go/src/testing/testing.go:742 +0x29d
panic(0x1129fa0, 0xc42011e710)
/usr/local/go/src/runtime/panic.go:505 +0x229
reflect.Value.assignTo(0x1125760, 0xc42000c0d8, 0x196, 0x115ed15, 0xb, 0x11385e0, 0x0, 0x1125701, 0x1125760, 0xc420111b30)
/usr/local/go/src/reflect/value.go:2235 +0x427
reflect.Value.Set(0x11385e0, 0xc420111b30, 0x199, 0x1125760, 0xc42000c0d8, 0x196)
/usr/local/go/src/reflect/value.go:1373 +0xa4
github.com/Kochava/envi.(*Reader).loadStructField(0xc420081f60, 0x1138660, 0xc42000c0d8, 0x199, 0x117c7c0, 0x1138660, 0x0, 0x115d9b0, 0x4, 0x1138660, ...)
…/github.com/Kochava/envi/reader.go:438 +0x3ec
github.com/Kochava/envi.(*Reader).loadStruct(0xc420081f60, 0x1138660, 0xc42000c0d8, 0x199, 0x115d9b0, 0x4, 0x20, 0xf4356b620113dc60)
…/github.com/Kochava/envi/reader.go:398 +0x12c
github.com/Kochava/envi.(*Reader).loadReflect(0xc420045f60, 0x1125820, 0xc42000c0d8, 0x0, 0x0, 0x115d9b0, 0x4, 0x0, 0x0)
…/github.com/Kochava/envi/reader.go:217 +0x23a
github.com/Kochava/envi.(*Reader).load(0xc420045f60, 0x1125820, 0xc42000c0d8, 0x0, 0x0, 0x115d9b0, 0x4, 0x2b61c81951471eac, 0x100fdb8)
…/github.com/Kochava/envi/reader.go:120 +0x1e9
github.com/Kochava/envi.(*Reader).Getenv(0xc420045f60, 0x1125820, 0xc42000c0d8, 0x115d9b0, 0x4, 0x117a840, 0xc420113740)
…/github.com/Kochava/envi/reader.go:60 +0x2a3
github.com/Kochava/envi.TestPointerFields.func1.1(0xc42011db30)
…/github.com/Kochava/envi/structptr_test.go:11 +0x16f
testing.tRunner(0xc42011db30, 0xc420113700)
/usr/local/go/src/testing/testing.go:777 +0xd0
created by testing.(*T).Run
/usr/local/go/src/testing/testing.go:824 +0x2e0
exit status 2
FAIL github.com/Kochava/envi 0.013s
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.