diff --git a/README.adoc b/README.adoc index bf022e1..056acfc 100644 --- a/README.adoc +++ b/README.adoc @@ -85,6 +85,8 @@ Comparison operators are in FIQL notation and some of them has an alternative sy * Greater than or equal to : `=ge=` or `>=` * In : `=in=` * Not in : `=out=` +* Null: `=null=` +* Not Null: `=notnull=` You can also simply extend this parser with your own operators (see the <>). @@ -94,11 +96,11 @@ comp-fiql = ( ( "=", { ALPHA } ) | "!" ), "="; comp-alt = ( ">" | "<" ), [ "=" ]; ---- -Argument can be a single value, or multiple values in parenthesis separated by comma. +Argument can be no value, single value, or multiple values in parenthesis separated by comma. Value that doesn’t contain any reserved character or a white space can be unquoted, other arguments must be enclosed in single or double quotes. ---- -arguments = ( "(", value, { "," , value }, ")" ) | value; +arguments = ( "(", value, { "," , value }, ")" ) | ( value )?; value = unreserved-str | double-quoted | single-quoted; unreserved-str = unreserved, { unreserved } @@ -130,6 +132,7 @@ Examples of RSQL expressions in both FIQL-like and alternative notation: - director.lastName==Nolan and year>=2000 and year<2010 - genres=in=(sci-fi,action);genres=out=(romance,animated,horror),director==Que*Tarantino - genres=in=(sci-fi,action) and genres=out=(romance,animated,horror) or director==Que*Tarantino +- year=notnull= and director.lastName=isnull= ---- == How to use diff --git a/build.gradle.kts b/build.gradle.kts index 20344eb..d4534a4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -145,6 +145,9 @@ tasks { withType().configureEach { options.encoding = "UTF-8" + (options as? StandardJavadocDocletOptions)?.tags( + "apiNote:a:API Note:" + ) } withType().configureEach { diff --git a/src/main/java/cz/jirutka/rsql/parser/ast/Arity.java b/src/main/java/cz/jirutka/rsql/parser/ast/Arity.java new file mode 100644 index 0000000..5d6c9ee --- /dev/null +++ b/src/main/java/cz/jirutka/rsql/parser/ast/Arity.java @@ -0,0 +1,70 @@ +/* + * The MIT License + * + * Copyright 2024 Edgar Asatryan . + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package cz.jirutka.rsql.parser.ast; + +/** + * The arity of an operator. + * + * @since 2.3.0 + */ +public interface Arity { + + /** + * The minimum number of arguments operator can receive. + * + * @return The minimum number of arguments operator can receive. Positive or zero. + * @apiNote The minimum values is always less than or equal to {@linkplain #max()} + */ + int min(); + + /** + * The maximum number of arguments operator can receive. + * + * @return The maximum number of arguments operator can receive. Positive or zero. + * @apiNote The maximum values is always greater than or equal to {@linkplain #min()}. For practically unlimited + * arity the implementations should return {@link Integer#MAX_VALUE}. + */ + int max(); + + /** + * Creates arity with given {@code min} and {@code max}. + * + * @param min The minimum number of arguments. Must be zero or positive. + * @param max The maximum number of arguments. Must be zero or positive and greater than or equal to {@code min}. + * @return the created arity + */ + static Arity of(int min, int max) { + return new DynamicArity(min, max); + } + + /** + * Creates N-ary object. + * + * @param n The N. + * @return the created arity + */ + static Arity nary(int n) { + return new NAry(n); + } +} diff --git a/src/main/java/cz/jirutka/rsql/parser/ast/ComparisonNode.java b/src/main/java/cz/jirutka/rsql/parser/ast/ComparisonNode.java index 82c7b00..0a43a46 100644 --- a/src/main/java/cz/jirutka/rsql/parser/ast/ComparisonNode.java +++ b/src/main/java/cz/jirutka/rsql/parser/ast/ComparisonNode.java @@ -2,6 +2,7 @@ * The MIT License * * Copyright 2013-2014 Jakub Jirutka . + * Copyright 2024 Edgar Asatryan . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,12 +24,11 @@ */ package cz.jirutka.rsql.parser.ast; -import net.jcip.annotations.Immutable; +import static cz.jirutka.rsql.parser.ast.StringUtils.join; import java.util.ArrayList; import java.util.List; - -import static cz.jirutka.rsql.parser.ast.StringUtils.join; +import net.jcip.annotations.Immutable; /** * This node represents a comparison with operator, selector and arguments, @@ -54,9 +54,8 @@ public final class ComparisonNode extends AbstractNode { public ComparisonNode(ComparisonOperator operator, String selector, List arguments) { Assert.notNull(operator, "operator must not be null"); Assert.notBlank(selector, "selector must not be blank"); - Assert.notEmpty(arguments, "arguments list must not be empty"); - Assert.isTrue(operator.isMultiValue() || arguments.size() == 1, - "operator %s expects single argument, but multiple values given", operator); + Assert.notNull(arguments, "arguments must not be null"); + validate(operator, arguments.size()); this.operator = operator; this.selector = selector; @@ -118,9 +117,28 @@ public ComparisonNode withArguments(List newArguments) { return new ComparisonNode(operator, selector, newArguments); } + private static void validate(ComparisonOperator operator, int argc) { + Arity arity = operator.getArity(); + int min = arity.min(); + int max = arity.max(); + + if (argc < min || argc > max) { + final String message; + if (min == max) { + message = String.format("operator '%s' can have exactly %d argument(s), but got %d", + operator.getSymbol(), max, argc); + } else { + message = String.format("operator '%s' can have from %d to %d argument(s), but got %d", + operator.getSymbol(), min, max, argc); + } + + throw new IllegalArgumentException(message); + } + } + @Override public String toString() { - String args = operator.isMultiValue() + String args = operator.getArity().max() > 1 ? join(arguments, "','", "('", "')") : "'" + arguments.get(0) + "'"; return selector + operator + args; diff --git a/src/main/java/cz/jirutka/rsql/parser/ast/ComparisonOperator.java b/src/main/java/cz/jirutka/rsql/parser/ast/ComparisonOperator.java index dc317d0..bebf5b5 100644 --- a/src/main/java/cz/jirutka/rsql/parser/ast/ComparisonOperator.java +++ b/src/main/java/cz/jirutka/rsql/parser/ast/ComparisonOperator.java @@ -2,6 +2,7 @@ * The MIT License * * Copyright 2013-2014 Jakub Jirutka . + * Copyright 2024 Edgar Asatryan . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -36,8 +37,7 @@ public final class ComparisonOperator { private final String[] symbols; - private final boolean multiValue; - + private final Arity arity; /** * @param symbols Textual representation of this operator (e.g. =gt=); the first item @@ -47,13 +47,30 @@ public final class ComparisonOperator { * validated in {@link NodesFactory}. * @throws IllegalArgumentException If the {@code symbols} is either null, empty, * or contain illegal symbols. + * @see #ComparisonOperator(String[], Arity) + * @deprecated in favor of {@linkplain #ComparisonOperator(String[], Arity)} */ + @Deprecated public ComparisonOperator(String[] symbols, boolean multiValue) { + this(symbols, multiValue ? Arity.of(1, Integer.MAX_VALUE) : Arity.nary(1)); + } + + /** + * @param symbols Textual representation of this operator (e.g. =gt=); the first item is primary + * representation, any others are alternatives. Must match {@literal =[a-zA-Z]*=|[><]=?|!=}. + * @param arity Arity of this operator. + * @throws IllegalArgumentException If the {@code symbols} is either null, empty, or contain illegal + * symbols. + * @since 2.3.0 + */ + public ComparisonOperator(String[] symbols, Arity arity) { Assert.notEmpty(symbols, "symbols must not be null or empty"); + Assert.notNull(arity, "arity must not be null"); for (String sym : symbols) { Assert.isTrue(isValidOperatorSymbol(sym), "symbol must match: %s", SYMBOL_PATTERN); } - this.multiValue = multiValue; + + this.arity = arity; this.symbols = symbols.clone(); } @@ -63,22 +80,48 @@ public ComparisonOperator(String[] symbols, boolean multiValue) { * @param multiValue Whether this operator may be used with multiple arguments. This is then * validated in {@link NodesFactory}. * @see #ComparisonOperator(String[], boolean) + * @deprecated in favor of {@linkplain #ComparisonOperator(String, Arity)} */ + @Deprecated public ComparisonOperator(String symbol, boolean multiValue) { this(new String[]{symbol}, multiValue); } + /** + * @param symbol Textual representation of this operator (e.g. =gt=); Must match + * {@literal =[a-zA-Z]*=|[><]=?|!=}. + * @param arity Arity of this operator. + * @see #ComparisonOperator(String[], boolean) + * @since 2.3.0 + */ + public ComparisonOperator(String symbol, Arity arity) { + this(new String[]{symbol}, arity); + } + /** * @param symbol Textual representation of this operator (e.g. =gt=); Must match * {@literal =[a-zA-Z]*=|[><]=?|!=}. * @param altSymbol Alternative representation for {@code symbol}. * @param multiValue Whether this operator may be used with multiple arguments. This is then * @see #ComparisonOperator(String[], boolean) + * @deprecated in favor of {@linkplain #ComparisonOperator(String, String, Arity)} */ public ComparisonOperator(String symbol, String altSymbol, boolean multiValue) { this(new String[]{symbol, altSymbol}, multiValue); } + /** + * @param symbol Textual representation of this operator (e.g. =gt=); Must match + * {@literal =[a-zA-Z]*=|[><]=?|!=}. + * @param altSymbol Alternative representation for {@code symbol}. + * @param arity Arity of this operator. + * @see #ComparisonOperator(String[], boolean) + * @since 2.3.0 + */ + public ComparisonOperator(String symbol, String altSymbol, Arity arity) { + this(new String[]{symbol, altSymbol}, arity); + } + /** * @param symbols Textual representation of this operator (e.g. =gt=); the first item * is primary representation, any others are alternatives. Must match {@literal =[a-zA-Z]*=|[><]=?|!=}. @@ -112,11 +155,22 @@ public String[] getSymbols() { * Whether this operator may be used with multiple arguments. * * @return Whether this operator may be used with multiple arguments. + * @deprecated use {@linkplain #getArity()} */ + @Deprecated public boolean isMultiValue() { - return multiValue; + return arity.max() > 1; } + /** + * Returns the arity of this operator. + * + * @return the arity of this operator. + * @since 2.3.0 + */ + public Arity getArity() { + return arity; + } /** * Whether the given string can represent an operator. diff --git a/src/main/java/cz/jirutka/rsql/parser/ast/DynamicArity.java b/src/main/java/cz/jirutka/rsql/parser/ast/DynamicArity.java new file mode 100644 index 0000000..4530712 --- /dev/null +++ b/src/main/java/cz/jirutka/rsql/parser/ast/DynamicArity.java @@ -0,0 +1,55 @@ +/* + * The MIT License + * + * Copyright 2024 Edgar Asatryan . + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package cz.jirutka.rsql.parser.ast; + +final class DynamicArity implements Arity { + + private final int min; + private final int max; + + DynamicArity(int min, int max) { + if (min < 0) { + throw new IllegalArgumentException("min must be positive or zero"); + } + if (max < 0) { + throw new IllegalArgumentException("max must be positive or zero"); + } + if (min > max) { + throw new IllegalArgumentException("min must be less than or equal to max"); + } + + this.min = min; + this.max = max; + } + + @Override + public int min() { + return min; + } + + @Override + public int max() { + return max; + } +} diff --git a/src/main/java/cz/jirutka/rsql/parser/ast/NAry.java b/src/main/java/cz/jirutka/rsql/parser/ast/NAry.java new file mode 100644 index 0000000..09e5317 --- /dev/null +++ b/src/main/java/cz/jirutka/rsql/parser/ast/NAry.java @@ -0,0 +1,47 @@ +/* + * The MIT License + * + * Copyright 2024 Edgar Asatryan . + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package cz.jirutka.rsql.parser.ast; + +final class NAry implements Arity { + + private final int n; + + NAry(int n) { + if (n < 0) { + throw new IllegalArgumentException("n must be positive or zero"); + } + + this.n = n; + } + + @Override + public int min() { + return n; + } + + @Override + public int max() { + return n; + } +} diff --git a/src/main/java/cz/jirutka/rsql/parser/ast/RSQLOperators.java b/src/main/java/cz/jirutka/rsql/parser/ast/RSQLOperators.java index f129ca3..3110b15 100644 --- a/src/main/java/cz/jirutka/rsql/parser/ast/RSQLOperators.java +++ b/src/main/java/cz/jirutka/rsql/parser/ast/RSQLOperators.java @@ -2,6 +2,7 @@ * The MIT License * * Copyright 2013-2014 Jakub Jirutka . + * Copyright 2024 Edgar Asatryan . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,18 +32,20 @@ public abstract class RSQLOperators { public static final ComparisonOperator - EQUAL = new ComparisonOperator("=="), - NOT_EQUAL = new ComparisonOperator("!="), - GREATER_THAN = new ComparisonOperator("=gt=", ">"), - GREATER_THAN_OR_EQUAL = new ComparisonOperator("=ge=", ">="), - LESS_THAN = new ComparisonOperator("=lt=", "<"), - LESS_THAN_OR_EQUAL = new ComparisonOperator("=le=", "<="), - IN = new ComparisonOperator("=in=", true), - NOT_IN = new ComparisonOperator("=out=", true); + EQUAL = new ComparisonOperator("==", Arity.nary(1)), + NOT_EQUAL = new ComparisonOperator("!=", Arity.nary(1)), + GREATER_THAN = new ComparisonOperator("=gt=", ">", Arity.nary(1)), + GREATER_THAN_OR_EQUAL = new ComparisonOperator("=ge=", ">=", Arity.nary(1)), + LESS_THAN = new ComparisonOperator("=lt=", "<", Arity.nary(1)), + LESS_THAN_OR_EQUAL = new ComparisonOperator("=le=", "<=", Arity.nary(1)), + IN = new ComparisonOperator("=in=", Arity.of(1, Integer.MAX_VALUE)), + NOT_IN = new ComparisonOperator("=out=", Arity.of(1, Integer.MAX_VALUE)), + IS_NULL = new ComparisonOperator("=null=", Arity.nary(0)), + NOT_NULL = new ComparisonOperator("=notnull=", Arity.nary(0)); public static Set defaultOperators() { return new HashSet<>(asList(EQUAL, NOT_EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL, - LESS_THAN, LESS_THAN_OR_EQUAL, IN, NOT_IN)); + LESS_THAN, LESS_THAN_OR_EQUAL, IN, NOT_IN, IS_NULL, NOT_NULL)); } } diff --git a/src/main/javacc/RSQLParser.jj b/src/main/javacc/RSQLParser.jj index e304945..22fdbcb 100644 --- a/src/main/javacc/RSQLParser.jj +++ b/src/main/javacc/RSQLParser.jj @@ -2,6 +2,7 @@ * The MIT License * * Copyright 2013-2016 Jakub Jirutka . + * Copyright 2024 Edgar Asatryan . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -43,6 +44,7 @@ import java.io.InputStream; import java.io.Reader; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; final class Parser { @@ -206,7 +208,10 @@ List Arguments(): { ( value = CommaSepArguments() ) { return (List) value; } | - value = Argument() { return Arrays.asList((String) value); } + (value = Argument() { return Arrays.asList((String) value); })? + { + return Collections.emptyList(); + } } List CommaSepArguments(): diff --git a/src/test/groovy/cz/jirutka/rsql/parser/RSQLParserTest.groovy b/src/test/groovy/cz/jirutka/rsql/parser/RSQLParserTest.groovy index c9a3568..1605f9a 100644 --- a/src/test/groovy/cz/jirutka/rsql/parser/RSQLParserTest.groovy +++ b/src/test/groovy/cz/jirutka/rsql/parser/RSQLParserTest.groovy @@ -2,6 +2,7 @@ * The MIT License * * Copyright 2013-2014 Jakub Jirutka . + * Copyright 2023-2024 Edgar Asatryan . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -68,7 +69,17 @@ class RSQLParserTest extends Specification { expect: parse("sel${op}val") == expected where: - op << defaultOperators()*.symbols.flatten() + op << defaultOperators().findAll { it != IS_NULL && it != NOT_NULL }.symbols.flatten() + } + + def 'should parse operators without arguments'() { + expect: + parse(input) == expected + + where: + input | expected + 's0=null=,s1=notnull=' | or(isNull('s0'), notNull('s1')) + 's0=null= or s1=notnull=' | or(isNull('s0'), notNull('s1')) } def 'throw exception for deprecated short equal operator: ='() { @@ -242,6 +253,22 @@ class RSQLParserTest extends Specification { ex.cause instanceof UnknownOperatorException } + def 'use parser with custom set of operators 2'() { + setup: + def allOperator = new ComparisonOperator('=all=', Arity.of(1, Integer.MAX_VALUE)) + def parser = new RSQLParser([EQUAL, allOperator] as Set) + def expected = and(eq('name', 'TRON'), new ComparisonNode(allOperator, 'genres', ['sci-fi', 'thriller'])) + + expect: + parser.parse('name==TRON;genres=all=(sci-fi,thriller)') == expected + + when: 'unsupported operator used' + parser.parse('name==TRON;year=ge=2010') + then: + def ex = thrown(RSQLParserException) + ex.cause instanceof UnknownOperatorException + } + //////// Helpers //////// @@ -251,4 +278,6 @@ class RSQLParserTest extends Specification { def or(Node... nodes) { new OrNode(nodes as List) } def eq(sel, arg) { new ComparisonNode(EQUAL, sel, [arg as String]) } def out(sel, ...args) { new ComparisonNode(NOT_IN, sel, args as List) } + def isNull(sel) { new ComparisonNode(IS_NULL, sel, []) } + def notNull(sel) { new ComparisonNode(NOT_NULL, sel, []) } } diff --git a/src/test/groovy/cz/jirutka/rsql/parser/ast/ComparisonNodeTest.groovy b/src/test/groovy/cz/jirutka/rsql/parser/ast/ComparisonNodeTest.groovy index 7e09169..7ea5418 100644 --- a/src/test/groovy/cz/jirutka/rsql/parser/ast/ComparisonNodeTest.groovy +++ b/src/test/groovy/cz/jirutka/rsql/parser/ast/ComparisonNodeTest.groovy @@ -2,6 +2,7 @@ * The MIT License * * Copyright 2013-2014 Jakub Jirutka . + * Copyright 2023-2024 Edgar Asatryan . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -72,6 +73,23 @@ class ComparisonNodeTest extends Specification { new ComparisonNode(EQUAL, 'genres', ['thriller']) | "genres=='thriller'" } + def 'should throw exception on arity mismatch'() { + when: + new ComparisonNode(operator, 's', args) + + then: + def e = thrown(IllegalArgumentException) + e.message == expected + + where: + operator | args | expected + new ComparisonOperator('=a=', Arity.nary(0)) | ['a'] | "operator '=a=' can have exactly 0 argument(s), but got 1" + new ComparisonOperator('=b=', Arity.nary(1)) | ['a', 'b'] | "operator '=b=' can have exactly 1 argument(s), but got 2" + new ComparisonOperator('=c=', Arity.nary(5)) | [] | "operator '=c=' can have exactly 5 argument(s), but got 0" + new ComparisonOperator('=d=', Arity.of(1, 5)) | [] | "operator '=d=' can have from 1 to 5 argument(s), but got 0" + new ComparisonOperator('=e=', Arity.of(2, 6)) | ['a'] | "operator '=e=' can have from 2 to 6 argument(s), but got 1" + } + def 'should honor equal and hashcode contracts'() { expect: EqualsVerifier.forClass(ComparisonNode) diff --git a/src/test/groovy/cz/jirutka/rsql/parser/ast/ComparisonOperatorTest.groovy b/src/test/groovy/cz/jirutka/rsql/parser/ast/ComparisonOperatorTest.groovy index 7ea9de0..6bc7d35 100644 --- a/src/test/groovy/cz/jirutka/rsql/parser/ast/ComparisonOperatorTest.groovy +++ b/src/test/groovy/cz/jirutka/rsql/parser/ast/ComparisonOperatorTest.groovy @@ -2,6 +2,7 @@ * The MIT License * * Copyright 2013-2014 Jakub Jirutka . + * Copyright 2023-2024 Edgar Asatryan . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -48,6 +49,11 @@ class ComparisonOperatorTest extends Specification { new ComparisonOperator('=out=', '=notin=') == new ComparisonOperator('=out=', '=notin=', true) } + def 'equals when contains same symbols 2'() { + expect: + new ComparisonOperator('=out=', '=notin=') == new ComparisonOperator('=out=', '=notin=', Arity.of(1, Integer.MAX_VALUE)) + } + def 'should create with varargs'() { given: def operator = new ComparisonOperator("=a=", "=b=", "=c=", "=d=") diff --git a/src/test/groovy/cz/jirutka/rsql/parser/ast/DynamicAritySpec.groovy b/src/test/groovy/cz/jirutka/rsql/parser/ast/DynamicAritySpec.groovy new file mode 100644 index 0000000..151f3fa --- /dev/null +++ b/src/test/groovy/cz/jirutka/rsql/parser/ast/DynamicAritySpec.groovy @@ -0,0 +1,33 @@ +package cz.jirutka.rsql.parser.ast + +import spock.lang.Specification + +class DynamicAritySpec extends Specification { + def 'Should throw exception when arguments are not valid'() { + when: + new DynamicArity(min, max) + + then: + def e = thrown(IllegalArgumentException) + e.message == expected + + where: + min | max | expected + -1 | 0 | 'min must be positive or zero' + 1 | -1 | 'max must be positive or zero' + 1 | 0 | 'min must be less than or equal to max' + } + + def 'Should create instance'() { + given: + def min = 1 + def max = Integer.MAX_VALUE + + when: + def actual = new DynamicArity(min, Integer.MAX_VALUE) + + then: + actual.min() == min + actual.max() == max + } +} diff --git a/src/test/groovy/cz/jirutka/rsql/parser/ast/NArySpec.groovy b/src/test/groovy/cz/jirutka/rsql/parser/ast/NArySpec.groovy new file mode 100644 index 0000000..c35e19f --- /dev/null +++ b/src/test/groovy/cz/jirutka/rsql/parser/ast/NArySpec.groovy @@ -0,0 +1,25 @@ +package cz.jirutka.rsql.parser.ast + +import spock.lang.Specification + +class NArySpec extends Specification { + def 'Should throw exception when arguments are not valid'() { + when: + new NAry(-1) + + then: + def e = thrown(IllegalArgumentException) + e.message == 'n must be positive or zero' + } + + def 'Should create instance'() { + given: + def n = 2 + when: + def actual = new NAry(n) + + then: + actual.min() == n + actual.max() == n + } +} diff --git a/src/test/groovy/cz/jirutka/rsql/parser/ast/RSQLOperatorsSpec.groovy b/src/test/groovy/cz/jirutka/rsql/parser/ast/RSQLOperatorsSpec.groovy new file mode 100644 index 0000000..3ad90f8 --- /dev/null +++ b/src/test/groovy/cz/jirutka/rsql/parser/ast/RSQLOperatorsSpec.groovy @@ -0,0 +1,27 @@ +package cz.jirutka.rsql.parser.ast + +import spock.lang.Specification + +import static cz.jirutka.rsql.parser.ast.RSQLOperators.* + +class RSQLOperatorsSpec extends Specification { + def 'Default operators spec'() { + expect: + op.symbols == symbols + op.arity.min() == min + op.arity.max() == max + + where: + op | symbols | min | max + EQUAL | new String[]{'=='} | 1 | 1 + NOT_EQUAL | new String[]{'!='} | 1 | 1 + GREATER_THAN | new String[]{'=gt=', '>'} | 1 | 1 + GREATER_THAN_OR_EQUAL | new String[]{'=ge=', '>='} | 1 | 1 + LESS_THAN | new String[]{'=lt=', '<'} | 1 | 1 + LESS_THAN_OR_EQUAL | new String[]{'=le=', '<='} | 1 | 1 + IN | new String[]{'=in='} | 1 | Integer.MAX_VALUE + NOT_IN | new String[]{'=out='} | 1 | Integer.MAX_VALUE + IS_NULL | new String[]{'=null='} | 0 | 0 + NOT_NULL | new String[]{'=notnull='} | 0 | 0 + } +}