v1.16.0
RestermScript gets append, min, max and math.sum, and you can use list, dict, text and math without the rts. prefix.
A few things that quietly gave wrong results in 1.15.0 fail in this version, such as searching a string for null and adding two lists or dictionaries with +. @use and @setting at the start of a ### block apply to that request instead of the whole file.
New RestermScript helpers
The validators.rts module from the 1.15.0 notes used rts.list.append. In 1.16.0 it looks like this:
module validators
export fn orders(list) {
let errs = []
for let _, o range list {
if o.total < 0 {
errs = append(errs, "order " + str(o.id) + ": total " + str(o.total) + " is negative")
}
if len(o.items) == 0 {
errs = append(errs, "order " + str(o.id) + ": no items")
}
}
return errs
}
The test output is the same as before:
Tests:
[FAIL] orders are valid - orders.http:7: order 1: total -5 is negative
orders.http:7: order 3: no items (82.25µs)
append(list, items...)returns a new list with the items added, soappend([1], 2, 3)is[1, 2, 3]. A null list counts as empty, and a list passed as an item stays one item:append([], [1])is[[1]]. Uselist.concatto join two lists.list.appendworks the same way and takes several items too.min(values...)andmax(values...)take one list, or two or more values, all numbers or all strings. An empty list or null gives null, somax(xs) ?? 0works.math.sum(list)adds up a list of numbers. An empty list or null gives0.
See the core helpers and math helpers docs.
Standard library names without rts.
list, dict, text and math work at the top level, like json and time already did. You can also declare your own json, len or any other library name.
### Cart
# @name cart
# @rts test "cart total"
> {%
fn price(item) {
return item.price
}
let json = response.json()
let prices = list.map(json.items, price)
if math.sum(prices) > 20 {
t.error("total " + str(math.sum(prices)) + " is over 20, top price " + str(max(prices)))
}
%}
GET {{base.url}}/cart1.15.0 stopped at let json, and the script would also have needed rts.list.map. math.sum and max didn't exist yet:
error[script]: name already defined: "json"
--> cart.http:8:1
Now it runs and reports the real failure:
Tests:
[FAIL] cart total - cart.http:11: total 23.75 is over 20, top price 12 (58.792µs)
- An
@usealias can be a library name too, such asjson. rts,stdliband host objects such asenvare still reserved at the top level of an@rtsblock.- Inside
@assert,text()is still the response body. Userts.textfor the text helpers there.
See name precedence.
Stricter null handling
Previosly, searching a string for null searched for an empty string, which every string contains. So this assertion passed when trace.id was never set:
# @assert vars.get("trace.id") in header("X-Trace")It fails in this release:
error[script]: cannot search a string for null
--> order.http:3:32
|
3 | # @assert vars.get("trace.id") in header("X-Trace")
| ^
The same goes for not in and contains(s, null). Membership in lists and dictionaries hasn't changed. If a missing value should fail the assertion instead of stopping the script, check for null first:
vars.get("trace.id") != null and vars.get("trace.id") in header("X-Trace")
text.startsWith, text.endsWith, text.split, text.replace, match, json.has and json.get reject a null value to search for. In 1.15.0 they treated it as an empty string, so text.startsWith(s, null) was always true and json.get(v, null) returned the whole value. Empty strings work as before, and so does searching in null: text.startsWith(null, "a") is still false.
See membership operators and text helpers.
+ on lists and dictionaries
In 1.15.0, + on two lists or two dictionaries turned both into text and joined them, so ["ops"] + ["billing"] gave the string ["ops"]["billing"]. Now it fails with cannot add two lists or cannot add two dicts. Use list.concat or append for lists and dict.merge for dictionaries. Joining text, as in "ids: " + ids, works as before.
@use and @setting stay in their request
An @use or @setting at the start of a ### block belongs to that request, even before @name or the request line. In 1.15.0 it applied to the whole file. Two requests that imported different modules as v failed with alias already defined: v, and a one-request base-url changed the URL of every request in the file. @settings works the same way.
To share an import or setting, put it above the first ###. A block with @use or @setting but no request warns:
@setting in a block without a request was ignored. Move it above the first ### to share it
Clearer error messages
Wrong argument counts give both numbers: len(x) expects 1 argument, got 0 instead of len(x) expects 1 args. Your own functions show their signature, so calling add with one argument gives add(a, b) expects 2 arguments, got 1 instead of arg count mismatch.
Errors inside a module show its path the way the request file's path is shown, such as rts/validators.rts:7:70, instead of the full absolute path.
Other fixes
list.sortputs NaN first. In 1.15.0,[3, num("NaN"), 1, 2]came back unsorted.list.sort([true])fails, aslist.sort([true, false])already did.
Upgrading from 1.15.0
Most scripts keep working. If you have request files, shared modules or CI checks, watch out for these:
+on two lists or dictionaries fails. Uselist.concat,appendordict.merge.null in s,null not in sandcontains(s, null)fail whensis a string. 1.15.0 treated null as an empty string, soinandcontainswere true andnot inwas false. Check for null first, as shown above.- A null value to search for fails in
text.startsWith,text.endsWith,text.split,text.replace,match,json.hasandjson.get, with errors such ascannot search for a null prefix. In 1.15.0,startsWith,endsWith,matchandjson.hasreturned true,splitsplit into single characters,replaceput the new text around every character, andjson.getreturned the whole value. Pass""if you relied on that. list.sortof one item that isn't a number or string, such as[true]or[null], fails withlist.sort(list) expects numbers or strings. 1.15.0 returned a copy.- Some error messages changed. Update CI checks that match them:
len(x) expects 1 argsislen(x) expects 1 argument, got 0.num(x[, def]) expects 1-2 argsisnum(x[, def]) expects 1 or 2 arguments, got 0.arg count mismatchisadd(a, b) expects 2 arguments, got 1, with the name and counts from the call.- A module error shows the module path like the request file, such as
rts/validators.rts:7:70, instead of the absolute path.
- The new top-level names don't break anything. If your script declares its own
listortext, it keeps using it. - An
@useat the start of a###block, or after only comments there, imports the module for that request only. Another request that used it fails withundefined name. Move shared imports above the first###. - An
@settingor@settingsat the start of a###block applies to that request only. That includes files written by--from-curl, where-kused to turn off TLS checks for every request in the file. Move file defaults above the first###. - Like
@name, a leading@useor@settingstarts the request, so lines after it belong to that request.@x = 1there is a request variable,@mockafter it is an error, and@workflowafter it drops the@useor@settingwith a warning. Put@mockand@workflowfirst in their block.
Upgrading from 1.14.1 or earlier
If you're upgrading from 1.14.1 or earlier, also read the 1.15.0 release notes. They cover changes to {% %} blocks, JSON paths, @rts options, exit codes and host objects in modules.