Skip to content

Added function to disable template rendering abort on error #109

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion default.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func init() {
a.RequireNumOfArguments("len", 1, 1)

expression := a.Get(0)
if expression.Kind() == reflect.Ptr {
if expression.Kind() == reflect.Ptr || expression.Kind() == reflect.Interface {
expression = expression.Elem()
}

Expand Down
112 changes: 70 additions & 42 deletions eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (renderer RendererFunc) Render(r *Runtime) {
// Ranger a value implementing a ranger interface is able to iterate on his value
// and can be used directly in a range statement
type Ranger interface {
Range() (reflect.Value, reflect.Value, bool)
Range() (interface{}, interface{}, bool)
}

type escapeeWriter struct {
Expand Down Expand Up @@ -221,6 +221,9 @@ func (state *Runtime) Resolve(name string) reflect.Value {
}

func (st *Runtime) recover(err *error) {
// reset state scope and context just to be safe (they might not be cleared properly if there was a panic while using the state)
st.scope = &scope{}
st.context = reflect.Value{}
pool_State.Put(st)
if recovered := recover(); recovered != nil {
var is bool
Expand Down Expand Up @@ -457,21 +460,21 @@ func (st *Runtime) executeList(list *ListNode) {
if isSet {
if isLet {
if isKeyVal {
st.variables[node.Set.Left[0].String()] = indexValue
st.variables[node.Set.Left[1].String()] = rangeValue
st.variables[node.Set.Left[0].String()] = reflect.ValueOf(indexValue)
st.variables[node.Set.Left[1].String()] = reflect.ValueOf(rangeValue)
} else {
st.variables[node.Set.Left[0].String()] = rangeValue
st.variables[node.Set.Left[0].String()] = reflect.ValueOf(rangeValue)
}
} else {
if isKeyVal {
st.executeSet(node.Set.Left[0], indexValue)
st.executeSet(node.Set.Left[1], rangeValue)
st.executeSet(node.Set.Left[0], reflect.ValueOf(indexValue))
st.executeSet(node.Set.Left[1], reflect.ValueOf(rangeValue))
} else {
st.executeSet(node.Set.Left[0], rangeValue)
st.executeSet(node.Set.Left[0], reflect.ValueOf(rangeValue))
}
}
} else {
st.context = rangeValue
st.context = reflect.ValueOf(rangeValue)
}
st.executeList(node.List)
indexValue, rangeValue, end = ranger.Range()
Expand Down Expand Up @@ -605,7 +608,12 @@ func (st *Runtime) evalPrimaryExpressionGroup(node Expression) reflect.Value {
return baseExpression.MapIndex(indexExpression)
case reflect.Array, reflect.String, reflect.Slice:
if canNumber(indexType.Kind()) {
return baseExpression.Index(int(castInt64(indexExpression)))
index := int(castInt64(indexExpression))
if 0 <= index && index < baseExpression.Len() {
return baseExpression.Index(index)
} else {
node.errorf("%s index out of range (index: %d, len: %d)", baseExpression.Kind().String(), index, baseExpression.Len())
}
} else {
node.errorf("non numeric value in index expression kind %s", baseExpression.Kind().String())
}
Expand Down Expand Up @@ -650,6 +658,20 @@ func (st *Runtime) evalPrimaryExpressionGroup(node Expression) reflect.Value {
return st.evalBaseExpressionGroup(node)
}

// notNil returns false when v.IsValid() == false
// or when v's kind can be nil and v.IsNil() == true
func notNil(v reflect.Value) bool {
if !v.IsValid() {
return false
}
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
return !v.IsNil()
default:
return true
}
}

func (st *Runtime) isSet(node Node) bool {
nodeType := node.Type()

Expand All @@ -668,7 +690,7 @@ func (st *Runtime) isSet(node Node) bool {
indexExpression := st.evalPrimaryExpressionGroup(node.Index)

indexType := indexExpression.Type()
if baseExpression.Kind() == reflect.Ptr {
if baseExpression.Kind() == reflect.Ptr || baseExpression.Kind() == reflect.Interface {
baseExpression = baseExpression.Elem()
}

Expand All @@ -682,7 +704,8 @@ func (st *Runtime) isSet(node Node) bool {
node.errorf("%s is not assignable|convertible to map key %s", indexType.String(), key.String())
}
}
return baseExpression.MapIndex(indexExpression).IsValid()
value := baseExpression.MapIndex(indexExpression)
return notNil(value)
case reflect.Array, reflect.String, reflect.Slice:
if canNumber(indexType.Kind()) {
i := int(castInt64(indexExpression))
Expand All @@ -695,38 +718,31 @@ func (st *Runtime) isSet(node Node) bool {
i := int(castInt64(indexExpression))
return i >= 0 && i < baseExpression.NumField()
} else if indexType.Kind() == reflect.String {
return getFieldOrMethodValue(indexExpression.String(), baseExpression).IsValid()
fieldValue := getFieldOrMethodValue(indexExpression.String(), baseExpression)
return notNil(fieldValue)

} else {
node.errorf("non numeric value in index expression kind %s", baseExpression.Kind().String())
}
default:
node.errorf("indexing is not supported in value type %s", baseExpression.Kind().String())
}
case NodeIdentifier:
if st.Resolve(node.String()).IsValid() == false {
return false
}
value := st.Resolve(node.String())
return notNil(value)
case NodeField:
node := node.(*FieldNode)
resolved := st.context
for i := 0; i < len(node.Ident); i++ {
resolved = getFieldOrMethodValue(node.Ident[i], resolved)
if !resolved.IsValid() {
if !notNil(resolved) {
return false
}
}
case NodeChain:
node := node.(*ChainNode)
var value = st.evalPrimaryExpressionGroup(node.Node)
if !value.IsValid() {
return false
}
for i := 0; i < len(node.Field); i++ {
value := getFieldOrMethodValue(node.Field[i], value)
if !value.IsValid() {
return false
}
}
resolved, _ := st.evalFieldAccessExpression(node)
return notNil(resolved)
default:
//todo: maybe work some edge cases
if !(nodeType > beginExpressions && nodeType < endExpressions) {
Expand Down Expand Up @@ -1104,14 +1120,9 @@ func (st *Runtime) evalBaseExpressionGroup(node Node) reflect.Value {
}
return resolved
case NodeChain:
node := node.(*ChainNode)
var resolved = st.evalPrimaryExpressionGroup(node.Node)
for i := 0; i < len(node.Field); i++ {
fieldValue := getFieldOrMethodValue(node.Field[i], resolved)
if !fieldValue.IsValid() {
node.errorf("there is no field or method %q in %s", node.Field[i], getTypeString(resolved))
}
resolved = fieldValue
resolved, err := st.evalFieldAccessExpression(node.(*ChainNode))
if err != nil {
node.error(err)
}
return resolved
case NodeNumber:
Expand Down Expand Up @@ -1169,6 +1180,17 @@ func (st *Runtime) evalCommandExpression(node *CommandNode) (reflect.Value, bool
return term, false
}

func (st *Runtime) evalFieldAccessExpression(node *ChainNode) (reflect.Value, error) {
resolved := st.evalPrimaryExpressionGroup(node.Node)
for i := 0; i < len(node.Field); i++ {
resolved = getFieldOrMethodValue(node.Field[i], resolved)
if !resolved.IsValid() {
return resolved, fmt.Errorf("there is no field or method %q in %s", node.Field[i], getTypeString(resolved))
}
}
return resolved, nil
}

type escapeWriter struct {
rawWriter io.Writer
safeWriter SafeWriter
Expand Down Expand Up @@ -1446,6 +1468,10 @@ var cachedStructsMutex = sync.RWMutex{}
var cachedStructsFieldIndex = map[reflect.Type]map[string][]int{}

func getFieldOrMethodValue(key string, v reflect.Value) reflect.Value {
if !v.IsValid() {
return reflect.Value{}
}

value := getValue(key, v)
if value.Kind() == reflect.Interface && !value.IsNil() {
value = value.Elem()
Expand Down Expand Up @@ -1586,11 +1612,11 @@ type sliceRanger struct {
i int
}

func (s *sliceRanger) Range() (index, value reflect.Value, end bool) {
func (s *sliceRanger) Range() (index, value interface{}, end bool) {
s.i++
index = reflect.ValueOf(&s.i).Elem()
index = s.i
if s.i < s.len {
value = s.v.Index(s.i)
value = s.v.Index(s.i).Interface()
return
}
pool_sliceRanger.Put(s)
Expand All @@ -1602,8 +1628,9 @@ type chanRanger struct {
v reflect.Value
}

func (s *chanRanger) Range() (_, value reflect.Value, end bool) {
value, end = s.v.Recv()
func (s *chanRanger) Range() (_, value interface{}, end bool) {
_value, end := s.v.Recv()
value = _value.Interface()
if end {
pool_chanRanger.Put(s)
}
Expand All @@ -1617,10 +1644,11 @@ type mapRanger struct {
i int
}

func (s *mapRanger) Range() (index, value reflect.Value, end bool) {
func (s *mapRanger) Range() (index, value interface{}, end bool) {
if s.i < s.len {
index = s.keys[s.i]
value = s.v.MapIndex(index)
_index := s.keys[s.i]
index = _index.Interface()
value = s.v.MapIndex(_index).Interface()
s.i++
return
}
Expand Down
14 changes: 14 additions & 0 deletions eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,20 @@ func TestEvalBuiltinExpression(t *testing.T) {
RunJetTest(t, data, nil, "LenExpression_1", `{{len("111")}}`, "3")
RunJetTest(t, data, nil, "LenExpression_2", `{{isset(data)?len(data):0}}`, "0")
RunJetTest(t, data, []string{"", "", "", ""}, "LenExpression_3", `{{len(.)}}`, "4")
data.Set(
"foo", map[string]interface{}{
"asd": map[string]string{
"bar": "baz",
},
},
)
RunJetTest(t, data, nil, "IsSetExpression_1", `{{isset(foo)}}`, "true")
RunJetTest(t, data, nil, "IsSetExpression_2", `{{isset(foo.asd)}}`, "true")
RunJetTest(t, data, nil, "IsSetExpression_3", `{{isset(foo.asd.bar)}}`, "true")
RunJetTest(t, data, nil, "IsSetExpression_4", `{{isset(asd)}}`, "false")
RunJetTest(t, data, nil, "IsSetExpression_5", `{{isset(foo.bar)}}`, "false")
RunJetTest(t, data, nil, "IsSetExpression_6", `{{isset(foo.asd.foo)}}`, "false")
RunJetTest(t, data, nil, "IsSetExpression_7", `{{isset(foo.asd.bar.xyz)}}`, "false")
}

func TestEvalAutoescape(t *testing.T) {
Expand Down
5 changes: 5 additions & 0 deletions func.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ type Arguments struct {
argVal []reflect.Value
}

// IsSet checks whether an argument is set or not. It behaves like the build-in isset function.
func (a *Arguments) IsSet(argumentIndex int) bool {
return a.runtime.isSet(a.argExpr[argumentIndex])
}

// Get gets an argument by index.
func (a *Arguments) Get(argumentIndex int) reflect.Value {
if argumentIndex < len(a.argVal) {
Expand Down
9 changes: 9 additions & 0 deletions global.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package jet

var abortTemplateOnError = true

// SetAbortTemplateOnError controls whether the template rendering process should be aborted when an error is encountered.
/// Default behavior is to abort the template rendering process when an error is encountered, so abortOnError == true.
func SetAbortTemplateOnError(abortOnError bool) {
abortTemplateOnError = abortOnError
}
4 changes: 3 additions & 1 deletion node.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ func (node *NodeBase) error(err error) {
}

func (node *NodeBase) errorf(format string, v ...interface{}) {
panic(fmt.Errorf("Jet Runtime Error(%q:%d): %s", node.TemplateName, node.Line, fmt.Sprintf(format, v...)))
if abortTemplateOnError {
panic(fmt.Errorf("Jet Runtime Error(%q:%d): %s", node.TemplateName, node.Line, fmt.Sprintf(format, v...)))
}
}

// Type returns itself and provides an easy default implementation
Expand Down