Skip to content

Commit 8a289ad

Browse files
authored
More Implicit JSON Serialization (#20)
Just taking a string on to the end of a template is very naive, it's entrely possible some clever user will put an action in each if/else clause and we'll miss one. Instead walk the parse tree and augment actions that don't set variables. A more acceptable solution than #19 proposed.
1 parent 7ee3089 commit 8a289ad

File tree

3 files changed

+53
-14
lines changed

3 files changed

+53
-14
lines changed

documentation/modules/ROOT/pages/concepts/dynamic-attributes.adoc

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,17 @@ Dynamic attributes are best thought of as functions: they accept input arguments
1919
Dynamic attributes do not have any side effects.
2020

2121
In general, dynamic attributes use the Go language https://golang.org/pkg/text/template/[template^] library with a few specializations.
22-
All pipelines and functions are fully supported.
23-
Actions involving control flow (e.g. `if` and `range`) are not supported at present.
24-
Arguments are confined to scalar values only (typically `int`, `string` and `bool`) and undefined values (`nil`).
22+
Control flow is allowed, however, each dynamic attribute should execute at one action in order to generate a value.
23+
Dot is not defined initially, all data must be accessed though accessor functions.
24+
Iteration is not supported.
2525

2626
=== Dynamic Attribute Typing
2727

2828
The key thing to note is that Go language templating operates on text.
2929
Text has no concept of type (other than being a string) therefore all dynamic attributes must be serialized to JSON in order to preserve type information and allow the Service Broker to make the correct decisions.
30-
The JSON serialization function is added implicitly, by the Service Broker, to all pipelines.
30+
The JSON serialization function is added implicitly, by the Service Broker, to all action pipelines that would usually generate template output.
3131
All dynamic attributes are initially defined as strings, however the attribute itself takes on the type of the value returned by attribute template processing.
3232

33-
Internally, the templating engine treats all data as abstract values.
34-
Functions, however, may require a parameter to be of a specific type.
35-
The templating engine will attempt to cast from an abstract value to a concrete data type where required by a function argument.
36-
If this conversion fails, an error is raised.
37-
3833
==== Optional Attributes
3934

4035
All attribute templates are optional by default.

documentation/modules/ROOT/pages/reference/template-functions.adoc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,7 @@ The result type will be any type.
115115
== `json`
116116

117117
The `json` function serializes its input as a JSON string.
118-
All templates implicitly append the JSON function to their pipelines as this is required by the Service Broker for all operations.
119-
This is only performed for simple, single pipeline actions, therefore complex action involving control flow are not supported at present.
118+
All action pipelines that generate output are implicitly appended with the JSON function.
120119

121120
[source]
122121
----

pkg/provisioners/template.go

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"math/big"
2323
"strings"
2424
"text/template"
25+
"text/template/parse"
2526
"time"
2627

2728
"github.com/couchbase/service-broker/pkg/errors"
@@ -295,6 +296,50 @@ const (
295296
templateSuffix = "}}"
296297
)
297298

299+
// jsonify is a command to be appended to actions in the template
300+
// parse tree.
301+
var jsonify = &parse.CommandNode{
302+
NodeType: parse.NodeCommand,
303+
Args: []parse.Node{
304+
parse.NewIdentifier("json"),
305+
},
306+
}
307+
308+
// transformActionsToJSON walks the parse tree and finds any actions that
309+
// would usually generate text output. These are appened with a JSON function
310+
// that turns the abstract type into a JSON string, preserving type for later
311+
// decoding and patching into the resource structure.
312+
func transformActionsToJSON(n parse.Node) {
313+
if n == nil {
314+
return
315+
}
316+
317+
switch node := n.(type) {
318+
case *parse.ActionNode:
319+
if node.Pipe != nil && len(node.Pipe.Decl) == 0 {
320+
node.Pipe.Cmds = append(node.Pipe.Cmds, jsonify)
321+
}
322+
case *parse.BranchNode:
323+
transformActionsToJSON(node.List)
324+
transformActionsToJSON(node.ElseList)
325+
case *parse.CommandNode:
326+
for _, arg := range node.Args {
327+
transformActionsToJSON(arg)
328+
}
329+
case *parse.IfNode:
330+
transformActionsToJSON(node.BranchNode.List)
331+
transformActionsToJSON(node.BranchNode.ElseList)
332+
case *parse.ListNode:
333+
for _, item := range node.Nodes {
334+
transformActionsToJSON(item)
335+
}
336+
case *parse.PipeNode:
337+
for _, cmd := range node.Cmds {
338+
transformActionsToJSON(cmd)
339+
}
340+
}
341+
}
342+
298343
// renderTemplateString takes a string and returns either the literal value if it's
299344
// not a template or the object returned after template rendering.
300345
func renderTemplateString(str string, entry *registry.Entry) (interface{}, error) {
@@ -309,9 +354,6 @@ func renderTemplateString(str string, entry *registry.Entry) (interface{}, error
309354

310355
glog.V(log.LevelDebug).Infof("resolving dynamic attribute %s", str)
311356

312-
// Implictly add in a JSON transformation to preserve type and structure.
313-
str = fmt.Sprintf("%s| json }}", str[:len(str)-len(templateSuffix)])
314-
315357
funcs := map[string]interface{}{
316358
"registry": templateFunctionRegistry(entry),
317359
"parameter": templateFunctionParameter(entry),
@@ -330,6 +372,9 @@ func renderTemplateString(str string, entry *registry.Entry) (interface{}, error
330372
return nil, err
331373
}
332374

375+
// Implictly add in a JSON transformation to preserve type and structure.
376+
transformActionsToJSON(tmpl.Root)
377+
333378
buf := &bytes.Buffer{}
334379
if err := tmpl.Execute(buf, nil); err != nil {
335380
return nil, errors.NewConfigurationError("dynamic attribute resolution failed: %v", err)

0 commit comments

Comments
 (0)