Skip to content

Commit d432a4b

Browse files
author
s.mareychev
committed
Add sort request param, replace resolveArgument method type from Object to Pageable, replace swagger with spring openapi
1 parent d2ae9b7 commit d432a4b

File tree

11 files changed

+300
-97
lines changed

11 files changed

+300
-97
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## [1.12.0] - 2024-08-30
4+
### Changed:
5+
- Update all dependencies
6+
- Add support for sort field
7+
- Replace **springfox-swagger** with the **springdoc-openapi-starter-webmvc-ui** dependency
8+
39
## [1.11.0] - 2023-08-02
410
### Added:
511
- Error meta field for meta object containing non-standard meta-information about the error

README.md

+46-5
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,29 @@ websockets, queues etc and don't need complex entities inner relationships in ou
88
exists standards so this library for this goals.
99

1010
## Docs
11-
[See project page](https://slm-dev.com/jsonapi-simple/)
11+
[See project page](https://devslm.github.io/jsonapi-simple/)
1212

1313
## Usage
1414
Add dependency to your project:
1515
```xml
1616
<dependency>
1717
<groupId>com.slm-dev</groupId>
1818
<artifactId>jsonapi-simple</artifactId>
19-
<version>1.11.0</version>
19+
<version>1.12.0</version>
2020
</dependency>
2121
```
2222

2323
> **Warning:**
2424
> The id field is included in the attributes because it more convenient for API clients.
2525
26+
### Navigation
27+
- [Build Response](#build-response)
28+
- [Filtering](#filtering)
29+
- [Sparse fieldsets](#sparse-fieldsets)
30+
- [Pagination](#pagination)
31+
- [Sorting](#sorting)
32+
- [Examples](#other-response-examples)
33+
2634
### Build Response
2735

2836
See documentation part: [response structure](https://jsonapi.org/format/#document-structure)
@@ -34,17 +42,17 @@ field is ```id```.
3442
If we want to use data types like lists, maps etc. in the response we can set manually data type to avoid exception
3543
(see example below).
3644

37-
Each response may contain generics (if you planning to use SWAGGER) or may not (without SWAGGER), for example both
45+
Each response may contain generics (if you planning to use OPENAPI) or may not (without OPENAPI), for example both
3846
variants correct:
3947
```java
4048
public class RestController {
41-
// The simplest option without SWAGGER support
49+
// The simplest option without OPENAPI support
4250
public Response responseWithoutGenerics() {
4351
return Response.builder()
4452
.build();
4553
}
4654

47-
// This option will display correctly in SWAGGER with all DTO fields
55+
// This option will display correctly in OPENAPI with all DTO fields
4856
public Response<SomeDto> responseWithGenerics() {
4957
return Response.<SomeDto, SomeDto>builder()
5058
.build();
@@ -349,6 +357,39 @@ And for the page size are:
349357
#### Request page always starts from 0 for compatible with spring repositories and etc.!
350358
#### If request page number < 1 resolver always return number = 0 and if size < 1 it always returns default value = 25!
351359
360+
### Sorting
361+
362+
See documentation part: [fetching-sorting](https://jsonapi.org/format/#fetching-sorting)
363+
364+
If you want to use request sort with annotation ```@RequestJsonApiPage``` add argument resolver in your configuration
365+
as described in section [Pagination](#pagination).
366+
367+
Then you can use annotation ```@RequestJsonApiPage``` in controllers and get standard spring Pageable object.
368+
369+
For example:
370+
```java
371+
@Slf4j
372+
@RestController
373+
@AllArgsConstructor
374+
@RequestMapping(value = "/app", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
375+
public class RestController {
376+
@GetMapping
377+
public Response<Void> get(final @RequestJsonApiPage Pageable page) throws Exception {
378+
final Sort sort = page.getSort();
379+
// do anything with sort
380+
381+
return Response.<Void, Void>builder()
382+
.build();
383+
}
384+
}
385+
```
386+
387+
Now if request will be contained list of fields like ```sort=field1,field2,-field3``` we can get them in the ```Pageable``` object.
388+
389+
For ASC order we just put fields name as is, for example: ```sort=name,age,...``` (see JSON:API spec).
390+
391+
For DESC order we should put fields name with prefix **-**, for example: ```sort=-name,age,...``` (see JSON:API spec).
392+
352393
### Other response examples
353394
Example response with one data object:
354395
```java

pom.xml

+18-58
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<modelVersion>4.0.0</modelVersion>
55
<groupId>com.slm-dev</groupId>
66
<artifactId>jsonapi-simple</artifactId>
7-
<version>1.11.0</version>
7+
<version>1.12.0</version>
88
<name>jsonapi-simple</name>
99
<description>Simple implementation of the JSON:API specification</description>
1010
<url>https://slm-dev.com/jsonapi-simple/</url>
@@ -35,23 +35,20 @@
3535
<properties>
3636
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
3737
<java.version>11</java.version>
38-
<spring.web.version>5.3.25</spring.web.version>
39-
<spring.data.commons.version>2.7.8</spring.data.commons.version>
40-
<lombok.version>1.18.26</lombok.version>
41-
<lombok.maven.plugin.version>1.18.20.0</lombok.maven.plugin.version>
42-
<jackson.version>2.14.2</jackson.version>
43-
<jackson.datatype.version>2.14.2</jackson.datatype.version>
44-
<springfox.version>3.0.0</springfox.version>
45-
<junit.version>5.9.2</junit.version>
46-
<hamcrest.version>2.2</hamcrest.version>
47-
<mockito.version>5.1.1</mockito.version>
48-
<maven.compiler.version>3.10.1</maven.compiler.version>
49-
<maven.surefire.version>2.22.2</maven.surefire.version>
50-
<maven.deploy.version>2.8.2</maven.deploy.version>
51-
<nexus.staging.maven.version>1.6.13</nexus.staging.maven.version>
52-
<maven.source.version>3.2.1</maven.source.version>
53-
<maven.javadoc.version>3.5.0</maven.javadoc.version>
54-
<maven.gpg.version>3.0.1</maven.gpg.version>
38+
<spring.web.version>5.3.39</spring.web.version>
39+
<spring.data.commons.version>2.7.18</spring.data.commons.version>
40+
<lombok.version>1.18.34</lombok.version>
41+
<jackson.version>2.16.2</jackson.version>
42+
<jackson.datatype.version>2.16.2</jackson.datatype.version>
43+
<springdoc.openapi.version>2.6.0</springdoc.openapi.version>
44+
<junit.version>5.11.0</junit.version>
45+
<hamcrest.version>3.0</hamcrest.version>
46+
<mockito.version>5.13.0</mockito.version>
47+
<maven.compiler.version>3.13.0</maven.compiler.version>
48+
<maven.surefire.version>3.5.0</maven.surefire.version>
49+
<nexus.staging.maven.version>1.7.0</nexus.staging.maven.version>
50+
<maven.source.version>3.3.1</maven.source.version>
51+
<maven.javadoc.version>3.10.0</maven.javadoc.version>
5552
</properties>
5653

5754
<dependencies>
@@ -86,9 +83,9 @@
8683
</dependency>
8784

8885
<dependency>
89-
<groupId>io.springfox</groupId>
90-
<artifactId>springfox-swagger2</artifactId>
91-
<version>${springfox.version}</version>
86+
<groupId>org.springdoc</groupId>
87+
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
88+
<version>${springdoc.openapi.version}</version>
9289
</dependency>
9390

9491
<dependency>
@@ -111,45 +108,9 @@
111108
<version>${mockito.version}</version>
112109
<scope>test</scope>
113110
</dependency>
114-
115-
<dependency>
116-
<groupId>org.projectlombok</groupId>
117-
<artifactId>lombok-maven-plugin</artifactId>
118-
<version>${lombok.maven.plugin.version}</version>
119-
</dependency>
120111
</dependencies>
121112

122113
<profiles>
123-
<profile>
124-
<id>delombok</id>
125-
<build>
126-
<plugins>
127-
<plugin>
128-
<groupId>org.projectlombok</groupId>
129-
<artifactId>lombok-maven-plugin</artifactId>
130-
<version>${lombok.maven.plugin.version}</version>
131-
<executions>
132-
<execution>
133-
<phase>generate-sources</phase>
134-
<goals>
135-
<goal>delombok</goal>
136-
</goals>
137-
</execution>
138-
</executions>
139-
<configuration>
140-
<sourceDirectory>src/main/java/com/slmdev/jsonapi/simple</sourceDirectory>
141-
<verbose>true</verbose>
142-
<formatPreferences>
143-
<generateDelombokComment>skip</generateDelombokComment>
144-
<javaLangAsFQN>skip</javaLangAsFQN>
145-
<suppressWarnings>skip</suppressWarnings>
146-
</formatPreferences>
147-
</configuration>
148-
</plugin>
149-
</plugins>
150-
</build>
151-
</profile>
152-
153114
<profile>
154115
<id>release</id>
155116
<build>
@@ -200,7 +161,6 @@
200161
<plugin>
201162
<groupId>org.apache.maven.plugins</groupId>
202163
<artifactId>maven-gpg-plugin</artifactId>
203-
<version>${maven.gpg.version}</version>
204164
<executions>
205165
<execution>
206166
<id>sign-artifacts</id>

src/main/java/com/slmdev/jsonapi/simple/annotation/RequestJsonApiPage.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
package com.slmdev.jsonapi.simple.annotation;
22

3-
import org.springframework.data.domain.Pageable;
4-
53
import java.lang.annotation.*;
64

75
/**
86
* The annotation extract page fields from request and
9-
* store them in the {@link Pageable} object.
7+
* store them in the {@link org.springframework.data.domain.Pageable} object.
108
*/
119
@Target(ElementType.PARAMETER)
1210
@Retention(RetentionPolicy.RUNTIME)

src/main/java/com/slmdev/jsonapi/simple/resolver/JsonApiPageArgumentResolver.java

+29-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import com.slmdev.jsonapi.simple.annotation.RequestJsonApiPage;
44
import org.springframework.core.MethodParameter;
55
import org.springframework.data.domain.PageRequest;
6+
import org.springframework.data.domain.Pageable;
7+
import org.springframework.data.domain.Sort;
8+
import org.springframework.util.CollectionUtils;
69
import org.springframework.web.bind.support.WebDataBinderFactory;
710
import org.springframework.web.context.request.NativeWebRequest;
811
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
@@ -30,12 +33,13 @@ public boolean supportsParameter(MethodParameter parameter) {
3033
return parameter.getParameterAnnotation(RequestJsonApiPage.class) != null;
3134
}
3235

33-
public Object resolveArgument(final MethodParameter methodParameter,
34-
final ModelAndViewContainer modelAndViewContainer,
35-
final NativeWebRequest nativeWebRequest,
36-
final WebDataBinderFactory webDataBinderFactory) {
36+
public Pageable resolveArgument(final MethodParameter methodParameter,
37+
final ModelAndViewContainer modelAndViewContainer,
38+
final NativeWebRequest nativeWebRequest,
39+
final WebDataBinderFactory webDataBinderFactory) {
3740
final RequestJsonApiPage RequestJsonApiPage = methodParameter.getParameterAnnotation(RequestJsonApiPage.class);
3841
final String pageKeyStart = RequestJsonApiPage.name() + REQUEST_PAGE_KEY_BRACKET_START;
42+
final Sort sort = parseSortField(nativeWebRequest);
3943
int page = 0;
4044
int size = 25;
4145

@@ -75,6 +79,10 @@ public Object resolveArgument(final MethodParameter methodParameter,
7579
if (size < 1) {
7680
size = 25;
7781
}
82+
83+
if (sort != null) {
84+
return PageRequest.of(page, size, sort);
85+
}
7886
return PageRequest.of(page, size);
7987
}
8088

@@ -92,4 +100,21 @@ private List<String> valueToList(final String[] values) {
92100

93101
return valueItems;
94102
}
103+
104+
private Sort parseSortField(final NativeWebRequest nativeWebRequest) {
105+
if (CollectionUtils.isEmpty(nativeWebRequest.getParameterMap())
106+
|| nativeWebRequest.getParameterMap().get("sort") == null) {
107+
return null;
108+
}
109+
final var sortOrders = Arrays.stream(nativeWebRequest.getParameterMap()
110+
.get("sort"))
111+
.map(field -> {
112+
if (!field.startsWith("-")) {
113+
return Sort.Order.asc(field);
114+
}
115+
return Sort.Order.desc(field.substring(1));
116+
}).collect(Collectors.toList());
117+
118+
return Sort.by(sortOrders);
119+
}
95120
}

src/main/java/com/slmdev/jsonapi/simple/response/Api.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.slmdev.jsonapi.simple.response;
22

33
import com.fasterxml.jackson.annotation.JsonInclude;
4-
import io.swagger.annotations.ApiModelProperty;
4+
import io.swagger.v3.oas.annotations.media.Schema;
55
import lombok.AllArgsConstructor;
66
import lombok.Data;
77
import lombok.NoArgsConstructor;
@@ -13,6 +13,6 @@
1313
@Accessors(chain = true)
1414
@JsonInclude(JsonInclude.Include.NON_NULL)
1515
public class Api {
16-
@ApiModelProperty(value = "Application specific api version", required = true)
16+
@Schema(description = "Application specific api version", requiredMode = Schema.RequiredMode.REQUIRED)
1717
private String version;
1818
}

src/main/java/com/slmdev/jsonapi/simple/response/Error.java

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.slmdev.jsonapi.simple.response;
22

33
import com.fasterxml.jackson.annotation.JsonInclude;
4-
import io.swagger.annotations.ApiModelProperty;
4+
import io.swagger.v3.oas.annotations.media.Schema;
55
import lombok.*;
66
import lombok.experimental.Accessors;
77

@@ -12,17 +12,17 @@
1212
@Accessors(chain = true)
1313
@JsonInclude(JsonInclude.Include.NON_NULL)
1414
public class Error {
15-
@ApiModelProperty(value = "Response HTTP code", required = true)
15+
@Schema(description = "Response HTTP code", requiredMode = Schema.RequiredMode.REQUIRED)
1616
private int status;
17-
@ApiModelProperty("Application specific error code")
17+
@Schema(description = "Application specific error code")
1818
private String code;
19-
@ApiModelProperty(value = "Error detail message", required = true)
19+
@Schema(description = "Error detail message", requiredMode = Schema.RequiredMode.REQUIRED)
2020
private String detail;
21-
@ApiModelProperty(value = "Parameter name only for validation errors")
21+
@Schema(description = "Parameter name only for validation errors")
2222
private Source source;
23-
@ApiModelProperty(value = "Links object can be used to represent links")
23+
@Schema(description = "Links object can be used to represent links")
2424
private ErrorLink links;
25-
@ApiModelProperty(value = "Meta object containing non-standard meta-information about the error")
25+
@Schema(description = "Meta object containing non-standard meta-information about the error")
2626
private Object meta;
2727

2828
@Getter
@@ -32,7 +32,7 @@ public class Error {
3232
@Accessors(chain = true)
3333
@JsonInclude(JsonInclude.Include.NON_NULL)
3434
public static class Source {
35-
@ApiModelProperty(value = "Object containing references to the primary source of the error")
35+
@Schema(description = "Object containing references to the primary source of the error")
3636
private String parameter;
3737
}
3838

@@ -44,9 +44,9 @@ public static class Source {
4444
@Accessors(chain = true)
4545
@JsonInclude(JsonInclude.Include.NON_NULL)
4646
public static class ErrorLink {
47-
@ApiModelProperty(value = "A link that leads to further details about this particular occurrence of the problem")
47+
@Schema(description = "A link that leads to further details about this particular occurrence of the problem")
4848
private String about;
49-
@ApiModelProperty(value = "A link that identifies the type of error that this particular error is an instance of")
49+
@Schema(description = "A link that identifies the type of error that this particular error is an instance of")
5050
private String type;
5151
}
5252
@Getter

0 commit comments

Comments
 (0)