[
  {
    "given": {
      "foo": -1,
      "zero": 0,
      "numbers": [-1, 3, 4, 5],
      "array": [-1, 3, 4, 5, "a", "100"],
      "strings": ["a", "b", "c"],
      "decimals": [1.01, 1.2, -1.5],
      "str": "Str",
      "false": false,
      "empty_list": [],
      "empty_hash": {},
      "objects": { "foo": "bar", "bar": "baz" },
      "null_key": null
    },
    "cases": [
      {
        "expression": "abs(foo)",
        "result": 1
      },
      {
        "expression": "abs(str)",
        "error": "invalid-type"
      },
      {
        "expression": "abs(array[1])",
        "result": 3
      },
      {
        "expression": "abs(`false`)",
        "error": "invalid-type"
      },
      {
        "expression": "abs(`-24`)",
        "result": 24
      },
      {
        "expression": "abs(`24`)",
        "result": 24
      },
      {
        "expression": "abs(`1`, `2`)",
        "error": "invalid-arity"
      },
      {
        "expression": "abs()",
        "error": "invalid-arity"
      },
      {
        "expression": "unknown_function(`1`, `2`)",
        "error": "unknown-function"
      },
      {
        "expression": "avg(numbers)",
        "result": 2.75
      },
      {
        "expression": "avg(array)",
        "error": "invalid-type"
      },
      {
        "expression": "avg('abc')",
        "error": "invalid-type"
      },
      {
        "expression": "avg(foo)",
        "error": "invalid-type"
      },
      {
        "expression": "avg(@)",
        "error": "invalid-type"
      },
      {
        "expression": "avg(strings)",
        "error": "invalid-type"
      },
      {
        "expression": "ceil(`1.2`)",
        "result": 2
      },
      {
        "expression": "ceil(decimals[0])",
        "result": 2
      },
      {
        "expression": "ceil(decimals[1])",
        "result": 2
      },
      {
        "expression": "ceil(decimals[2])",
        "result": -1
      },
      {
        "expression": "ceil('string')",
        "error": "invalid-type"
      },
      {
        "expression": "contains('abc', 'a')",
        "result": true
      },
      {
        "expression": "contains('abc', 'd')",
        "result": false
      },
      {
        "expression": "contains(`false`, 'd')",
        "error": "invalid-type"
      },
      {
        "expression": "contains(strings, 'a')",
        "result": true
      },
      {
        "expression": "contains(decimals, `1.2`)",
        "result": true
      },
      {
        "expression": "contains(decimals, `false`)",
        "result": false
      },
      {
        "expression": "ends_with(str, 'r')",
        "result": true
      },
      {
        "expression": "ends_with(str, 'tr')",
        "result": true
      },
      {
        "expression": "ends_with(str, 'Str')",
        "result": true
      },
      {
        "expression": "ends_with(str, 'SStr')",
        "result": false
      },
      {
        "expression": "ends_with(str, 'foo')",
        "result": false
      },
      {
        "expression": "ends_with(str, `0`)",
        "error": "invalid-type"
      },
      {
        "expression": "floor(`1.2`)",
        "result": 1
      },
      {
        "expression": "floor('string')",
        "error": "invalid-type"
      },
      {
        "expression": "floor(decimals[0])",
        "result": 1
      },
      {
        "expression": "floor(foo)",
        "result": -1
      },
      {
        "expression": "floor(str)",
        "error": "invalid-type"
      },
      {
        "expression": "length('abc')",
        "result": 3
      },
      {
        "expression": "length('✓foo')",
        "result": 4
      },
      {
        "expression": "length('')",
        "result": 0
      },
      {
        "expression": "length(@)",
        "result": 12
      },
      {
        "expression": "length(strings[0])",
        "result": 1
      },
      {
        "expression": "length(str)",
        "result": 3
      },
      {
        "expression": "length(array)",
        "result": 6
      },
      {
        "expression": "length(objects)",
        "result": 2
      },
      {
        "expression": "length(`false`)",
        "error": "invalid-type"
      },
      {
        "expression": "length(foo)",
        "error": "invalid-type"
      },
      {
        "expression": "max(numbers)",
        "result": 5
      },
      {
        "expression": "max(decimals)",
        "result": 1.2
      },
      {
        "expression": "max(strings)",
        "result": "c"
      },
      {
        "expression": "max(abc)",
        "error": "invalid-type"
      },
      {
        "expression": "max(array)",
        "error": "invalid-type"
      },
      {
        "expression": "max(empty_list)",
        "result": null
      },
      {
        "expression": "merge(`{}`)",
        "result": {}
      },
      {
        "expression": "merge(`{}`, `{}`)",
        "result": {}
      },
      {
        "expression": "merge(`{\"a\": 1}`, `{\"b\": 2}`)",
        "result": { "a": 1, "b": 2 }
      },
      {
        "expression": "merge(`{\"a\": 1}`, `{\"a\": 2}`)",
        "result": { "a": 2 }
      },
      {
        "expression": "merge(`{\"a\": 1, \"b\": 2}`, `{\"a\": 2, \"c\": 3}`, `{\"d\": 4}`)",
        "result": { "a": 2, "b": 2, "c": 3, "d": 4 }
      },
      {
        "expression": "min(numbers)",
        "result": -1
      },
      {
        "expression": "min(decimals)",
        "result": -1.5
      },
      {
        "expression": "min(abc)",
        "error": "invalid-type"
      },
      {
        "expression": "min(array)",
        "error": "invalid-type"
      },
      {
        "expression": "min(empty_list)",
        "result": null
      },
      {
        "expression": "min(strings)",
        "result": "a"
      },
      {
        "expression": "type('abc')",
        "result": "string"
      },
      {
        "expression": "type(`1.0`)",
        "result": "number"
      },
      {
        "expression": "type(`2`)",
        "result": "number"
      },
      {
        "expression": "type(`true`)",
        "result": "boolean"
      },
      {
        "expression": "type(`false`)",
        "result": "boolean"
      },
      {
        "expression": "type(`null`)",
        "result": "null"
      },
      {
        "expression": "type(`[0]`)",
        "result": "array"
      },
      {
        "expression": "type(`{\"a\": \"b\"}`)",
        "result": "object"
      },
      {
        "expression": "type(@)",
        "result": "object"
      },
      {
        "expression": "sort(keys(objects))",
        "result": ["bar", "foo"]
      },
      {
        "expression": "keys(foo)",
        "error": "invalid-type"
      },
      {
        "expression": "keys(strings)",
        "error": "invalid-type"
      },
      {
        "expression": "keys(`false`)",
        "error": "invalid-type"
      },
      {
        "expression": "sort(values(objects))",
        "result": ["bar", "baz"]
      },
      {
        "expression": "keys(empty_hash)",
        "result": []
      },
      {
        "expression": "values(foo)",
        "error": "invalid-type"
      },
      {
        "expression": "join(', ', strings)",
        "result": "a, b, c"
      },
      {
        "expression": "join(',', `[\"a\", \"b\"]`)",
        "result": "a,b"
      },
      {
        "expression": "join(',', `[\"a\", 0]`)",
        "error": "invalid-type"
      },
      {
        "expression": "join(', ', str)",
        "error": "invalid-type"
      },
      {
        "expression": "join('|', strings)",
        "result": "a|b|c"
      },
      {
        "expression": "join(`2`, strings)",
        "error": "invalid-type"
      },
      {
        "expression": "join('|', decimals)",
        "error": "invalid-type"
      },
      {
        "expression": "join('|', decimals[].to_string(@))",
        "result": "1.01|1.2|-1.5"
      },
      {
        "expression": "join('|', empty_list)",
        "result": ""
      },
      {
        "expression": "reverse(numbers)",
        "result": [5, 4, 3, -1]
      },
      {
        "expression": "reverse(array)",
        "result": ["100", "a", 5, 4, 3, -1]
      },
      {
        "expression": "reverse(`[]`)",
        "result": []
      },
      {
        "expression": "reverse('')",
        "result": ""
      },
      {
        "expression": "reverse('hello world')",
        "result": "dlrow olleh"
      },
      {
        "expression": "starts_with(str, 'S')",
        "result": true
      },
      {
        "expression": "starts_with(str, 'St')",
        "result": true
      },
      {
        "expression": "starts_with(str, 'Str')",
        "result": true
      },
      {
        "expression": "starts_with(str, 'String')",
        "result": false
      },
      {
        "expression": "starts_with(str, `0`)",
        "error": "invalid-type"
      },
      {
        "expression": "sum(numbers)",
        "result": 11
      },
      {
        "expression": "sum(decimals)",
        "result": 0.71
      },
      {
        "expression": "sum(array)",
        "error": "invalid-type"
      },
      {
        "expression": "sum(array[].to_number(@))",
        "result": 111
      },
      {
        "expression": "sum(`[]`)",
        "result": 0
      },
      {
        "expression": "to_array('foo')",
        "result": ["foo"]
      },
      {
        "expression": "to_array(`0`)",
        "result": [0]
      },
      {
        "expression": "to_array(objects)",
        "result": [{ "foo": "bar", "bar": "baz" }]
      },
      {
        "expression": "to_array(`[1, 2, 3]`)",
        "result": [1, 2, 3]
      },
      {
        "expression": "to_array(false)",
        "result": [false]
      },
      {
        "expression": "to_string('foo')",
        "result": "foo"
      },
      {
        "expression": "to_string(`1.2`)",
        "result": "1.2"
      },
      {
        "expression": "to_string(`[0, 1]`)",
        "result": "[0,1]"
      },
      {
        "expression": "to_number('1.0')",
        "result": 1.0
      },
      {
        "expression": "to_number('1.1')",
        "result": 1.1
      },
      {
        "expression": "to_number('4')",
        "result": 4
      },
      {
        "expression": "to_number('notanumber')",
        "result": null
      },
      {
        "expression": "to_number(`false`)",
        "result": null
      },
      {
        "expression": "to_number(`null`)",
        "result": null
      },
      {
        "expression": "to_number(`[0]`)",
        "result": null
      },
      {
        "expression": "to_number(`{\"foo\": 0}`)",
        "result": null
      },
      {
        "expression": "\"to_string\"(`1.0`)",
        "error": "syntax"
      },
      {
        "expression": "sort(numbers)",
        "result": [-1, 3, 4, 5]
      },
      {
        "expression": "sort(strings)",
        "result": ["a", "b", "c"]
      },
      {
        "expression": "sort(decimals)",
        "result": [-1.5, 1.01, 1.2]
      },
      {
        "expression": "sort(array)",
        "error": "invalid-type"
      },
      {
        "expression": "sort(abc)",
        "error": "invalid-type"
      },
      {
        "expression": "sort(empty_list)",
        "result": []
      },
      {
        "expression": "sort(@)",
        "error": "invalid-type"
      },
      {
        "expression": "not_null(unknown_key, str)",
        "result": "Str"
      },
      {
        "expression": "not_null(unknown_key, foo.bar, empty_list, str)",
        "result": []
      },
      {
        "expression": "not_null(unknown_key, null_key, empty_list, str)",
        "result": []
      },
      {
        "expression": "not_null(all, expressions, are_null)",
        "result": null
      },
      {
        "expression": "not_null()",
        "error": "invalid-arity"
      },
      {
        "description": "function projection on single arg function",
        "expression": "numbers[].to_string(@)",
        "result": ["-1", "3", "4", "5"]
      },
      {
        "description": "function projection on single arg function",
        "expression": "array[].to_number(@)",
        "result": [-1, 3, 4, 5, 100]
      }
    ]
  },
  {
    "given": {
      "foo": [
        { "b": "b", "a": "a" },
        { "c": "c", "b": "b" },
        { "d": "d", "c": "c" },
        { "e": "e", "d": "d" },
        { "f": "f", "e": "e" }
      ]
    },
    "cases": [
      {
        "description": "function projection on variadic function",
        "expression": "foo[].not_null(f, e, d, c, b, a)",
        "result": ["b", "c", "d", "e", "f"]
      }
    ]
  },
  {
    "given": {
      "people": [
        {
          "age": 20,
          "age_str": "20",
          "bool": true,
          "name": "a",
          "extra": "foo"
        },
        {
          "age": 40,
          "age_str": "40",
          "bool": false,
          "name": "b",
          "extra": "bar"
        },
        { "age": 30, "age_str": "30", "bool": true, "name": "c" },
        { "age": 50, "age_str": "50", "bool": false, "name": "d" },
        { "age": 10, "age_str": "10", "bool": true, "name": 3 }
      ]
    },
    "cases": [
      {
        "description": "sort by field expression",
        "expression": "sort_by(people, &age)",
        "result": [
          { "age": 10, "age_str": "10", "bool": true, "name": 3 },
          {
            "age": 20,
            "age_str": "20",
            "bool": true,
            "name": "a",
            "extra": "foo"
          },
          { "age": 30, "age_str": "30", "bool": true, "name": "c" },
          {
            "age": 40,
            "age_str": "40",
            "bool": false,
            "name": "b",
            "extra": "bar"
          },
          { "age": 50, "age_str": "50", "bool": false, "name": "d" }
        ]
      },
      {
        "expression": "sort_by(people, &age_str)",
        "result": [
          { "age": 10, "age_str": "10", "bool": true, "name": 3 },
          {
            "age": 20,
            "age_str": "20",
            "bool": true,
            "name": "a",
            "extra": "foo"
          },
          { "age": 30, "age_str": "30", "bool": true, "name": "c" },
          {
            "age": 40,
            "age_str": "40",
            "bool": false,
            "name": "b",
            "extra": "bar"
          },
          { "age": 50, "age_str": "50", "bool": false, "name": "d" }
        ]
      },
      {
        "description": "sort by function expression",
        "expression": "sort_by(people, &to_number(age_str))",
        "result": [
          { "age": 10, "age_str": "10", "bool": true, "name": 3 },
          {
            "age": 20,
            "age_str": "20",
            "bool": true,
            "name": "a",
            "extra": "foo"
          },
          { "age": 30, "age_str": "30", "bool": true, "name": "c" },
          {
            "age": 40,
            "age_str": "40",
            "bool": false,
            "name": "b",
            "extra": "bar"
          },
          { "age": 50, "age_str": "50", "bool": false, "name": "d" }
        ]
      },
      {
        "description": "function projection on sort_by function",
        "expression": "sort_by(people, &age)[].name",
        "result": [3, "a", "c", "b", "d"]
      },
      {
        "expression": "sort_by(people, &extra)",
        "error": "invalid-type"
      },
      {
        "expression": "sort_by(people, &bool)",
        "error": "invalid-type"
      },
      {
        "expression": "sort_by(people, &name)",
        "error": "invalid-type"
      },
      {
        "expression": "sort_by(people, name)",
        "error": "invalid-type"
      },
      {
        "expression": "sort_by(people, &age)[].extra",
        "result": ["foo", "bar"]
      },
      {
        "expression": "sort_by(`[]`, &age)",
        "result": []
      },
      {
        "expression": "max_by(people, &age)",
        "result": { "age": 50, "age_str": "50", "bool": false, "name": "d" }
      },
      {
        "expression": "max_by(people, &age_str)",
        "result": { "age": 50, "age_str": "50", "bool": false, "name": "d" }
      },
      {
        "expression": "max_by(people, &bool)",
        "error": "invalid-type"
      },
      {
        "expression": "max_by(people, &extra)",
        "error": "invalid-type"
      },
      {
        "expression": "max_by(people, &to_number(age_str))",
        "result": { "age": 50, "age_str": "50", "bool": false, "name": "d" }
      },
      {
        "expression": "min_by(people, &age)",
        "result": { "age": 10, "age_str": "10", "bool": true, "name": 3 }
      },
      {
        "expression": "min_by(people, &age_str)",
        "result": { "age": 10, "age_str": "10", "bool": true, "name": 3 }
      },
      {
        "expression": "min_by(people, &bool)",
        "error": "invalid-type"
      },
      {
        "expression": "min_by(people, &extra)",
        "error": "invalid-type"
      },
      {
        "expression": "min_by(people, &to_number(age_str))",
        "result": { "age": 10, "age_str": "10", "bool": true, "name": 3 }
      }
    ]
  },
  {
    "given": {
      "people": [
        { "age": 10, "order": "1" },
        { "age": 10, "order": "2" },
        { "age": 10, "order": "3" },
        { "age": 10, "order": "4" },
        { "age": 10, "order": "5" },
        { "age": 10, "order": "6" },
        { "age": 10, "order": "7" },
        { "age": 10, "order": "8" },
        { "age": 10, "order": "9" },
        { "age": 10, "order": "10" },
        { "age": 10, "order": "11" }
      ]
    },
    "cases": [
      {
        "description": "stable sort order",
        "expression": "sort_by(people, &age)",
        "result": [
          { "age": 10, "order": "1" },
          { "age": 10, "order": "2" },
          { "age": 10, "order": "3" },
          { "age": 10, "order": "4" },
          { "age": 10, "order": "5" },
          { "age": 10, "order": "6" },
          { "age": 10, "order": "7" },
          { "age": 10, "order": "8" },
          { "age": 10, "order": "9" },
          { "age": 10, "order": "10" },
          { "age": 10, "order": "11" }
        ]
      }
    ]
  },
  {
    "given": {
      "people": [
        { "a": 10, "b": 1, "c": "z" },
        { "a": 10, "b": 2, "c": null },
        { "a": 10, "b": 3 },
        { "a": 10, "b": 4, "c": "z" },
        { "a": 10, "b": 5, "c": null },
        { "a": 10, "b": 6 },
        { "a": 10, "b": 7, "c": "z" },
        { "a": 10, "b": 8, "c": null },
        { "a": 10, "b": 9 }
      ],
      "empty": []
    },
    "cases": [
      {
        "expression": "map(&a, people)",
        "result": [10, 10, 10, 10, 10, 10, 10, 10, 10]
      },
      {
        "expression": "map(&c, people)",
        "result": ["z", null, null, "z", null, null, "z", null, null]
      },
      {
        "expression": "map(&a, badkey)",
        "error": "invalid-type"
      },
      {
        "expression": "map(&foo, empty)",
        "result": []
      }
    ]
  },
  {
    "given": {
      "array": [
        {
          "foo": { "bar": "yes1" }
        },
        {
          "foo": { "bar": "yes2" }
        },
        {
          "foo1": { "bar": "no" }
        }
      ]
    },
    "cases": [
      {
        "expression": "map(&foo.bar, array)",
        "result": ["yes1", "yes2", null]
      },
      {
        "expression": "map(&foo1.bar, array)",
        "result": [null, null, "no"]
      },
      {
        "expression": "map(&foo.bar.baz, array)",
        "result": [null, null, null]
      }
    ]
  },
  {
    "given": {
      "array": [[1, 2, 3, [4]], [5, 6, 7, [8, 9]]]
    },
    "cases": [
      {
        "expression": "map(&[], array)",
        "result": [[1, 2, 3, 4], [5, 6, 7, 8, 9]]
      }
    ]
  }
]
