github unkn0wn-root/resterm v1.16.0

3 hours ago

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, so append([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]]. Use list.concat to join two lists. list.append works the same way and takes several items too.
  • min(values...) and max(values...) take one list, or two or more values, all numbers or all strings. An empty list or null gives null, so max(xs) ?? 0 works.
  • math.sum(list) adds up a list of numbers. An empty list or null gives 0.

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}}/cart

1.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 @use alias can be a library name too, such as json.
  • rts, stdlib and host objects such as env are still reserved at the top level of an @rts block.
  • Inside @assert, text() is still the response body. Use rts.text for 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.sort puts NaN first. In 1.15.0, [3, num("NaN"), 1, 2] came back unsorted.
  • list.sort([true]) fails, as list.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:

  1. + on two lists or dictionaries fails. Use list.concat, append or dict.merge.
  2. null in s, null not in s and contains(s, null) fail when s is a string. 1.15.0 treated null as an empty string, so in and contains were true and not in was false. Check for null first, as shown above.
  3. A null value to search for fails in text.startsWith, text.endsWith, text.split, text.replace, match, json.has and json.get, with errors such as cannot search for a null prefix. In 1.15.0, startsWith, endsWith, match and json.has returned true, split split into single characters, replace put the new text around every character, and json.get returned the whole value. Pass "" if you relied on that.
  4. list.sort of one item that isn't a number or string, such as [true] or [null], fails with list.sort(list) expects numbers or strings. 1.15.0 returned a copy.
  5. Some error messages changed. Update CI checks that match them:
    • len(x) expects 1 args is len(x) expects 1 argument, got 0.
    • num(x[, def]) expects 1-2 args is num(x[, def]) expects 1 or 2 arguments, got 0.
    • arg count mismatch is add(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.
  6. The new top-level names don't break anything. If your script declares its own list or text, it keeps using it.
  7. An @use at the start of a ### block, or after only comments there, imports the module for that request only. Another request that used it fails with undefined name. Move shared imports above the first ###.
  8. An @setting or @settings at the start of a ### block applies to that request only. That includes files written by --from-curl, where -k used to turn off TLS checks for every request in the file. Move file defaults above the first ###.
  9. Like @name, a leading @use or @setting starts the request, so lines after it belong to that request. @x = 1 there is a request variable, @mock after it is an error, and @workflow after it drops the @use or @setting with a warning. Put @mock and @workflow first 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.

Don't miss a new resterm release

NewReleases is sending notifications on new releases.