From 5d3f953c13fea1d3b9dae749dcd83a2ac9e502dd Mon Sep 17 00:00:00 2001
From: Akshit Bansal Only fields annotated with {@link Sortable} on this entity
- * are permitted in the {@code sort} request parameter.
- * Allows mapping query parameter field names (aliases) to actual entity field names.
- *
+ * This annotation is intended to be placed on handler methods so query-related
+ * configuration can be defined once and reused by both filtering and sorting
+ * argument resolvers.
+ *
* If a requested sort field is not annotated as {@link Sortable}, a
* {@link QueryValidationException} is thrown.
@@ -79,16 +81,18 @@ public boolean supportsParameter(MethodParameter parameter) {
* The process is as follows:
* Example controller usage:
* The RSQL query is read from the request parameter defined by
- * {@link RsqlSpec#paramName()}. The query is then parsed, validated,
- * and converted into a JPA {@link Specification}.
+ * {@link RsqlSpec#paramName()}. Entity metadata and alias mappings are read
+ * from {@link WebQuery} on the same controller method. The query is then
+ * parsed, validated, and converted into a JPA {@link Specification}.
*
* @param parameter the method parameter to resolve
* @param mavContainer the model and view container
diff --git a/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/ValidationRSQLVisitor.java b/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/ValidationRSQLVisitor.java
index 09f5b7b..420c7d4 100644
--- a/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/ValidationRSQLVisitor.java
+++ b/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/ValidationRSQLVisitor.java
@@ -36,7 +36,7 @@
* Usage example: This visitor is typically used in combination with
diff --git a/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/annotation/FieldMapping.java b/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/annotation/FieldMapping.java
index abd79f9..3192b31 100644
--- a/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/annotation/FieldMapping.java
+++ b/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/annotation/FieldMapping.java
@@ -5,25 +5,27 @@
import java.lang.annotation.Target;
/**
- * Defines a mapping between a query parameter field name and an actual entity field name.
+ * Defines a mapping between an API-facing field name and an actual entity field name.
*
- * This annotation is used within {@link RsqlSpec#fieldMappings()} to create aliases
- * for entity fields in RSQL queries. It allows clients to use simpler or more
- * intuitive names in query strings while mapping them to the actual field names on
- * the entity.
+ * This annotation is used within {@link WebQuery#fieldMappings()} to create aliases
+ * for entity fields in filtering and sorting requests. It allows clients to use
+ * simpler or more intuitive names while mapping them to the actual field names
+ * on the entity.
* Example usage:
*
*
* @param methodParameter the method parameter for which the value should be resolved
* @param mavContainer the ModelAndViewContainer (can be {@code null})
* @param webRequest the current request
* @param binderFactory a factory for creating WebDataBinder instances (can be {@code null})
- * @return a {@link Pageable} object containing page, size, and validated sort information
+ * @return a {@link Pageable} object containing page, size, validated sort information,
+ * and mapped sort field paths
* @throws QueryValidationException if any requested sort field is not marked as {@link Sortable}
*/
@Override
diff --git a/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/RsqlSpecificationArgumentResolver.java b/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/RsqlSpecificationArgumentResolver.java
index 9a9dee6..76d5e76 100644
--- a/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/RsqlSpecificationArgumentResolver.java
+++ b/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/RsqlSpecificationArgumentResolver.java
@@ -38,6 +38,7 @@
* This resolver enables transparent usage of RSQL queries in controller methods.
* When a request contains an RSQL query parameter, the resolver:
*
+ *
{@code
* @GetMapping("/users")
+ * @WebQuery(entityClass = User.class)
* public List{@code
* Node root = new RSQLParser().parse("status==ACTIVE;age>30");
- * new ValidationRSQLVisitor(User.class).visit(root);
+ * new ValidationRSQLVisitor(User.class, new FieldMapping[0], Set.of()).visit(root);
* }
*
* {@code
* @GetMapping("/users")
+ * @WebQuery(
+ * entityClass = User.class,
+ * fieldMappings = {
+ * @FieldMapping(name = "id", field = "userId"),
+ * @FieldMapping(name = "fullName", field = "profile.name")
+ * }
+ * )
* public List
* This is the name that clients will use when constructing their queries. *
@@ -64,8 +66,8 @@ * Whether to allow the use of the original field name in addition to the alias. ** When {@code false} (default), only the alias name defined in {@link #name()} - * can be used in queries. When {@code true}, both the alias and the original - * field name are allowed. + * can be used in filter and sort expressions. When {@code true}, both the alias + * and the original field name are allowed. *
* * @return {@code true} if the original field name should remain usable, diff --git a/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/annotation/RestrictedPageable.java b/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/annotation/RestrictedPageable.java index ceddd97..9172ae4 100644 --- a/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/annotation/RestrictedPageable.java +++ b/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/annotation/RestrictedPageable.java @@ -7,7 +7,9 @@ * as subject to field-level sorting restrictions. * *When applied, the pageable argument is validated so that sorting is only - * allowed on entity fields explicitly annotated with {@link Sortable}.
+ * allowed on entity fields explicitly annotated with {@link Sortable}. Entity + * metadata and alias mappings are resolved from {@link WebQuery} on the same + * controller method. * *This annotation does not affect pagination parameters * such as page number or page size. It only governs which fields may be used @@ -21,8 +23,9 @@ * *
{@code
* @GetMapping
+ * @WebQuery(entityClass = User.class)
* public Page search(
- * @RestrictedPageable(entityClass = User.class) Pageable pageable
+ * @RestrictedPageable Pageable pageable
* ) {
* ...
* }
diff --git a/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/annotation/RsqlSpec.java b/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/annotation/RsqlSpec.java
index 70ec806..7be07b5 100644
--- a/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/annotation/RsqlSpec.java
+++ b/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/annotation/RsqlSpec.java
@@ -9,14 +9,16 @@
* When applied to a method parameter, the annotated parameter will receive a Specification
* built from the RSQL query provided in the HTTP request. The RSQL query is parsed,
* validated against the target entity's {@link RsqlFilterable} annotations, and converted
- * into a Spring Data JPA {@link org.springframework.data.jpa.domain.Specification}.
+ * into a Spring Data JPA {@link org.springframework.data.jpa.domain.Specification}. Entity
+ * metadata and alias mappings are sourced from {@link WebQuery} on the same controller method.
*
*
* Example usage in a controller:
* {@code
* @GetMapping("/users")
+ * @WebQuery(entityClass = User.class)
* public List search(
- * @RsqlSpec(entityClass = User.class, paramName = "filter") Specification spec
+ * @RsqlSpec(paramName = "filter") Specification spec
* ) {
* return userRepository.findAll(spec);
* }
diff --git a/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/util/AnnotationUtil.java b/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/util/AnnotationUtil.java
index e30853a..af20f93 100644
--- a/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/util/AnnotationUtil.java
+++ b/spring-web-query-core/src/main/java/in/co/akshitbansal/springwebquery/util/AnnotationUtil.java
@@ -8,8 +8,19 @@
import java.lang.reflect.Method;
import java.text.MessageFormat;
+/**
+ * Utility methods for resolving query-related annotations from controller metadata.
+ */
public class AnnotationUtil {
+ /**
+ * Resolves {@link WebQuery} from the controller method that declares the
+ * provided Spring MVC method parameter.
+ *
+ * @param parameter controller method parameter currently being resolved
+ * @return resolved {@link WebQuery} annotation
+ * @throws QueryConfigurationException if the method cannot be resolved or is not annotated with {@link WebQuery}
+ */
public static WebQuery resolveWebQueryFromParameter(@NonNull MethodParameter parameter) {
// Retrieve the controller method
Method controllerMethod = parameter.getMethod();
@@ -22,7 +33,7 @@ public static WebQuery resolveWebQueryFromParameter(@NonNull MethodParameter par
// Ensure that the method is annotated with @WebQuery to access query configuration
if(webQueryAnnotation == null)
throw new QueryConfigurationException(MessageFormat.format(
- "Controller method {0} must be annotated with @WebQuery to use @RsqlSpec parameters",
+ "Controller method {0} must be annotated with @WebQuery to use query argument resolvers",
controllerMethod
));
return webQueryAnnotation;
From 551411dc7413b3a82c6012cabbe64f6ede4b57e3 Mon Sep 17 00:00:00 2001
From: Akshit Bansal
Date: Sat, 21 Feb 2026 17:12:56 +0530
Subject: [PATCH 3/5] Updated README with new behaviour details
---
README.md | 23 +++++++++++++++++------
1 file changed, 17 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index 5f52111..52d4c8c 100644
--- a/README.md
+++ b/README.md
@@ -107,14 +107,17 @@ public class Profile {
```java
@GetMapping("/users")
+@WebQuery(entityClass = User.class)
public Page search(
- @RsqlSpec(entityClass = User.class) Specification spec,
- @RestrictedPageable(entityClass = User.class) Pageable pageable
+ @RsqlSpec Specification spec,
+ @RestrictedPageable Pageable pageable
) {
return userRepository.findAll(spec, pageable);
}
```
+`@WebQuery` is required on the controller method when using `@RsqlSpec` and/or `@RestrictedPageable`.
+
### 3. Example Queries
| Feature | Query |
@@ -132,19 +135,26 @@ public Page search(
### Field Mapping (Aliases)
```java
-@RsqlSpec(
+@WebQuery(
entityClass = User.class,
fieldMappings = {
@FieldMapping(name = "joined", field = "createdAt", allowOriginalFieldName = false)
}
-) Specification spec
+)
+public Page search(
+ @RsqlSpec Specification spec,
+ @RestrictedPageable Pageable pageable
+) {
+ return userRepository.findAll(spec, pageable);
+}
```
- `name`: The alias to be used in the query.
- `field`: The actual entity field path.
-- `allowOriginalFieldName`: If `true`, both the alias and original field name can be used. If `false` (default), only the alias is allowed.
+- `allowOriginalFieldName`: If `true`, both the alias and original field name can be used. If `false` (default), only the alias is allowed for both filtering and sorting.
Query: `/users?filter=joined=gt=2024-01-01T00:00:00Z`
+Sort query: `/users?sort=joined,desc`
---
@@ -234,6 +244,7 @@ The library provides a hierarchy of exceptions to distinguish between client-sid
- Using an original field name when a mapping alias is required (`allowOriginalFieldName = false`).
- Malformed RSQL syntax.
- **`QueryConfigurationException`**: Thrown when the library or entity mapping is misconfigured by the developer. These should typically be treated as a `500 Internal Server Error`.
+ - Missing `@WebQuery` on a controller method that uses `@RsqlSpec` or `@RestrictedPageable`.
- Custom operators referenced in `@RsqlFilterable` that are not registered.
- Field mappings pointing to non-existent fields on the entity.
@@ -266,7 +277,7 @@ public class GlobalExceptionHandler {
## How It Works
1. Parsing: The RSQL string is parsed into an AST.
-2. Validation: A custom `RSQLVisitor` traverses the AST and checks every node against the `@RsqlFilterable` configuration on the target entity.
+2. Validation: A custom `RSQLVisitor` traverses the AST and checks every node against the `@RsqlFilterable` configuration on the target entity defined by `@WebQuery`.
3. Reflection: `ReflectionUtil` resolves dot-notation paths, handling JPA associations and collection types.
4. Specification: Once validated, it is converted into a `Specification` compatible with Spring Data JPA `findAll(Specification, Pageable)`.
From 1f105a011be118726226cb133336e32d40587fd4 Mon Sep 17 00:00:00 2001
From: Akshit Bansal
Date: Sat, 21 Feb 2026 17:17:25 +0530
Subject: [PATCH 4/5] Fixed core tests
---
...strictedPageableArgumentResolverTest.java} | 64 ++++++++++++++++++-
...sqlSpecificationArgumentResolverTest.java} | 51 +++++++++++++--
.../ValidationRSQLVisitorTest.java | 61 ++++++++++++++++++
.../util/AnnotationUtilTest.java | 48 ++++++++++++++
4 files changed, 216 insertions(+), 8 deletions(-)
rename spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/{RestrictedPageableArgumentResolverTest.java.in => RestrictedPageableArgumentResolverTest.java} (51%)
rename spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/{RsqlSpecificationArgumentResolverTest.java.in => RsqlSpecificationArgumentResolverTest.java} (76%)
create mode 100644 spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/util/AnnotationUtilTest.java
diff --git a/spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/RestrictedPageableArgumentResolverTest.java.in b/spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/RestrictedPageableArgumentResolverTest.java
similarity index 51%
rename from spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/RestrictedPageableArgumentResolverTest.java.in
rename to spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/RestrictedPageableArgumentResolverTest.java
index eee85f0..81f7661 100644
--- a/spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/RestrictedPageableArgumentResolverTest.java.in
+++ b/spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/RestrictedPageableArgumentResolverTest.java
@@ -1,7 +1,10 @@
package in.co.akshitbansal.springwebquery;
+import in.co.akshitbansal.springwebquery.annotation.FieldMapping;
import in.co.akshitbansal.springwebquery.annotation.RestrictedPageable;
import in.co.akshitbansal.springwebquery.annotation.Sortable;
+import in.co.akshitbansal.springwebquery.annotation.WebQuery;
+import in.co.akshitbansal.springwebquery.exception.QueryConfigurationException;
import in.co.akshitbansal.springwebquery.exception.QueryValidationException;
import org.junit.jupiter.api.Test;
import org.springframework.core.MethodParameter;
@@ -46,6 +49,16 @@ void resolveArgument_allowsSortableField() throws NoSuchMethodException {
assertEquals("name", pageable.getSort().iterator().next().getProperty());
}
+ @Test
+ void resolveArgument_rewritesMappedAliasField() throws NoSuchMethodException {
+ Method method = TestController.class.getDeclaredMethod("searchWithMapping", Pageable.class);
+ MethodParameter parameter = new MethodParameter(method, 0);
+ NativeWebRequest request = requestWithSort("displayName,asc");
+
+ Pageable pageable = resolver.resolveArgument(parameter, null, request, null);
+ assertEquals("name", pageable.getSort().iterator().next().getProperty());
+ }
+
@Test
void resolveArgument_rejectsNonSortableField() throws NoSuchMethodException {
Method method = TestController.class.getDeclaredMethod("search", Pageable.class);
@@ -55,6 +68,34 @@ void resolveArgument_rejectsNonSortableField() throws NoSuchMethodException {
assertThrows(QueryValidationException.class, () -> resolver.resolveArgument(parameter, null, request, null));
}
+ @Test
+ void resolveArgument_rejectsOriginalMappedFieldWhenNotAllowed() throws NoSuchMethodException {
+ Method method = TestController.class.getDeclaredMethod("searchWithMapping", Pageable.class);
+ MethodParameter parameter = new MethodParameter(method, 0);
+ NativeWebRequest request = requestWithSort("name,asc");
+
+ assertThrows(QueryValidationException.class, () -> resolver.resolveArgument(parameter, null, request, null));
+ }
+
+ @Test
+ void resolveArgument_allowsOriginalMappedFieldWhenAllowed() throws NoSuchMethodException {
+ Method method = TestController.class.getDeclaredMethod("searchWithMappingAllowOriginal", Pageable.class);
+ MethodParameter parameter = new MethodParameter(method, 0);
+ NativeWebRequest request = requestWithSort("name,asc");
+
+ Pageable pageable = resolver.resolveArgument(parameter, null, request, null);
+ assertEquals("name", pageable.getSort().iterator().next().getProperty());
+ }
+
+ @Test
+ void resolveArgument_rejectsWhenWebQueryMissing() throws NoSuchMethodException {
+ Method method = TestController.class.getDeclaredMethod("searchWithoutWebQuery", Pageable.class);
+ MethodParameter parameter = new MethodParameter(method, 0);
+ NativeWebRequest request = requestWithSort("name,asc");
+
+ assertThrows(QueryConfigurationException.class, () -> resolver.resolveArgument(parameter, null, request, null));
+ }
+
private static NativeWebRequest requestWithSort(String sort) {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("sort", sort);
@@ -63,16 +104,37 @@ private static NativeWebRequest requestWithSort(String sort) {
@SuppressWarnings("unused")
private static class TestController {
- void search(@RestrictedPageable(entityClass = SortEntity.class) Pageable pageable) {
+
+ @WebQuery(entityClass = SortEntity.class)
+ void search(@RestrictedPageable Pageable pageable) {
}
void searchWithoutAnnotation(Pageable pageable) {
}
+
+ @WebQuery(
+ entityClass = SortEntity.class,
+ fieldMappings = {@FieldMapping(name = "displayName", field = "name")}
+ )
+ void searchWithMapping(@RestrictedPageable Pageable pageable) {
+ }
+
+ @WebQuery(
+ entityClass = SortEntity.class,
+ fieldMappings = {@FieldMapping(name = "displayName", field = "name", allowOriginalFieldName = true)}
+ )
+ void searchWithMappingAllowOriginal(@RestrictedPageable Pageable pageable) {
+ }
+
+ void searchWithoutWebQuery(@RestrictedPageable Pageable pageable) {
+ }
}
private static class SortEntity {
+
@Sortable
private String name;
+
private String secret;
}
}
diff --git a/spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/RsqlSpecificationArgumentResolverTest.java.in b/spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/RsqlSpecificationArgumentResolverTest.java
similarity index 76%
rename from spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/RsqlSpecificationArgumentResolverTest.java.in
rename to spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/RsqlSpecificationArgumentResolverTest.java
index 974722d..9d5d710 100644
--- a/spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/RsqlSpecificationArgumentResolverTest.java.in
+++ b/spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/RsqlSpecificationArgumentResolverTest.java
@@ -4,6 +4,8 @@
import in.co.akshitbansal.springwebquery.annotation.FieldMapping;
import in.co.akshitbansal.springwebquery.annotation.RsqlFilterable;
import in.co.akshitbansal.springwebquery.annotation.RsqlSpec;
+import in.co.akshitbansal.springwebquery.annotation.WebQuery;
+import in.co.akshitbansal.springwebquery.exception.QueryConfigurationException;
import in.co.akshitbansal.springwebquery.exception.QueryValidationException;
import in.co.akshitbansal.springwebquery.operator.RsqlCustomOperator;
import in.co.akshitbansal.springwebquery.operator.RsqlOperator;
@@ -50,7 +52,8 @@ void resolveArgument_returnsUnrestrictedWhenFilterMissing() throws NoSuchMethodE
Method method = TestController.class.getDeclaredMethod("search", Specification.class);
MethodParameter parameter = new MethodParameter(method, 0);
- resolver.resolveArgument(parameter, null, emptyRequest(), null);
+ Specification> spec = resolver.resolveArgument(parameter, null, emptyRequest(), null);
+ assertNotNull(spec);
}
@Test
@@ -102,6 +105,25 @@ void resolveArgument_allowsCustomOperator() throws NoSuchMethodException {
resolverWithCustom.resolveArgument(parameter, null, webRequest, null);
}
+ @Test
+ void resolveArgument_usesCustomParameterName() throws NoSuchMethodException {
+ Method method = TestController.class.getDeclaredMethod("searchWithCustomParam", Specification.class);
+ MethodParameter parameter = new MethodParameter(method, 0);
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setParameter("q", "name==john");
+
+ resolver.resolveArgument(parameter, null, new ServletWebRequest(request), null);
+ }
+
+ @Test
+ void resolveArgument_rejectsWhenWebQueryMissing() throws NoSuchMethodException {
+ Method method = TestController.class.getDeclaredMethod("searchWithoutWebQuery", Specification.class);
+ MethodParameter parameter = new MethodParameter(method, 0);
+ NativeWebRequest webRequest = requestWithFilter("name==john");
+
+ assertThrows(QueryConfigurationException.class, () -> resolver.resolveArgument(parameter, null, webRequest, null));
+ }
+
private static NativeWebRequest emptyRequest() {
return new ServletWebRequest(new MockHttpServletRequest());
}
@@ -132,38 +154,53 @@ public Predicate toPredicate(RSQLCustomPredicateInput input) {
@SuppressWarnings("unused")
private static class TestController {
- void search(@RsqlSpec(entityClass = TestEntity.class) Specification specification) {
+
+ @WebQuery(entityClass = TestEntity.class)
+ void search(@RsqlSpec Specification specification) {
}
void searchWithoutAnnotation(Specification specification) {
}
- void searchWithMapping(@RsqlSpec(
+ @WebQuery(
entityClass = TestEntity.class,
fieldMappings = {
@FieldMapping(name = "displayName", field = "name")
}
- ) Specification specification) {
+ )
+ void searchWithMapping(@RsqlSpec Specification specification) {
}
- void searchWithMappingAllowOriginal(@RsqlSpec(
+ @WebQuery(
entityClass = TestEntity.class,
fieldMappings = {
@FieldMapping(name = "displayName", field = "name", allowOriginalFieldName = true)
}
- ) Specification specification) {
+ )
+ void searchWithMappingAllowOriginal(@RsqlSpec Specification specification) {
+ }
+
+ @WebQuery(entityClass = TestEntityWithCustom.class)
+ void searchWithCustom(@RsqlSpec Specification specification) {
}
- void searchWithCustom(@RsqlSpec(entityClass = TestEntityWithCustom.class) Specification specification) {
+ @WebQuery(entityClass = TestEntity.class)
+ void searchWithCustomParam(@RsqlSpec(paramName = "q") Specification specification) {
+ }
+
+ void searchWithoutWebQuery(@RsqlSpec Specification specification) {
}
}
private static class TestEntity {
+
@RsqlFilterable(operators = {RsqlOperator.EQUAL})
+
private String name;
}
private static class TestEntityWithCustom {
+
@RsqlFilterable(operators = {RsqlOperator.EQUAL}, customOperators = {MockCustomOperator.class})
private String name;
}
diff --git a/spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/ValidationRSQLVisitorTest.java b/spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/ValidationRSQLVisitorTest.java
index 7e5c27a..6ba7d41 100644
--- a/spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/ValidationRSQLVisitorTest.java
+++ b/spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/ValidationRSQLVisitorTest.java
@@ -70,6 +70,40 @@ void visit_rejectsDisallowedOperator() {
assertEquals("Operator '!=' not allowed on field 'name'", ex.getMessage());
}
+ @Test
+ void visit_allowsMappedAliasField() {
+ ValidationRSQLVisitor visitor = new ValidationRSQLVisitor(
+ TestEntity.class,
+ new FieldMapping[]{mapping("displayName", "name", false)},
+ Collections.emptySet()
+ );
+ parser.parse("displayName==john").accept(visitor);
+ }
+
+ @Test
+ void visit_rejectsOriginalMappedFieldWhenNotAllowed() {
+ ValidationRSQLVisitor visitor = new ValidationRSQLVisitor(
+ TestEntity.class,
+ new FieldMapping[]{mapping("displayName", "name", false)},
+ Collections.emptySet()
+ );
+ QueryValidationException ex = assertThrows(
+ QueryValidationException.class,
+ () -> parser.parse("name==john").accept(visitor)
+ );
+ assertEquals("Unknown field 'name'", ex.getMessage());
+ }
+
+ @Test
+ void visit_allowsOriginalMappedFieldWhenAllowed() {
+ ValidationRSQLVisitor visitor = new ValidationRSQLVisitor(
+ TestEntity.class,
+ new FieldMapping[]{mapping("displayName", "name", true)},
+ Collections.emptySet()
+ );
+ parser.parse("name==john").accept(visitor);
+ }
+
@Test
void visit_allowsCustomOperator() {
Set> customOperators = Set.of(new MockCustomOperator());
@@ -108,6 +142,7 @@ public Predicate toPredicate(RSQLCustomPredicateInput input) {
}
private static class TestEntity {
+
@RsqlFilterable(operators = {RsqlOperator.EQUAL})
private String name;
@@ -115,7 +150,33 @@ private static class TestEntity {
}
private static class TestEntityWithCustom {
+
@RsqlFilterable(operators = {RsqlOperator.EQUAL}, customOperators = {MockCustomOperator.class})
private String name;
}
+
+ private static FieldMapping mapping(String name, String field, boolean allowOriginalFieldName) {
+ return new FieldMapping() {
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public String field() {
+ return field;
+ }
+
+ @Override
+ public boolean allowOriginalFieldName() {
+ return allowOriginalFieldName;
+ }
+
+ @Override
+ public Class extends java.lang.annotation.Annotation> annotationType() {
+ return FieldMapping.class;
+ }
+ };
+ }
}
diff --git a/spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/util/AnnotationUtilTest.java b/spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/util/AnnotationUtilTest.java
new file mode 100644
index 0000000..236dde4
--- /dev/null
+++ b/spring-web-query-core/src/test/java/in/co/akshitbansal/springwebquery/util/AnnotationUtilTest.java
@@ -0,0 +1,48 @@
+package in.co.akshitbansal.springwebquery.util;
+
+import in.co.akshitbansal.springwebquery.annotation.RsqlSpec;
+import in.co.akshitbansal.springwebquery.annotation.WebQuery;
+import in.co.akshitbansal.springwebquery.exception.QueryConfigurationException;
+import org.junit.jupiter.api.Test;
+import org.springframework.core.MethodParameter;
+import org.springframework.data.jpa.domain.Specification;
+
+import java.lang.reflect.Method;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class AnnotationUtilTest {
+
+ @Test
+ void resolveWebQueryFromParameter_returnsAnnotation() throws NoSuchMethodException {
+ Method method = TestController.class.getDeclaredMethod("search", Specification.class);
+ MethodParameter parameter = new MethodParameter(method, 0);
+
+ WebQuery annotation = AnnotationUtil.resolveWebQueryFromParameter(parameter);
+
+ assertEquals(TestEntity.class, annotation.entityClass());
+ }
+
+ @Test
+ void resolveWebQueryFromParameter_throwsWhenMissing() throws NoSuchMethodException {
+ Method method = TestController.class.getDeclaredMethod("searchWithoutWebQuery", Specification.class);
+ MethodParameter parameter = new MethodParameter(method, 0);
+
+ assertThrows(QueryConfigurationException.class, () -> AnnotationUtil.resolveWebQueryFromParameter(parameter));
+ }
+
+ @SuppressWarnings("unused")
+ private static class TestController {
+
+ @WebQuery(entityClass = TestEntity.class)
+ void search(@RsqlSpec Specification specification) {
+ }
+
+ void searchWithoutWebQuery(@RsqlSpec Specification specification) {
+ }
+ }
+
+ private static class TestEntity {
+ }
+}
From 9d6276a84d4313830af6281b4cb1da4d0ba29497 Mon Sep 17 00:00:00 2001
From: Akshit Bansal
Date: Sat, 21 Feb 2026 17:20:55 +0530
Subject: [PATCH 5/5] Enabled starter tests
---
...nfigIntegrationTest.java.in => AutoConfigIntegrationTest.java} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename spring-boot-starter-web-query/src/test/java/in/co/akshitbansal/springwebquery/config/{AutoConfigIntegrationTest.java.in => AutoConfigIntegrationTest.java} (100%)
diff --git a/spring-boot-starter-web-query/src/test/java/in/co/akshitbansal/springwebquery/config/AutoConfigIntegrationTest.java.in b/spring-boot-starter-web-query/src/test/java/in/co/akshitbansal/springwebquery/config/AutoConfigIntegrationTest.java
similarity index 100%
rename from spring-boot-starter-web-query/src/test/java/in/co/akshitbansal/springwebquery/config/AutoConfigIntegrationTest.java.in
rename to spring-boot-starter-web-query/src/test/java/in/co/akshitbansal/springwebquery/config/AutoConfigIntegrationTest.java