From 7351cefd55cbba81ad5c1b9019f497fe4aa78d58 Mon Sep 17 00:00:00 2001 From: Theo Date: Mon, 29 Aug 2016 20:53:45 +0200 Subject: [PATCH 1/2] No need for public in interfaces --- .../io/burt/jmespath/function/ArgumentConstraint.java | 8 ++++---- .../src/main/java/io/burt/jmespath/function/Function.java | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/jmespath-core/src/main/java/io/burt/jmespath/function/ArgumentConstraint.java b/jmespath-core/src/main/java/io/burt/jmespath/function/ArgumentConstraint.java index cb370fb2..f79e6ed5 100644 --- a/jmespath-core/src/main/java/io/burt/jmespath/function/ArgumentConstraint.java +++ b/jmespath-core/src/main/java/io/burt/jmespath/function/ArgumentConstraint.java @@ -18,21 +18,21 @@ public interface ArgumentConstraint { * @throws ArityException when there are not enough arguments left to satisfy the constraint * @throws ArgumentTypeException when an argument does not satisfy the constraint */ - public void check(Adapter runtime, Iterator> arguments); + void check(Adapter runtime, Iterator> arguments); /** * @return the minimum number of arguments required. */ - public int minArity(); + int minArity(); /** * @return the maximum number of arguments accepted. */ - public int maxArity(); + int maxArity(); /** * @return a string representation of the types accepted. Used to construct * user friendly error messages. */ - public String expectedType(); + String expectedType(); } diff --git a/jmespath-core/src/main/java/io/burt/jmespath/function/Function.java b/jmespath-core/src/main/java/io/burt/jmespath/function/Function.java index c6a3d834..2e9cc070 100644 --- a/jmespath-core/src/main/java/io/burt/jmespath/function/Function.java +++ b/jmespath-core/src/main/java/io/burt/jmespath/function/Function.java @@ -17,7 +17,7 @@ public interface Function { * The name is either automatically generated from the class name, or * explicitly specified in the constructor. */ - public String name(); + String name(); /** * Call this function with a list of arguments. @@ -28,5 +28,5 @@ public interface Function { * @throws ArgumentTypeException when the function is called with arguments of the wrong type * @throws ArityException when the function is called with the wrong number of arguments */ - public T call(Adapter runtime, List> arguments); + T call(Adapter runtime, List> arguments); } From e92c683676759033d1912009d5ca0c2a9d8b202b Mon Sep 17 00:00:00 2001 From: Theo Date: Wed, 31 Aug 2016 08:59:22 +0200 Subject: [PATCH 2/2] Check function arity at compile time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have all the information we need to check whether or not a function is called with the correct number of arguments at compile time. Doing this will save us from having to deal with runtime errors. The runtime checking of arity still remains, but the plan is to remove it. The error message for ArityException has been changed to work better as a part of a ParseException error message, and the code that constructs ArityException messages is used by the compiler to create the ParseException message. I took the time to rewrite all default function tests so that they don't use `@Test(expected = …)` but instead catch the exception (or fail if no exception is thrown) and assert on the error message. Perhaps it's overkill to have tests for the arity and argument types of every single default function, but we have them so we might as well keep them. I added some additional tests to ParserTest to cover that arities are checked at parse/compile time. --- .../jmespath/function/ArityException.java | 26 +- .../burt/jmespath/function/BaseFunction.java | 9 +- .../io/burt/jmespath/function/Function.java | 6 + .../jmespath/parser/ExpressionParser.java | 9 + .../io/burt/jmespath/JmesPathRuntimeTest.java | 1046 +++++++++++++---- .../burt/jmespath/function/FunctionTest.java | 16 +- .../io/burt/jmespath/parser/ParserTest.java | 50 + 7 files changed, 899 insertions(+), 263 deletions(-) diff --git a/jmespath-core/src/main/java/io/burt/jmespath/function/ArityException.java b/jmespath-core/src/main/java/io/burt/jmespath/function/ArityException.java index fc07fac2..bd4e292c 100644 --- a/jmespath-core/src/main/java/io/burt/jmespath/function/ArityException.java +++ b/jmespath-core/src/main/java/io/burt/jmespath/function/ArityException.java @@ -2,21 +2,31 @@ @SuppressWarnings("serial") public class ArityException extends FunctionCallException { - public ArityException(String functionName, int minArity, int maxArity, int numArguments) { - this(functionName, minArity, maxArity, numArguments, null); + public ArityException(Function function, int numArguments) { + this(function, numArguments, null); } - public ArityException(String functionName, int minArity, int maxArity, int numArguments, Throwable cause) { - super(createMessage(functionName, minArity, maxArity, numArguments), cause); + public ArityException(Function function, int numArguments, Throwable cause) { + super(createMessage(function, numArguments, true), cause); } - private static String createMessage(String functionName, int minArity, int maxArity, int numArguments) { + public static String createMessage(Function function, int numArguments, boolean initialUppercase) { + int minArity = function.argumentConstraints().minArity(); + int maxArity = function.argumentConstraints().maxArity(); + StringBuilder buffer = new StringBuilder(); + if (initialUppercase) { + buffer.append("Invalid"); + } else { + buffer.append("invalid"); + } + buffer.append(" arity calling \"").append(function.name()).append("\""); if (maxArity == minArity) { - return String.format("Invalid arity calling \"%s\": expected %d but was %d", functionName, minArity, numArguments); + buffer.append(String.format(" (expected %d but was %d)", minArity, numArguments)); } else if (numArguments < minArity) { - return String.format("Invalid arity calling \"%s\": expected at least %d but was %d", functionName, minArity, numArguments); + buffer.append(String.format(" (expected at least %d but was %d)", minArity, numArguments)); } else { - return String.format("Invalid arity calling \"%s\": expected at most %d but was %d", functionName, maxArity, numArguments); + buffer.append(String.format(" (expected at most %d but was %d)", maxArity, numArguments)); } + return buffer.toString(); } } diff --git a/jmespath-core/src/main/java/io/burt/jmespath/function/BaseFunction.java b/jmespath-core/src/main/java/io/burt/jmespath/function/BaseFunction.java index 1a0b8d0a..065f334c 100644 --- a/jmespath-core/src/main/java/io/burt/jmespath/function/BaseFunction.java +++ b/jmespath-core/src/main/java/io/burt/jmespath/function/BaseFunction.java @@ -98,6 +98,11 @@ public String name() { return name; } + @Override + public ArgumentConstraint argumentConstraints() { + return argumentConstraints; + } + /** * Call this function with a list of arguments. * @@ -121,10 +126,10 @@ protected void checkArguments(Adapter runtime, List> Iterator> argumentIterator = arguments.iterator(); argumentConstraints.check(runtime, argumentIterator); if (argumentIterator.hasNext()) { - throw new ArityException(name(), argumentConstraints.minArity(), argumentConstraints.maxArity(), arguments.size()); + throw new ArityException(this, arguments.size()); } } catch (ArgumentConstraints.InternalArityException e) { - throw new ArityException(name(), argumentConstraints.minArity(), argumentConstraints.maxArity(), arguments.size(), e); + throw new ArityException(this, arguments.size(), e); } catch (ArgumentConstraints.InternalArgumentTypeException e) { throw new ArgumentTypeException(name(), e.expectedType(), e.actualType(), e); } diff --git a/jmespath-core/src/main/java/io/burt/jmespath/function/Function.java b/jmespath-core/src/main/java/io/burt/jmespath/function/Function.java index 2e9cc070..41396a0f 100644 --- a/jmespath-core/src/main/java/io/burt/jmespath/function/Function.java +++ b/jmespath-core/src/main/java/io/burt/jmespath/function/Function.java @@ -19,6 +19,12 @@ public interface Function { */ String name(); + /** + * Returns the constraints to use when checking the list of arguments before + * the function is called. + */ + ArgumentConstraint argumentConstraints(); + /** * Call this function with a list of arguments. *

diff --git a/jmespath-core/src/main/java/io/burt/jmespath/parser/ExpressionParser.java b/jmespath-core/src/main/java/io/burt/jmespath/parser/ExpressionParser.java index 2c96c01b..c309fb52 100644 --- a/jmespath-core/src/main/java/io/burt/jmespath/parser/ExpressionParser.java +++ b/jmespath-core/src/main/java/io/burt/jmespath/parser/ExpressionParser.java @@ -10,6 +10,8 @@ import io.burt.jmespath.Expression; import io.burt.jmespath.Adapter; import io.burt.jmespath.function.Function; +import io.burt.jmespath.function.ArgumentConstraint; +import io.burt.jmespath.function.ArityException; import io.burt.jmespath.util.StringEscapeHelper; import io.burt.jmespath.util.AntlrHelper; import io.burt.jmespath.node.NodeFactory; @@ -296,6 +298,13 @@ public Node visitFunctionExpression(JmesPathParser.FunctionExpressionContext if (implementation == null) { Token token = ctx.NAME().getSymbol(); errors.parseError(String.format("unknown function \"%s\"", name), token.getStartIndex()); + } else { + ArgumentConstraint argumentConstraints = implementation.argumentConstraints(); + if (n < argumentConstraints.minArity() || n > argumentConstraints.maxArity()) { + Token token = ctx.NAME().getSymbol(); + String message = ArityException.createMessage(implementation, n, false); + errors.parseError(message, token.getStartIndex()); + } } return createSequenceIfChained(nodeFactory.createFunctionCall(implementation, args)); } diff --git a/jmespath-core/src/test/java/io/burt/jmespath/JmesPathRuntimeTest.java b/jmespath-core/src/test/java/io/burt/jmespath/JmesPathRuntimeTest.java index cf2b7253..7a66088c 100644 --- a/jmespath-core/src/test/java/io/burt/jmespath/JmesPathRuntimeTest.java +++ b/jmespath-core/src/test/java/io/burt/jmespath/JmesPathRuntimeTest.java @@ -17,10 +17,10 @@ import java.util.Collections; import io.burt.jmespath.parser.ParseException; -import io.burt.jmespath.function.ArityException; import io.burt.jmespath.function.ArgumentTypeException; import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.instanceOf; @@ -264,6 +264,7 @@ public void literalStringIgnoresSource() { assertThat(result, is(jsonString("hello world"))); } + @Test public void flattenStartsProjection() { T result = search("Records[].userIdentity.userName", cloudtrail); assertThat(result, is(jsonArrayOfStrings("Alice", "Bob", "Alice"))); @@ -863,19 +864,34 @@ public void callVariadicFunction() { assertThat(result, is(jsonString("mykeypair"))); } - @Test(expected = ParseException.class) - public void callNonExistentFunctionThrowsParseException() { - search("bork()", parse("{}")); + @Test + public void callingNonExistentFunctionThrowsParseException() { + try { + search("bork()", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("unknown function \"bork\"")); + } } - @Test(expected = ArityException.class) - public void callFunctionWithTooFewArgumentsThrowsArityException() { - search("type()", parse("{}")); + @Test + public void callingAFunctionWithTooFewArgumentsIsACompileTimeError() { + try { + search("type()", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"type\" (expected 1 but was 0)")); + } } - @Test(expected = ArityException.class) - public void callFunctionWithTooManyArgumentsThrowsArityException() { - search("type(@, @, @)", parse("{}")); + @Test + public void callingAFunctionWithTooManyArgumentsIsACompileTimeError() { + try { + search("type(@, @, @)", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"type\" (expected 1 but was 3)")); + } } @Test @@ -886,19 +902,34 @@ public void absReturnsTheAbsoluteValueOfANumber() { assertThat(result2, is(jsonNumber(1))); } - @Test(expected = ArgumentTypeException.class) + @Test public void absRequiresANumberArgument() { - search("abs('foo')", parse("{}")); + try { + search("abs('foo')", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected number but was string")); + } } - @Test(expected = ArityException.class) + @Test public void absRequiresExactlyOneArgument() { - search("abs(`1`, `2`)", parse("{}")); + try { + search("abs(`1`, `2`)", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"abs\" (expected 1 but was 2)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void absRequiresAValue() { - search("abs(&foo)", parse("{}")); + try { + search("abs(&foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected number but was expression")); + } } @Test @@ -913,19 +944,34 @@ public void avgReturnsNullWhenGivenAnEmptyArray() { assertThat(result, is(jsonNull())); } - @Test(expected = ArgumentTypeException.class) + @Test public void avgRequiresAnArrayOfNumbers() { - search("avg('foo')", parse("{}")); + try { + search("avg('foo')", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of number but was string")); + } } - @Test(expected = ArityException.class) + @Test public void avgRequiresExactlyOneArgument() { - search("avg(`[]`, `[]`)", parse("{}")); + try { + search("avg(`[]`, `[]`)", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"avg\" (expected 1 but was 2)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void avgRequiresAValue() { - search("avg(&foo)", parse("{}")); + try { + search("avg(&foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of number but was expression")); + } } @Test @@ -952,34 +998,54 @@ public void containsSearchesInStrings() { assertThat(result, is(jsonBoolean(true))); } - @Test(expected = ArityException.class) - public void containsRequiresTwoArguments() { - search("contains(@)", parse("[]")); - } - - @Test(expected = ArgumentTypeException.class) + @Test public void containsRequiresAnArrayOrStringAsFirstArgument() { - search("contains(@, 'foo')", parse("{}")); + try { + search("contains(@, 'foo')", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array or string but was object")); + } } - @Test(expected = ArityException.class) + @Test public void containsRequiresTwoArguments1() { - search("contains('foo')", parse("{}")); + try { + search("contains('foo')", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"contains\" (expected 2 but was 1)")); + } } - @Test(expected = ArityException.class) + @Test public void containsRequiresTwoArguments2() { - search("contains('foo', 'bar', 'baz')", parse("{}")); + try { + search("contains('foo', 'bar', 'baz')", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"contains\" (expected 2 but was 3)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void containsRequiresValues1() { - search("contains(@, &foo)", parse("{}")); + try { + search("contains(@, &foo)", parse("[]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected any value but was expression")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void containsRequiresValues2() { - search("contains(&foo, 'bar')", parse("{}")); + try { + search("contains(&foo, 'bar')", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array or string but was expression")); + } } @Test @@ -990,19 +1056,34 @@ public void ceilReturnsTheNextWholeNumber() { assertThat(result2, is(jsonNumber(34))); } - @Test(expected = ArgumentTypeException.class) + @Test public void ceilRequiresANumberArgument() { - search("ceil('foo')", parse("{}")); + try { + search("ceil('foo')", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected number but was string")); + } } - @Test(expected = ArityException.class) + @Test public void ceilRequiresExactlyOneArgument() { - search("ceil(`1`, `2`)", parse("{}")); + try { + search("ceil(`1`, `2`)", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"ceil\" (expected 1 but was 2)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void ceilRequiresAValue() { - search("ceil(&foo)", parse("{}")); + try { + search("ceil(&foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected number but was expression")); + } } @Test @@ -1017,39 +1098,64 @@ public void endsWithReturnsFalseWhenTheFirstArgumentDoesNotEndWithTheSecond() { assertThat(result, is(jsonBoolean(false))); } - @Test(expected = ArityException.class) - public void endsWithRequiresTwoArguments() { - search("ends_with('')", parse("{}")); - } - - @Test(expected = ArgumentTypeException.class) + @Test public void endsWithRequiresAStringAsFirstArgument() { - search("ends_with(@, 'foo')", parse("{}")); + try { + search("ends_with(@, 'foo')", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected string but was object")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void endsWithRequiresAStringAsSecondArgument() { - search("ends_with('foo', @)", parse("{}")); + try { + search("ends_with('foo', @)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected string but was object")); + } } - @Test(expected = ArityException.class) + @Test public void endsWithRequiresTwoArguments1() { - search("ends_with('foo')", parse("{}")); + try { + search("ends_with('foo')", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"ends_with\" (expected 2 but was 1)")); + } } - @Test(expected = ArityException.class) + @Test public void endsWithRequiresTwoArguments2() { - search("ends_with('foo', 'bar', @)", parse("{}")); + try { + search("ends_with('foo', 'bar', @)", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"ends_with\" (expected 2 but was 3)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void endsWithRequiresAValue1() { - search("ends_with(&foo, 'bar')", parse("{}")); + try { + search("ends_with(&foo, 'bar')", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected string but was expression")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void endsWithRequiresAValue2() { - search("ends_with('foo', &bar)", parse("{}")); + try { + search("ends_with('foo', &bar)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected string but was expression")); + } } @Test @@ -1060,19 +1166,34 @@ public void floorReturnsThePreviousWholeNumber() { assertThat(result2, is(jsonNumber(33))); } - @Test(expected = ArgumentTypeException.class) + @Test public void floorRequiresANumberArgument() { - search("floor('foo')", parse("{}")); + try { + search("floor('foo')", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected number but was string")); + } } - @Test(expected = ArityException.class) + @Test public void floorRequiresExactlyOneArgument() { - search("floor(`1`, `2`)", parse("{}")); + try { + search("floor(`1`, `2`)", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"floor\" (expected 1 but was 2)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void floorRequiresAValue() { - search("floor(&foo)", parse("{}")); + try { + search("floor(&foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected number but was expression")); + } } @Test @@ -1089,24 +1210,44 @@ public void joinHandlesDuplicates() { assertThat(result, is(jsonString("foo|foo|foo"))); } - @Test(expected = ArgumentTypeException.class) + @Test public void joinRequiresAStringAsFirstArgument() { - search("join(`3`, @)", parse("[\"foo\", 3, \"bar\", \"baz\"]")); + try { + search("join(`3`, @)", parse("[\"foo\", 3, \"bar\", \"baz\"]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected string but was number")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void joinRequiresAStringArrayAsSecondArgument() { - search("join('|', @)", parse("[\"foo\", 3, \"bar\", \"baz\"]")); + try { + search("join('|', @)", parse("[\"foo\", 3, \"bar\", \"baz\"]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of string but was array containing string and number")); + } } - @Test(expected = ArityException.class) + @Test public void joinRequiresTwoArguments1() { - search("join('|')", parse("[]")); + try { + search("join('|')", parse("[]")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"join\" (expected 2 but was 1)")); + } } - @Test(expected = ArityException.class) + @Test public void joinRequiresTwoArguments2() { - search("join('|', @, @)", parse("[]")); + try { + search("join('|', @, @)", parse("[]")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"join\" (expected 2 but was 3)")); + } } @Test @@ -1115,14 +1256,24 @@ public void joinWithAnEmptyArrayReturnsAnEmptyString() { assertThat(result, is(jsonString(""))); } - @Test(expected = ArgumentTypeException.class) + @Test public void joinRequiresAValue1() { - search("join(&foo, @)", parse("{}")); + try { + search("join(&foo, @)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected string but was expression")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void joinRequiresAValue2() { - search("join('foo', &bar)", parse("{}")); + try { + search("join('foo', &bar)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of string but was expression")); + } } @Test @@ -1138,19 +1289,34 @@ public void keysReturnsAnEmptyArrayWhenGivenAnEmptyObject() { assertThat(result, is(parse("[]"))); } - @Test(expected = ArgumentTypeException.class) + @Test public void keysRequiresAnObjectAsArgument() { - search("keys(@)", parse("[3]")); + try { + search("keys(@)", parse("[3]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected object but was array")); + } } - @Test(expected = ArityException.class) + @Test public void keysRequiresASingleArgument() { - search("keys(@, @)", parse("{}")); + try { + search("keys(@, @)", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"keys\" (expected 1 but was 2)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void keysRequiresAValue() { - search("keys(&foo)", parse("{}")); + try { + search("keys(&foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected object but was expression")); + } } @Test @@ -1183,14 +1349,44 @@ public void lengthCanBeUsedInComparisons() { assertThat(result, is(jsonBoolean(true))); } - @Test(expected = ArgumentTypeException.class) + @Test public void lengthRequiresAStringArrayOrObjectAsArgument() { - search("length(@)", parse("3")); + try { + search("length(@)", parse("3")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected string, array or object but was number")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void lengthRequiresAValue() { - search("length(&foo)", parse("{}")); + try { + search("length(&foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected string, array or object but was expression")); + } + } + + @Test + public void lengthRequiresAnArgument() { + try { + search("length()", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"length\" (expected 1 but was 0)")); + } + } + + @Test + public void lengthRequiresExactlyOneArgument() { + try { + search("length(@, @)", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"length\" (expected 1 but was 2)")); + } } @Test @@ -1223,29 +1419,54 @@ public void mapAcceptsAnArrayOfNumbers() { assertThat(result, is(parse("[\"1\", \"-2\", \"3\"]"))); } - @Test(expected = ArgumentTypeException.class) + @Test public void mapRequiresAnExpressionAsFirstArgument() { - search("map(@, @)", parse("[]")); + try { + search("map(@, @)", parse("[]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected expression but was array")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void mapRequiresAnArrayAsSecondArgument1() { - search("map(&foo, @)", parse("{}")); + try { + search("map(&foo, @)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of any value but was object")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void mapRequiresAnArrayAsSecondArgument2() { - search("map(@, &foo)", parse("[]")); + try { + search("map(@, &foo)", parse("[]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected expression but was array")); + } } - @Test(expected = ArityException.class) + @Test public void mapRequiresTwoArguments1() { - search("map(&foo.bar)", parse("[]")); + try { + search("map(&foo.bar)", parse("[]")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"map\" (expected 2 but was 1)")); + } } - @Test(expected = ArityException.class) + @Test public void mapRequiresTwoArguments2() { - search("map(&foo.bar, @, @)", parse("[]")); + try { + search("map(&foo.bar, @, @)", parse("[]")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"map\" (expected 2 but was 3)")); + } } @Test @@ -1266,24 +1487,44 @@ public void maxReturnsNullWhenGivenAnEmptyArray() { assertThat(result, is(jsonNull())); } - @Test(expected = ArgumentTypeException.class) + @Test public void maxRequiresAnArrayOfNumbersOrStrings() { - search("max('foo')", parse("{}")); + try { + search("max('foo')", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of number or string but was string")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void maxRequiresTheElementsToBeOfTheSameType() { - search("max(`[\"foo\", 1]`)", parse("{}")); + try { + search("max(`[\"foo\", 1]`)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of number or string but was array containing string and number")); + } } - @Test(expected = ArityException.class) + @Test public void maxRequiresExactlyOneArgument() { - search("max(`[]`, `[]`)", parse("{}")); + try { + search("max(`[]`, `[]`)", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"max\" (expected 1 but was 2)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void maxRequiresAValue() { - search("max(&foo)", parse("{}")); + try { + search("max(&foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of number or string but was expression")); + } } @Test @@ -1304,39 +1545,74 @@ public void maxByReturnsWithAnEmptyArrayReturnsNull() { assertThat(result, is(jsonNull())); } - @Test(expected = ArgumentTypeException.class) - public void maxByDoesNotAcceptMixedResults() { - search("max_by(@, &foo)", parse("[{\"foo\": 3}, {\"foo\": \"bar\"}, {\"foo\": 1}]")); + @Test + public void maxByDoesNotAcceptExpressionsThatResultInMixedResults() { + try { + search("max_by(@, &foo)", parse("[{\"foo\": 3}, {\"foo\": \"bar\"}, {\"foo\": 1}]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected number but was string")); + } } - @Test(expected = ArgumentTypeException.class) - public void maxByDoesNotAcceptNonStringsOrNumbers() { - search("max_by(@, &foo)", parse("[{\"foo\": []}]")); + @Test + public void maxByDoesNotAcceptExpressionsThatResultInNonStringsOrNumbers() { + try { + search("max_by(@, &foo)", parse("[{\"foo\": []}]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected number or string but was array")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void maxByRequiresAnArrayAsFirstArgument1() { - search("max_by(@, &foo)", parse("{}")); + try { + search("max_by(@, &foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of object but was object")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void maxByRequiresAnArrayAsFirstArgument2() { - search("max_by(&foo, @)", parse("[]")); + try { + search("max_by(&foo, @)", parse("[]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of object but was expression")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void maxByRequiresAnExpressionAsSecondArgument() { - search("max_by(@, @)", parse("[]")); + try { + search("max_by(@, @)", parse("[]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected expression but was array")); + } } - @Test(expected = ArityException.class) + @Test public void maxByRequiresTwoArguments1() { - search("max_by(@)", parse("[]")); + try { + search("max_by(@)", parse("[]")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"max_by\" (expected 2 but was 1)")); + } } - @Test(expected = ArityException.class) + @Test public void maxByRequiresTwoArguments2() { - search("max_by(@, &foo, @)", parse("[]")); + try { + search("max_by(@, &foo, @)", parse("[]")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"max_by\" (expected 2 but was 3)")); + } } @Test @@ -1357,24 +1633,44 @@ public void mergeDoesNotMutate() { assertThat(result, is(parse("{\"a\": 1, \"b\": 1}"))); } - @Test(expected = ArgumentTypeException.class) + @Test public void mergeRequiresObjectArguments1() { - search("merge('foo', 'bar')", parse("{}")); + try { + search("merge('foo', 'bar')", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected object but was string")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void mergeRequiresObjectArguments2() { - search("merge(`{}`, @)", parse("[]")); + try { + search("merge(`{}`, @)", parse("[]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected object but was array")); + } } - @Test(expected = ArityException.class) + @Test public void mergeRequiresAtLeastOneArgument() { - search("merge()", parse("{}")); + try { + search("merge()", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"merge\" (expected at least 1 but was 0)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void mergeRequiresAValue() { - search("merge(&foo)", parse("{}")); + try { + search("merge(&foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected object but was expression")); + } } @Test @@ -1395,24 +1691,44 @@ public void minReturnsNullWhenGivenAnEmptyArray() { assertThat(result, is(jsonNull())); } - @Test(expected = ArgumentTypeException.class) + @Test public void minRequiresAnArrayOfNumbersOrStrings() { - search("min('foo')", parse("{}")); + try { + search("min('foo')", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of number or string but was string")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void minRequiresTheElementsToBeOfTheSameType() { - search("min(`[\"foo\", 1]`)", parse("{}")); + try { + search("min(`[\"foo\", 1]`)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of number or string but was array containing string and number")); + } } - @Test(expected = ArityException.class) + @Test public void minRequiresExactlyOneArgument() { - search("min(`[]`, `[]`)", parse("{}")); + try { + search("min(`[]`, `[]`)", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"min\" (expected 1 but was 2)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void minRequiresAValue() { - search("min(&foo)", parse("{}")); + try { + search("min(&foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of number or string but was expression")); + } } @Test @@ -1433,39 +1749,74 @@ public void minByReturnsWithAnEmptyArrayReturnsNull() { assertThat(result, is(jsonNull())); } - @Test(expected = ArgumentTypeException.class) + @Test public void minByDoesNotAcceptMixedResults() { - search("min_by(@, &foo)", parse("[{\"foo\": 3}, {\"foo\": \"bar\"}, {\"foo\": 1}]")); + try { + search("min_by(@, &foo)", parse("[{\"foo\": 3}, {\"foo\": \"bar\"}, {\"foo\": 1}]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected number but was string")); + } } - @Test(expected = ArgumentTypeException.class) - public void minByDoesNotAcceptNonStringsOrNumbers() { - search("min_by(@, &foo)", parse("[{\"foo\": []}]")); + @Test + public void minByDoesNotAcceptExpressionsThatResultInNonStringsOrNumbers() { + try { + search("min_by(@, &foo)", parse("[{\"foo\": []}]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected number or string but was array")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void minByRequiresAnArrayAsFirstArgument1() { - search("min_by(@, &foo)", parse("{}")); + try { + search("min_by(@, &foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of object but was object")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void minByRequiresAnArrayAsFirstArgument2() { - search("min_by(&foo, @)", parse("[]")); + try { + search("min_by(&foo, @)", parse("[]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of object but was expression")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void minByRequiresAnExpressionAsSecondArgument() { - search("min_by(@, @)", parse("[]")); + try { + search("min_by(@, @)", parse("[]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected expression but was array")); + } } - @Test(expected = ArityException.class) + @Test public void minByRequiresTwoArguments1() { - search("min_by(@)", parse("[]")); + try { + search("min_by(@)", parse("[]")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"min_by\" (expected 2 but was 1)")); + } } - @Test(expected = ArityException.class) + @Test public void minByRequiresTwoArguments2() { - search("min_by(@, &foo, @)", parse("[]")); + try { + search("min_by(@, &foo, @)", parse("[]")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"min_by\" (expected 2 but was 3)")); + } } @Test @@ -1480,19 +1831,34 @@ public void notNullReturnsNullWhenGivenOnlyNull() { assertThat(result, is(jsonNull())); } - @Test(expected = ArityException.class) + @Test public void notNullRequiresAtLeastOneArgument() { - search("not_null()", parse("{}")); + try { + search("not_null()", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"not_null\" (expected at least 1 but was 0)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void notNullRequiresAValue() { - search("not_null(`null`, &foo)", parse("{}")); + try { + search("not_null(`null`, &foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected any value but was expression")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void notNullRequiresAValueForArgumentsThatAreNotInspected() { - search("not_null('foo', &foo)", parse("{}")); + try { + search("not_null('foo', &foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected any value but was expression")); + } } @Test @@ -1519,24 +1885,43 @@ public void reverseReturnsAnEmptyStringWhenGivenAnEmptyString() { assertThat(result, is(jsonString(""))); } - @Test(expected = ArityException.class) + @Test public void reverseRequiresOneArgument1() { - search("reverse()", parse("[]")); + try { + search("reverse()", parse("[]")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"reverse\" (expected 1 but was 0)")); + } } - @Test(expected = ArityException.class) + @Test public void reverseRequiresOneArgument2() { - search("reverse(@, @)", parse("[]")); + try { + search("reverse(@, @)", parse("[]")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"reverse\" (expected 1 but was 2)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void reverseRequiresAnArrayAsArgument() { - search("reverse(@)", parse("{}")); + try { + search("reverse(@)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array or string but was object")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void reverseRequiresAValue() { - search("reverse(&foo)", parse("{}")); + try { + search("reverse(&foo)", parse("{}")); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array or string but was expression")); + } } @Test @@ -1563,29 +1948,54 @@ public void sortReturnsAnEmptyArrayWhenGivenAnEmptyArray() { assertThat(result, is(parse("[]"))); } - @Test(expected = ArityException.class) + @Test public void sortRequiresOneArgument1() { - search("sort()", parse("[]")); + try { + search("sort()", parse("[]")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"sort\" (expected 1 but was 0)")); + } } - @Test(expected = ArityException.class) + @Test public void sortRequiresOneArgument2() { - search("sort(@, @)", parse("[]")); + try { + search("sort(@, @)", parse("[]")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"sort\" (expected 1 but was 2)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void sortRequiresAnArrayAsArgument() { - search("sort(@)", parse("{}")); + try { + search("sort(@)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of number or string but was object")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void sortDoesNotAcceptMixedInputs() { - search("sort(@)", parse("[1, \"foo\"]")); + try { + search("sort(@)", parse("[1, \"foo\"]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of number or string but was array containing number and string")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void sortRequiresAValue() { - search("sort(&foo)", parse("{}")); + try { + search("sort(&foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of number or string but was expression")); + } } @Test @@ -1618,39 +2028,74 @@ public void sortByReturnsWithAnEmptyArrayReturnsNull() { assertThat(result, is(parse("[]"))); } - @Test(expected = ArgumentTypeException.class) + @Test public void sortByDoesNotAcceptMixedResults() { - search("sort_by(@, &foo)", parse("[{\"foo\": 3}, {\"foo\": \"bar\"}, {\"foo\": 1}]")); + try { + search("sort_by(@, &foo)", parse("[{\"foo\": 3}, {\"foo\": \"bar\"}, {\"foo\": 1}]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected number but was string")); + } } - @Test(expected = ArgumentTypeException.class) - public void sortByDoesNotAcceptNonStringsOrNumbers() { - search("sort_by(@, &foo)", parse("[{\"foo\": []}]")); + @Test + public void sortByDoesNotAcceptExpressionsThatResultInNonStringsOrNumbers() { + try { + search("sort_by(@, &foo)", parse("[{\"foo\": []}]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected number or string but was array")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void sortByRequiresAnArrayAsFirstArgument1() { - search("sort_by(@, &foo)", parse("{}")); + try { + search("sort_by(@, &foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of object but was object")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void sortByRequiresAnArrayAsFirstArgument2() { - search("sort_by(&foo, @)", parse("[]")); + try { + search("sort_by(&foo, @)", parse("[]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of object but was expression")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void sortByRequiresAnExpressionAsSecondArgument() { - search("sort_by(@, @)", parse("[]")); + try { + search("sort_by(@, @)", parse("[]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected expression but was array")); + } } - @Test(expected = ArityException.class) + @Test public void sortByRequiresTwoArguments1() { - search("sort_by(@)", parse("[]")); + try { + search("sort_by(@)", parse("[]")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"sort_by\" (expected 2 but was 1)")); + } } - @Test(expected = ArityException.class) + @Test public void sortByRequiresTwoArguments2() { - search("sort_by(@, &foo, @)", parse("[]")); + try { + search("sort_by(@, &foo, @)", parse("[]")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"sort_by\" (expected 2 but was 3)")); + } } @Test @@ -1665,39 +2110,60 @@ public void startsWithReturnsFalseWhenTheFirstArgumentDoesNotEndWithTheSecond() assertThat(result, is(jsonBoolean(false))); } - @Test(expected = ArityException.class) - public void startsWithRequiresTwoArguments() { - search("starts_with('')", parse("{}")); - } - - @Test(expected = ArgumentTypeException.class) + @Test public void startsWithRequiresAStringAsFirstArgument() { - search("starts_with(@, 'foo')", parse("{}")); + try { + search("starts_with(@, 'foo')", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected string but was object")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void startsWithRequiresAStringAsSecondArgument() { - search("starts_with('foo', @)", parse("{}")); + try { + search("starts_with('foo', @)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected string but was object")); + } } - @Test(expected = ArityException.class) + @Test public void startsWithRequiresTwoArguments1() { - search("starts_with('foo')", parse("{}")); + try { + search("starts_with('foo')", parse("{}")); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"starts_with\" (expected 2 but was 1)")); + } } - @Test(expected = ArityException.class) + @Test public void startsWithRequiresTwoArguments2() { - search("starts_with('foo', 'bar', @)", parse("{}")); + try { + search("starts_with('foo', 'bar', @)", parse("{}")); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"starts_with\" (expected 2 but was 3)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void startsWithRequiresAValue1() { - search("starts_with(&foo, 'bar')", parse("{}")); + try { + search("starts_with(&foo, 'bar')", parse("{}")); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected string but was expression")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void startsWithRequiresAValue2() { - search("starts_with('foo', &bar)", parse("{}")); + try { + search("starts_with('foo', &bar)", parse("{}")); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected string but was expression")); + } } @Test @@ -1712,19 +2178,34 @@ public void sumReturnsZeroWhenGivenAnEmptyArray() { assertThat(result, is(jsonNumber(0))); } - @Test(expected = ArgumentTypeException.class) + @Test public void sumRequiresAnArrayOfNumbers() { - search("sum('foo')", parse("{}")); + try { + search("sum('foo')", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of number but was string")); + } } - @Test(expected = ArityException.class) + @Test public void sumRequiresExactlyOneArgument() { - search("sum(`[]`, `[]`)", parse("{}")); + try { + search("sum(`[]`, `[]`)", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"sum\" (expected 1 but was 2)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void sumRequiresAValue() { - search("sum(&foo)", parse("{}")); + try { + search("sum(&foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected array of number but was expression")); + } } @Test @@ -1739,19 +2220,34 @@ public void toArrayWithAnArrayReturnsTheArgument() { assertThat(result, is(parse("[0, 1, 2, 3.5, 4]"))); } - @Test(expected = ArityException.class) + @Test public void toArrayRequiresExactlyOneArgument1() { - search("to_array()", parse("{}")); + try { + search("to_array()", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"to_array\" (expected 1 but was 0)")); + } } - @Test(expected = ArityException.class) + @Test public void toArrayRequiresExactlyOneArgument2() { - search("to_array(`1`, `2`)", parse("{}")); + try { + search("to_array(`1`, `2`)", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"to_array\" (expected 1 but was 2)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void toArrayRequiresAValue() { - search("to_array(&foo)", parse("{}")); + try { + search("to_array(&foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected any value but was expression")); + } } @Test @@ -1785,19 +2281,34 @@ public void toStringWithAStringReturnsTheArgument() { assertThat(result, is(jsonString("hello"))); } - @Test(expected = ArityException.class) + @Test public void toStringRequiresExactlyOneArgument1() { - search("to_string()", parse("{}")); + try { + search("to_string()", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"to_string\" (expected 1 but was 0)")); + } } - @Test(expected = ArityException.class) + @Test public void toStringRequiresExactlyOneArgument2() { - search("to_string(`1`, `2`)", parse("{}")); + try { + search("to_string(`1`, `2`)", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"to_string\" (expected 1 but was 2)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void toStringRequiresAValue() { - search("to_string(&foo)", parse("{}")); + try { + search("to_string(&foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected any value but was expression")); + } } @Test @@ -1848,19 +2359,34 @@ public void toNumberReturnsNullWhenGivenNull() { assertThat(result, is(jsonNull())); } - @Test(expected = ArityException.class) + @Test public void toNumberRequiresExactlyOneArgument1() { - search("to_number()", parse("{}")); + try { + search("to_number()", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"to_number\" (expected 1 but was 0)")); + } } - @Test(expected = ArityException.class) + @Test public void toNumberRequiresExactlyOneArgument2() { - search("to_number(`1`, `2`)", parse("{}")); + try { + search("to_number(`1`, `2`)", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"to_number\" (expected 1 but was 2)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void toNumberRequiresAValue() { - search("to_number(&foo)", parse("{}")); + try { + search("to_number(&foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected any value but was expression")); + } } @Test @@ -1873,19 +2399,34 @@ public void typeReturnsTheTypeOfTheArgument() { assertThat(search("type(@)", parse("1")), is(jsonString("number"))); } - @Test(expected = ArityException.class) + @Test public void typeRequiresExactlyOneArgument1() { - search("type()", parse("{}")); + try { + search("type()", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"type\" (expected 1 but was 0)")); + } } - @Test(expected = ArityException.class) + @Test public void typeRequiresExactlyOneArgument2() { - search("type(`1`, `2`)", parse("{}")); + try { + search("type(`1`, `2`)", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"type\" (expected 1 but was 2)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void typeRequiresAValue() { - search("type(&foo)", parse("{}")); + try { + search("type(&foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected any value but was expression")); + } } @Test @@ -1900,19 +2441,34 @@ public void valuesReturnsAnEmptyArrayWhenGivenAnEmptyObject() { assertThat(runtime().toList(result), is(empty())); } - @Test(expected = ArgumentTypeException.class) + @Test public void valuesRequiresAnObjectAsArgument() { - search("values(@)", parse("[3]")); + try { + search("values(@)", parse("[3]")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected object but was array")); + } } - @Test(expected = ArityException.class) + @Test public void valuesRequiresASingleArgument() { - search("values(@, @)", parse("{}")); + try { + search("values(@, @)", parse("{}")); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"values\" (expected 1 but was 2)")); + } } - @Test(expected = ArgumentTypeException.class) + @Test public void valuesRequiresAValue() { - search("values(&foo)", parse("{}")); + try { + search("values(&foo)", parse("{}")); + fail("Expected ArgumentTypeException to have been thrown"); + } catch (ArgumentTypeException ate) { + assertThat(ate.getMessage(), containsString("expected object but was expression")); + } } @Test diff --git a/jmespath-core/src/test/java/io/burt/jmespath/function/FunctionTest.java b/jmespath-core/src/test/java/io/burt/jmespath/function/FunctionTest.java index 6085f367..ebb6f495 100644 --- a/jmespath-core/src/test/java/io/burt/jmespath/function/FunctionTest.java +++ b/jmespath-core/src/test/java/io/burt/jmespath/function/FunctionTest.java @@ -117,7 +117,7 @@ public void heterogenousListOfWithTooFewArguments() { )); fail("No exception was thrown"); } catch (ArityException ae) { - assertThat(ae.getMessage(), is("Invalid arity calling \"heterogenous_list\": expected 3 but was 2")); + assertThat(ae.getMessage(), is("Invalid arity calling \"heterogenous_list\" (expected 3 but was 2)")); } } @@ -132,7 +132,7 @@ public void heterogenousListOfWithTooManyArguments() { )); fail("No exception was thrown"); } catch (ArityException ae) { - assertThat(ae.getMessage(), is("Invalid arity calling \"heterogenous_list\": expected 3 but was 4")); + assertThat(ae.getMessage(), is("Invalid arity calling \"heterogenous_list\" (expected 3 but was 4)")); } } @@ -172,7 +172,7 @@ public void typeOfRequiresExactlyOneArgument() { typeOfFunction.call(runtime, createValueArguments()); fail("No exception was thrown"); } catch (ArityException ae) { - assertThat(ae.getMessage(), containsString("Invalid arity calling \"type_of\": expected 1 but was 0")); + assertThat(ae.getMessage(), containsString("Invalid arity calling \"type_of\" (expected 1 but was 0)")); } try { typeOfFunction.call(runtime, createValueArguments( @@ -181,7 +181,7 @@ public void typeOfRequiresExactlyOneArgument() { )); fail("No exception was thrown"); } catch (ArityException ae) { - assertThat(ae.getMessage(), containsString("Invalid arity calling \"type_of\": expected 1 but was 2")); + assertThat(ae.getMessage(), containsString("Invalid arity calling \"type_of\" (expected 1 but was 2)")); } } @@ -320,7 +320,7 @@ public void arrayOfRequiresExactlyOneArgument() { arrayOfFunction.call(runtime, createValueArguments()); fail("No exception was thrown"); } catch (ArityException ae) { - assertThat(ae.getMessage(), containsString("Invalid arity calling \"array_of\": expected 1 but was 0")); + assertThat(ae.getMessage(), containsString("Invalid arity calling \"array_of\" (expected 1 but was 0)")); } try { arrayOfFunction.call(runtime, createValueArguments( @@ -329,7 +329,7 @@ public void arrayOfRequiresExactlyOneArgument() { )); fail("No exception was thrown"); } catch (ArityException ae) { - assertThat(ae.getMessage(), containsString("Invalid arity calling \"array_of\": expected 1 but was 2")); + assertThat(ae.getMessage(), containsString("Invalid arity calling \"array_of\" (expected 1 but was 2)")); } } @@ -427,7 +427,7 @@ public void listOfNeedsTheMinimumAmountOfValues() { acceptsBetweenThreeAndTenValues.call(runtime, createValueArguments(runtime.createNull())); fail("No exception was thrown"); } catch (ArityException ae) { - assertThat(ae.getMessage(), containsString("Invalid arity calling \"hello\": expected at least 3 but was 1")); + assertThat(ae.getMessage(), containsString("Invalid arity calling \"hello\" (expected at least 3 but was 1)")); } } @@ -441,7 +441,7 @@ public void listOfAcceptsOnlyTheMaximumAmountOfValues() { acceptsBetweenThreeAndTenValues.call(runtime, createValueArguments(runtime.createNull(), runtime.createNull(), runtime.createNull(), runtime.createNull())); fail("No exception was thrown"); } catch (ArityException ae) { - assertThat(ae.getMessage(), containsString("Invalid arity calling \"hello\": expected at most 3 but was 4")); + assertThat(ae.getMessage(), containsString("Invalid arity calling \"hello\" (expected at most 3 but was 4)")); } } diff --git a/jmespath-core/src/test/java/io/burt/jmespath/parser/ParserTest.java b/jmespath-core/src/test/java/io/burt/jmespath/parser/ParserTest.java index 1f33ebed..5ea640e3 100644 --- a/jmespath-core/src/test/java/io/burt/jmespath/parser/ParserTest.java +++ b/jmespath-core/src/test/java/io/burt/jmespath/parser/ParserTest.java @@ -12,10 +12,15 @@ import io.burt.jmespath.node.CreateObjectNode; import io.burt.jmespath.node.Node; import io.burt.jmespath.node.Operator; +import io.burt.jmespath.function.FunctionRegistry; +import io.burt.jmespath.function.BaseFunction; +import io.burt.jmespath.function.ArgumentConstraints; +import io.burt.jmespath.function.FunctionArgument; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.containsString; public class ParserTest { private Adapter runtime = new JcfRuntime(); @@ -1165,4 +1170,49 @@ public void parseExceptionsCanBeIterated() { assertThat(errorCount, is(2)); } } + + @Test + public void callingAFixedArityFunctionWithTooFewArgumentsThrowsParseException() { + try { + compile("max()"); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"max\" (expected 1 but was 0)")); + } + } + + @Test + public void callingAFixedArityFunctionWithTooManyArgumentsThrowsParseException() { + try { + compile("max(@, @)"); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"max\" (expected 1 but was 2)")); + } + } + + @Test + public void callingAVariableArityFunctionWithTooFewArgumentsThrowsParseException() { + try { + compile("not_null()"); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"not_null\" (expected at least 1 but was 0)")); + } + } + + @Test + public void callingAVariableArityFunctionWithTooManyArgumentsThrowsParseException() { + runtime = new JcfRuntime(FunctionRegistry.defaultRegistry().extend( + new BaseFunction("foobar", ArgumentConstraints.listOf(1, 3, ArgumentConstraints.anyValue())) { + protected T callFunction(Adapter runtime, List> arguments) { return runtime.createNull(); } + } + )); + try { + compile("foobar(a, b, c, d, e, f, g)"); + fail("Expected ParseException to have been thrown"); + } catch (ParseException pe) { + assertThat(pe.getMessage(), containsString("invalid arity calling \"foobar\" (expected at most 3 but was 7)")); + } + } }