From d3e3bb32f269dc2a39546374e9f8427bf701fad7 Mon Sep 17 00:00:00 2001 From: Jamie Stephens Date: Fri, 9 Apr 2021 14:33:14 +0000 Subject: [PATCH] Test and comments re unbound vars; closes #70 --- subst/bindings.go | 18 +++++++++++------- subst/subst.go | 10 ++++++++++ subst/subst_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/subst/bindings.go b/subst/bindings.go index 48ecb9f..81ac191 100644 --- a/subst/bindings.go +++ b/subst/bindings.go @@ -89,12 +89,8 @@ func (bs *Bindings) Set(value string) error { return bs.SetJSON(pv[0], pv[1]) } -// replaceBindings replaces all variables in x with their -// corresponding values in bs (if any). -// -// This operation is destructive (and probably shouldn't be). -// -// An array or map should have interface{}-typed elements or values. +// replaceBindings replaces all structural variables in x with their +// corresponding values in bs (if any). See docs for Bindings.Bind(). func (bs *Bindings) replaceBindings(ctx *Ctx, x interface{}) (interface{}, error) { b := *bs switch vv := x.(type) { @@ -165,7 +161,15 @@ func (bs *Bindings) replaceBindings(ctx *Ctx, x interface{}) (interface{}, error } } -// Bind replaces all bindings in the given (structured) thing. +// Bind replaces all structural variables in x with their +// corresponding values in bs (if any). +// +// This operation is destructive (and probably shouldn't be). +// +// An array or map should have interface{}-typed elements or values. +// +// An unbound variable does not result in an error. See some comments +// in the Subber type. func (bs *Bindings) Bind(ctx *Ctx, x interface{}) (interface{}, error) { return bs.replaceBindings(ctx, x) } diff --git a/subst/subst.go b/subst/subst.go index 001ec88..df2c365 100644 --- a/subst/subst.go +++ b/subst/subst.go @@ -78,6 +78,16 @@ type Subber struct { // pipePattern is the (compiled) Regexp that included the // delimiters provided to NewSubber. pipePattern *regexp.Regexp + + // ToDo: We might want a switch that controls whether the + // Subber returns an error when it encounters a string-based + // VAR that is not bound. There are some (exotic?) situations + // where an unbound VAR is a string-based substitution + // expression should be left as is and without and error. + // Note that a structured unbound VAR is usually fine in a + // 'recv' context because the point can be to bind that var + // via pattern matching. A switch could be helpful to get the + // right behavior in different contexts. } // NewSubber makes a new Subber with the pipe expression delimiters diff --git a/subst/subst_test.go b/subst/subst_test.go index ce07eff..009820b 100644 --- a/subst/subst_test.go +++ b/subst/subst_test.go @@ -217,6 +217,30 @@ func TestSubberWithProcs(t *testing.T) { } +func TestUnbound(t *testing.T) { + var ( + ctx = NewCtx(context.Background(), nil) + bs = NewBindings() + b, err = NewSubber("") + ) + if err != nil { + t.Fatal(err) + } + + s, err := b.Sub(ctx, bs, `{?NOPE}`) + if err == nil { + t.Fatal(s) + } + // source '?NOPE' variable not bound in '{?NOPE}' + + // We do not want to complain (in recv cases at least) if a + // structured VAR isn't bound. + s, err = b.Sub(ctx, bs, `"?NOPE"`) + if err != nil { + t.Fatal(err) + } +} + func TestLimit(t *testing.T) { var ( ctx = NewCtx(context.Background(), nil)