There are three kinds of expressions available in different contexts:
$send
$expect
or $when`$if
These are used when you're computing values to pass to messages and generally work as you would expect. Here are some examples. See more working tests with supported expressions at expr story++ and expr spec++.
The expressions are meant to feel natural, so simple stuff like 1 + 2 > 5
works as expected (yeah, false). You can have named operators, such as 1 contains 5
, as the language pretends to be human friendly, for instance $expect (result is number)
.
Also, you can throw in JS expressions any time you need extra expression power or access to basics already provided there, by using a js:
prefix, see examples below. Note that this is best avoided, as JS is much slower.
Associativity is left to right and precedence is as follows:
*,/
+,-,||,|
>,<,>=,<=,==,!=,~=,~path,?=,is,not,in,notIn,contains,containsNot
and,or,xor
map,flatMap,flatten,filter,exists,fold
as
1 + 2 is 3
works ok, but 1 > 2 is false
doesn't in some contexts, although you may have expected it to... so when outside the bounds of normal, either stay conservative and don't skimp on brackets (1 > 2) is false
and/or double-check things in the fiddle - it's super-easy to!
This is a quick reference guide for expressions - there are lots of examples with comments in expr story++.
// constants and values
$send ctx.set (
constants = 321,
addition = last+first,
jsonBlock={
"accountNumber": "1729534",
"start_num": 123
},
qualified = jsonBlock.accountNumber,
interpolation="is${1+2}",
url="${HOST}/search?q=dieselapps",
builtInFunctions1 = sizeOf(x=cart.items),
builtInFunctions2 = typeOf(x=cart.items),
anArray = [1,"a",3],
anotherArray = [5 .. (99/2)], // will generate an array easily
elem1 = anArray[1],
// json operations
cart=cart || {id:customer, items:[] } ,
cart=cart+{items:{sku : sku, quantity : quantity} } ,
sku1 = cart["items"].sku,
sku2 = cart[someVariableFieldName].subfield,
// embedded Javascript expressions
simpleJs = js:wix.diesel.env ,
res78=js:cart.items[0].sku ,
res40=js:email.replace(/(\w+)@(\w+).com/, "$1") ,
later=js:{var d = new Date(); d.setSeconds(d.getSeconds() + 10); d.toISOString();} ,
a284 = [1,2] + [3] filter (x=> x > 1) map (x=> x + 1),
a285 = [1 .. sizeOfStuff] map (x=> x + 1),
isThere = x in [1,2,3],
notThere = x not in [1,2,3],
size = size || 4 // JS style if defined OR default. WARN: Do not use || for "or", use "or" for boolean expr
)
$when something.hapenned
=> $if (AUTH ~= "Basic[^ ].*") (AUTH = "Basic " + AUTH[5..])
$when query.elk (search, size?) // add _id to the set of results from an elasticsearch query
=> elk.query(index=TABLE, search, size = size || 9)
=> (payload = payload.hits.hits map (z => z._source.report + {_id:z._id} ))
The built-in types understood are:
boolean
number
string
json
array
date
For each of these types, the operators behave in a straight forward manner.
The expressions are type-aware. Common sense type casts are done (string plus number is a string).
Use as
to force a typecast 5 as string
.
Use typeOf
to get the type of something.
Use is
or not
to check the type of something age is Number
.
Custom types and classes can be defined and used, see Domain Modelling. All defined types are derived from Json
and look at feel like Json.
Strings are simple constants, with escaping and natural inline expressions:
\b
, \n
, \t
, \r
, \f
\
is escaped, and not interpreted
\\
, \"
, \'
\
is escaped"${name}"
is expanded - note that the curly braces are mandatory.
"$${name}"
is not expanded - it's escapedUse """
to delimitate multi-line strings - same rules above apply. This is quite good for simple templates, as interpolation works anywhere.
Arrays can be statically defined, generated or come from a function/method:
$val arr1 = ["345", "asdf"]
$val arr2 = [5 .. 8]
$val arr3 = split (a="1,2,3,4", separator=",")
You can test if an element is in or not in an array:
$when test.diesel.isin(x in ["123", "234"], y not in [1,2])
Dates are ISO format, there is a built-in function now()
and the operators work intuitively. Here are some examples of date-related expressions. This is equivalent to this js expression: js:new Date().toISOString()
.
$var date = now()
$var future = now() + "5 seconds"
$var sameFuture = now() + "5s"
$var past = now() - "5s"
$val millis = toMillis(a=now())
$val alsoNow = millis as Date
Operators:
+
you can add a date with a duration-
you can subtract a date with a duration==
, >
etc)Conditions are used in $ifc
and here are some examples:
=> if (not false) (b=b+1, a02)
=> if (not a ~= b ) (b=b+1, a07)
=> if ( a < b or a > b ) (b=b+1, a12)
=> if ( a < b and (a > b or a < b) ) (b=b+1, a13)
There is also another if, using the match expressions: $ifm
. The default for $if
is the $ifc
with boolean expressions.
See expr story++, which is a live test of all expressions supported.
Besides the regular comparison operators, we have these.
$when test.diesel.isin(x in ["123", "234"], y not in [1,2])
$expect (["123", "234"] contains x, [1,2] containsNot y)
Note that you can't use contains
with $when
- the limitation there is that the named parameter has to be first element in the expression.
These are type-aware:
String
they simply look if the string contains or not the substringArray
they compare the entire elementJSON
or Map, they look if there is a key with the nameComplex types can be defined and used like so:
$class DieselTestStudent (name, address:DieselTestAddress)
$class DieselTestAddress (street, no)
$send ctx.echo(m = new DieselTestStudent {
name: "John",
address: new DieselTestAddress {
street: "Baker St",
no: "121b"
}
})
See Domain Modelling. All defined types are derived from Json
and look at feel like Json.
map
will work on objects too - it takes a function with one argument which will be an object with two attributes: key
and value
:
$val query={key:"elf", age:1000}
$send ctx.set (y = query map (x => x.key + ":\"" + x.value + "\""))
$send ctx.echo (x = y mkString " AND ")
Objects are JSON and are used much like a sensical Javascript would:
$val x = {a:1,b:"asdf"}
$val y = {a:1,b:"asdf"}
$send ctx.echo(z = x + y) // merge two objects
$val n = x.a
Arrays (or lists) are dynamic in size and look and feel much like in JS:
a284 = [1,2] + [3] filter (x=> x > 1) map (x=> x + 1)
x in [1,2]
x not in [1,2]
[3,4] contains y
[1,2] containsNot y
sizeOf([1,2,3])
The array operations are: map,flatMap,flatten,filter,exists
. The right side must be a lambda function:
map
will transform the valuesflatten
will flatten an array of arrays into a single arrayflatMap
transform and flattenfilter
keeps only the items meeting the predicateexists
true if an element meets the criteriamkString
takes a right-hand constant and will create a nice stringfold
start from payload and reduce the array using the lambdaindexBy
turn an array of objects into an object indexed by a key a284 = [1,2] + [3] filter (x=> x > 1) map (x=> x + 1)
a284 = [1,2] + [3] filter (x=> x > 1) map (x=> x + 1)
$send ctx.set (payload = 0)
$send ctx.set (z51 = [1,2,3,4] fold x => (payload + x))
$expect (z51 is 10)
$val z = (activeAlarms indexBy x => x.deviceId)
$val zz = (activeAlarms indexBy "deviceId")
Note that map
can also be applied to objects, in which case the x
will be a tuple with {key
, value
} as in the expression below.
$val AUTH_PERMS = payload.roles indexBy "name" map x => (x.value.permissions + {name:x.key}) indexBy "name"
as
- typecastsYou can typecast an expression, to change the type or make sure it's of a certain type. The right side expression can be:
x as number
x as Student
x as avalue
where avalue is "Student" will be re-evaluated as x as Student
This is a special transformation that can be applied to a json document, to flatten it into a list of parameters that can be used to call a message:
$when something.hapenned ()
=> (j = {a:1, b:2})
=> something.else (j.asAttrs)
j
would be flattened and a
and b
would be parameters to the new message something.else
.
You can get system properties with diesel.props.system
which will populate the payload with an object containing all system properties, IF running in self-hosted mode.
Likewise, you can load a property file with diesel.props.file
only in self-hosted mode. Or a JSON file via diesel.props.jsonFile
message, also only in self-hosted mode.
Matching rules for decomposition is done according to these pattern matching rules.
$when dieseltest.rule1(a,b)
=> (rule1ab=a+b)
This matches the message by name and list of values, no ambiguity.
You can match the entity with a *
- this will match a.sendtest
as well as a.b.c.sendtest
:
$when *.sendtest
=> (rule12=true)
Or, you can match the action with a *
- but note that this matches only the last word, i.e. the "action" - so it will match dieseltestsendtest.dothis
, but not dieseltestsendtest.a.b.c.dothis
:
$when dieseltestsendtest.*
=> (rule16=true)
$when dieseltest.send.multiple.*
=> (rule18=true)
You can use a full and more complex js-like match for both entity and action together:
$when /dieseltest.*/
=> (rule19=true)
$when /dieseltest\..*/
=> (rule19a=true)
The second rule above will match dieseltest.a.b.c.action
but not dieseltests.a.b.c.action
.
...can be builtinto the list of arguments, like diesel.isLocalhost is false
below - that is not an argument that is passed in, but a general condition:
$when diesel.realm.configure(realm is "devnetlinq", diesel.isLocalhost is false)
$expect
and matchesThese are match expressions, used in $expect
, $when
and $mock
. Their purpose is to match a parameter by name with a value expression. Some can also extract more parameters (say using regular expressions wiht named groups).
The full options around the expect are: $expect not IF MSG (matches) IF
not
does what you think it doesIF
you can add conditions - when this expect should be taken into accountNote that the expressions in the expect are NOT regular conditional expressions, but match expressions, which are quite alike but not the same thing...
$expect (name) // name is present
$expect (name == "Jane") // equal
$expect (name is "Jane") // equal
$expect (subId ~= "sub[0-9]*") // matches
$expect (subId matches "sub[0-9]*") // matches
$expect (subId matches "sub[0-9]*") // matches
Type checking
$expect (age is Number)
$expect (persons is Array)
$expect (name is String)
$expect (x1 is Student) // class defined in a domain model
$expect (x1 is Json)
// extract more parameters - will also populate an acctId when matching the regex
$mock diesel.rest (path ~= "/getAccount1/(?<acctId>\\d+)") => (payload={
"status": "ok",
"someStats": "67",
"accountId" : acctId
})
Note that while `diesel.rest` is a reserved message, you can use this implicit group parsing into variable in any message:
$when expr.captureGroups (path ~= "/getAccount1/(?<acctId>\\d+)") => (payload={
"accountId" : acctId
})
To avoid having to declare a rule just to do regex group decomposition, there is a `ctx.regex`:
$send ctx.set (payload = "/getAccount1/99")
$send ctx.regex(regex = "/getAccount1/(?<acctId3>\\d+)")
$expect (acctId3 is "99")
Or if you don't want to overwrite payload:
$send ctx.set (payload = "pa")
$send ctx.regex(regex = "/getAccount1/(?<acctId4>\\d+)", payload="/getAccount1/999")
$expect (acctId4 is "999")
$expect (payload is "pa")
Negative and other ideas:
$send ctx.set(d1 = now() - "1 second", d2 = now() + "1 second")
$expect (d1 < d2)
$expect not (d1 > d2)
$expect ((d1 > d2) is false)
Here's some other variants, with a more intuitive language:
$expect (name is empty) // name is NOT present OR is present but empty (type-aware)
$expect (name is defined) // name is present but may be empty
$expect (name not defined) // name is NOT present
$expect (name not empty) // name is present AND has some value non-empty (type-aware)
Note that you can also use other values for comparison - so take care to not use common reserved words for values:
$val jane = new Person{name:"Jane"}
$expect (guest is jane) // name is NOT present
Note that $expect
uses a match expression, so you can use a list of parameters and the result is composed:
$expect (name is "Jane", subId is defined) // both must match
There are a few built-in functions you can use in a variety of situations in a common manner x=sizeOf(anArray)
NOTE that these are sensitive to the order of arguments, unlike messages!
Returns a UUID using Mongo helpers (see object id in Mongo) - it is shorter than other UUIDs, when you don't need to encode the world!
Returns current time in ISO format
Returns current time in ISO format, date portion only
Returns hashcode of argument.
Compare two arguments, returning -1, 0, 1: cmp(op=">", a, b)
select either b or c, depending on first condition iff (a=payload>2, b=payload+2, c=payload+3)
Classic string format: sprintf(format="%d", value=x)
Test if first parm matches second - equivalent of the matches
, replaceAll
and replaceFirst
Java/Scala String method. The name of the parameters does not matter, the order does.
$send ctx.set (re176=matches(a="abc", b="[abc]+"))
$expect (re176 is true)
$send ctx.set (re176=matches(a="abc", b="[bc]+"))
$expect (re176 is false)
$send ctx.set (re176=replaceAll(a="abc", b="[bc]+", c="xx"))
$expect (re176 is "axx")
Encrypt the argument.
UTF-8 URL encoder.
Trim a string. Convert to upper or lower.
Split a string into array: split(string, regex)
Create a list from a range. The range is exclusive: rangeList(from=1, to=3) would be [1,2]
Calculates the size of an array/list (number of elements) or of an object (number of attributes) or length of a string.
$val builtInFunctions1 = sizeOf(x=cart.items),
$val builtInFunctions2 = typeOf(x=cart.items),
Returns the type of a parameter: typeOf(x=4)
See as
operator as well, for typecasts.
Use it to access a JSON object with an expression:
$send ctx.echo(x5=accessor(obj={a:{a:{a:1}}, b:{b:{b:2}}}, accessor="a.a.a"))
Flatten a lists of lists (or array of arrays).
Convert an ISO Z-datetime to milliseconds.
Are: math.min, math.max, math.average, math.sum
=> (theBigOne = math.max (x = [3,2,1]))
They take an array of numbers.
Formats a nice json if the value was a json.
You need to log in to post a comment!