Skip to content

Commit

Permalink
Merge pull request #456 from noahlaux/feat/default-in-switch
Browse files Browse the repository at this point in the history
`$default` arm  in `$switch` operator
  • Loading branch information
djmitche committed Dec 24, 2022
2 parents c8fe6d3 + 826c512 commit 4c9cd12
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 37 deletions.
13 changes: 10 additions & 3 deletions docs/src/operators.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,10 +257,11 @@ The `$switch` operator behaves like a combination of the `$if` and
in which every key is a string expression(s), where at most *one* must
evaluate to `true` and the remaining to `false` based on the context.
The result will be the value corresponding to the key that were
evaluated to `true`.
evaluated to `true` or optionally the fallback `$default` value.

If there are no matches, the result is either null or if used within an
object or array, omitted from the parent object.
If there are no matches, and no `$default` fallback is provided, the
result is either null or if used within an object or array, omitted
from the parent object.

```yaml,json-e
template: {$switch: {"x == 10": "ten", "x == 20": "twenty"}}
Expand Down Expand Up @@ -298,6 +299,12 @@ context: {cond: 3}
result: [0]
```

```yaml,json-e
template: [0, {$switch: {'cond > 3': 2, 'cond == 5': 3, $default: 4}}]
context: {cond: 1}
result: [4]
```

## `$merge`

The `$merge` operator merges an array of objects, returning a single object
Expand Down
16 changes: 15 additions & 1 deletion internal/jsone.go
Original file line number Diff line number Diff line change
Expand Up @@ -715,7 +715,9 @@ var operators = map[string]operator{

conditions := make([]string, 0, len(match))
for condition := range match {
conditions = append(conditions, condition)
if (condition != "$default") {
conditions = append(conditions, condition)
}
}

result := make([]interface{}, 0, len(match))
Expand Down Expand Up @@ -749,6 +751,18 @@ var operators = map[string]operator{
}
}

if len(result) == 0 && match["$default"] != nil {
value := match["$default"]
r, err := render(value, context)
if err != nil {
return nil, TemplateError{
Message: err.Error(),
Template: template,
}
}
result = append(result, r)
}

if len(result) == 0 {
return deleteMarker, nil
}
Expand Down
8 changes: 6 additions & 2 deletions js/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ operators.$match = (template, context) => {
};

operators.$switch = (template, context) => {
checkUndefinedProperties(template, ['\\$switch']);
checkUndefinedProperties(template, [ '\\$switch' ]);

if (!isObject(template['$switch'])) {
throw new TemplateError('$switch can evaluate objects only');
Expand All @@ -248,7 +248,7 @@ operators.$switch = (template, context) => {
let result = [];
const conditions = template['$switch'];

for (let condition of Object.keys(conditions)) {
for (let condition of Object.keys(conditions).filter(k => k !== '$default').sort()) {
if (isTruthy(parse(condition, context))) {
result.push(render(conditions[condition], context));
}
Expand All @@ -258,6 +258,10 @@ operators.$switch = (template, context) => {
throw new TemplateError('$switch can only have one truthy condition');
}

if (result.length === 0 && conditions[ '$default' ]) {
result.push(render(conditions[ '$default' ], context));
}

return result.length > 0 ? result[0] : deleteMarker;
};

Expand Down
1 change: 1 addition & 0 deletions py/jsone/newsfragments/455.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added a $default case to the switch operator
6 changes: 5 additions & 1 deletion py/jsone/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,12 +283,16 @@ def switch(template, context):

result = []
for condition in template['$switch']:
if parse(condition, context):
if not condition == '$default' and parse(condition, context):
result.append(renderValue(template['$switch'][condition], context))

if len(result) > 1:
raise TemplateError("$switch can only have one truthy condition")

if len(result) == 0:
if '$default' in template['$switch']:
result.append(renderValue(template['$switch']['$default'], context))

return result[0] if len(result) > 0 else DeleteMarker


Expand Down
69 changes: 41 additions & 28 deletions rs/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,9 +465,19 @@ fn match_operator(
context: &Context,
) -> Result<Value> {
check_operator_properties(operator, object, |_| false)?;
if let Value::Object(obj) = _render(value, context)? {
get_matching_conditions(obj, context)
.or(Err(template_error!("parsing error in $match condition")))
if let Value::Object(ref obj) = value {
let mut res = vec![];
for (cond, val) in obj {
if let Ok(cond) = evaluate(&cond, context) {
if !bool::from(cond) {
continue;
}
res.push(_render(val, context)?);
} else {
bail!(template_error!("parsing error in condition"));
}
}
Ok(Value::Array(res))
} else {
Err(template_error!("$match can evaluate objects only"))
}
Expand All @@ -479,38 +489,41 @@ fn switch_operator(
object: &Object,
context: &Context,
) -> Result<Value> {
check_operator_properties(operator, object, |_| false)?;
if let Value::Object(obj) = _render(value, context)? {
if let Ok(Value::Array(mut matches)) = get_matching_conditions(obj, context) {
if matches.len() == 0 {
Ok(Value::DeletionMarker)
} else if matches.len() > 1 {
Err(template_error!(
"$switch can only have one truthy condition"
))
if let Value::Object(ref obj) = value {
let mut res = None;
let mut unrendered_default = None;
for (cond, val) in obj {
// if the condition is `$default`, stash it for later
if cond == "$default" {
unrendered_default = Some(val);
continue;
}
// try to evaluate the condition
if let Ok(cond) = evaluate(&cond, context) {
if !bool::from(cond) {
continue;
}
if res.is_some() {
bail!(template_error!(
"$switch can only have one truthy condition"
))
}
res = Some(val);
} else {
Ok(matches.remove(0))
bail!(template_error!("parsing error in condition"));
}
} else {
Err(template_error!("parsing error in $switch condition"))
}
} else {
Err(template_error!("$switch can evaluate objects only"))
}
}

fn get_matching_conditions(object: Object, context: &Context) -> Result<Value> {
let mut result = Vec::new();
for (ref cond, val) in object {
if let Ok(v) = evaluate(cond, context) {
if v.into() {
result.push(val);
}
if let Some(res) = res {
_render(res, context)
} else if let Some(unrendered_default) = unrendered_default {
_render(unrendered_default, context)
} else {
return Err(template_error!("parsing error in condition"));
Ok(Value::DeletionMarker)
}
} else {
Err(template_error!("$switch can evaluate objects only"))
}
Ok(Value::Array(result))
}

fn merge_operator(
Expand Down
19 changes: 17 additions & 2 deletions specification.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
################################################################################
# ### Specificaiton for json-e
# ### Specification for json-e
# This document specifies the json-e template language through a series of
# test cases on the form: `{title, context, template, result, error, panic}`
# where `jsone.render(template, context) = result` and title is short name
Expand Down Expand Up @@ -1051,7 +1051,22 @@ title: $switch, value that also needs evaluation
context: {cond: 3, ifcond: false}
template: {$switch: {'cond == 3': {$if: 'ifcond', then: "t", else: "f"}}}
result: "f"
################################################################################
---
title: $switch, fallback to $default
context: {cond: 4}
template: {$switch: {'cond == 3': 1, $default: 'fallback'}}
result: "fallback"
---
title: $switch, $default value that also needs evaluation
context: {cond: 4, ifcond: false}
template: {$switch: {'cond == 3': 1, $default: {$if: 'ifcond', then: "t", else: "f"}}}
result: "f"
---
title: $switch, $default with 1 match in object
context: {cond: 3}
template: {$switch: {'cond == 3': 1, $default: 'fallback'}}
result: 1
###############################################################################
---
section: $merge
---
Expand Down

0 comments on commit 4c9cd12

Please sign in to comment.