Swagger improvements
This commit is contained in:
parent
113a6389fa
commit
34d6dc50b5
@ -15,25 +15,30 @@
|
||||
*/
|
||||
package org.thingsboard.server.config;
|
||||
|
||||
import com.fasterxml.classmate.ResolvedType;
|
||||
import com.fasterxml.classmate.TypeResolver;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
|
||||
import org.thingsboard.server.common.data.security.Authority;
|
||||
import org.thingsboard.server.exception.ThingsboardCredentialsExpiredResponse;
|
||||
import org.thingsboard.server.exception.ThingsboardErrorResponse;
|
||||
import org.thingsboard.server.service.security.auth.rest.LoginRequest;
|
||||
import org.thingsboard.server.service.security.auth.rest.LoginResponse;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.ExampleBuilder;
|
||||
import springfox.documentation.builders.OperationBuilder;
|
||||
import springfox.documentation.builders.RepresentationBuilder;
|
||||
import springfox.documentation.builders.RequestParameterBuilder;
|
||||
import springfox.documentation.builders.ResponseBuilder;
|
||||
import springfox.documentation.schema.Example;
|
||||
import springfox.documentation.service.ApiDescription;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.service.ApiListing;
|
||||
@ -74,6 +79,7 @@ import static java.util.function.Predicate.not;
|
||||
import static springfox.documentation.builders.PathSelectors.any;
|
||||
import static springfox.documentation.builders.PathSelectors.regex;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class SwaggerConfiguration {
|
||||
|
||||
@ -100,44 +106,30 @@ public class SwaggerConfiguration {
|
||||
@Value("${swagger.version}")
|
||||
private String version;
|
||||
|
||||
@Autowired
|
||||
private CachingOperationNameGenerator operationNames;
|
||||
|
||||
@Bean
|
||||
public Docket thingsboardApi() {
|
||||
TypeResolver typeResolver = new TypeResolver();
|
||||
final ResolvedType jsonNodeType =
|
||||
typeResolver.resolve(
|
||||
JsonNode.class);
|
||||
final ResolvedType stringType =
|
||||
typeResolver.resolve(
|
||||
String.class);
|
||||
|
||||
return new Docket(DocumentationType.OAS_30)
|
||||
.groupName("thingsboard")
|
||||
.apiInfo(apiInfo())
|
||||
.additionalModels(
|
||||
typeResolver.resolve(ThingsboardErrorResponse.class),
|
||||
typeResolver.resolve(ThingsboardCredentialsExpiredResponse.class),
|
||||
typeResolver.resolve(LoginRequest.class),
|
||||
typeResolver.resolve(LoginResponse.class)
|
||||
)
|
||||
/* .alternateTypeRules(
|
||||
new AlternateTypeRule(
|
||||
jsonNodeType,
|
||||
stringType))*/
|
||||
.select()
|
||||
.paths(apiPaths())
|
||||
.paths(any())
|
||||
.build()
|
||||
.globalResponses(HttpMethod.GET,
|
||||
List.of(
|
||||
new ResponseBuilder()
|
||||
.code("401")
|
||||
.description("Unauthorized")
|
||||
.representation(MediaType.APPLICATION_JSON)
|
||||
.apply(classRepresentation(ThingsboardErrorResponse.class, true))
|
||||
.build()
|
||||
)
|
||||
defaultErrorResponses(false)
|
||||
)
|
||||
.globalResponses(HttpMethod.POST,
|
||||
defaultErrorResponses(true)
|
||||
)
|
||||
.globalResponses(HttpMethod.DELETE,
|
||||
defaultErrorResponses(false)
|
||||
)
|
||||
.securitySchemes(newArrayList(httpLogin()))
|
||||
.securityContexts(newArrayList(securityContext()))
|
||||
@ -146,15 +138,15 @@ public class SwaggerConfiguration {
|
||||
|
||||
@Bean
|
||||
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER)
|
||||
ApiListingScannerPlugin loginEndpointListingScanner() {
|
||||
ApiListingScannerPlugin loginEndpointListingScanner(final CachingOperationNameGenerator operationNames) {
|
||||
return new ApiListingScannerPlugin() {
|
||||
@Override
|
||||
public List<ApiDescription> apply(DocumentationContext context) {
|
||||
return List.of(loginEndpointApiDescription());
|
||||
return List.of(loginEndpointApiDescription(operationNames));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(DocumentationType delimiter) {
|
||||
public boolean supports(@NotNull DocumentationType delimiter) {
|
||||
return DocumentationType.SWAGGER_2.equals(delimiter) || DocumentationType.OAS_30.equals(delimiter);
|
||||
}
|
||||
};
|
||||
@ -176,7 +168,7 @@ public class SwaggerConfiguration {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(DocumentationType delimiter) {
|
||||
public boolean supports(@NotNull DocumentationType delimiter) {
|
||||
return DocumentationType.SWAGGER_2.equals(delimiter) || DocumentationType.OAS_30.equals(delimiter);
|
||||
}
|
||||
};
|
||||
@ -199,6 +191,7 @@ public class SwaggerConfiguration {
|
||||
.showCommonExtensions(false)
|
||||
.supportedSubmitMethods(UiConfiguration.Constants.DEFAULT_SUBMIT_METHODS)
|
||||
.validatorUrl(null)
|
||||
.persistAuthorization(true)
|
||||
.syntaxHighlightActivate(true)
|
||||
.syntaxHighlightTheme("agate")
|
||||
.build();
|
||||
@ -248,7 +241,7 @@ public class SwaggerConfiguration {
|
||||
.build();
|
||||
}
|
||||
|
||||
private ApiDescription loginEndpointApiDescription() {
|
||||
private ApiDescription loginEndpointApiDescription(final CachingOperationNameGenerator operationNames) {
|
||||
return new ApiDescription(null, "/api/auth/login", "Login method to get user JWT token data", "Login endpoint", Collections.singletonList(
|
||||
new OperationBuilder(operationNames)
|
||||
.summary("Login method to get user JWT token data")
|
||||
@ -257,7 +250,8 @@ public class SwaggerConfiguration {
|
||||
.position(0)
|
||||
.codegenMethodNameStem("loginPost")
|
||||
.method(HttpMethod.POST)
|
||||
.notes("Login method to get user JWT token data.\n\nValue of the response **token** field can be used as JWT token value for authorization.")
|
||||
.notes("Login method used to authenticate user and get JWT token data.\n\nValue of the response **token** " +
|
||||
"field can be used as **X-Authorization** header value:\n\n`X-Authorization: Bearer $JWT_TOKEN_VALUE`.")
|
||||
.requestParameters(
|
||||
List.of(
|
||||
new RequestParameterBuilder()
|
||||
@ -278,7 +272,8 @@ public class SwaggerConfiguration {
|
||||
}
|
||||
|
||||
private Collection<Response> loginResponses() {
|
||||
return List.of(
|
||||
List<Response> responses = new ArrayList<>();
|
||||
responses.add(
|
||||
new ResponseBuilder()
|
||||
.code("200")
|
||||
.description("OK")
|
||||
@ -286,11 +281,82 @@ public class SwaggerConfiguration {
|
||||
.apply(classRepresentation(LoginResponse.class, true)).
|
||||
build()
|
||||
);
|
||||
responses.addAll(loginErrorResponses());
|
||||
return responses;
|
||||
}
|
||||
|
||||
/** Helper methods **/
|
||||
|
||||
private Consumer<RepresentationBuilder> classRepresentation(Class clazz, boolean isResponse) {
|
||||
private List<Response> defaultErrorResponses(boolean isPost) {
|
||||
return List.of(
|
||||
errorResponse("400", "Bad Request",
|
||||
ThingsboardErrorResponse.of(isPost ? "Invalid request body" : "Invalid UUID string: 123", ThingsboardErrorCode.BAD_REQUEST_PARAMS, HttpStatus.BAD_REQUEST)),
|
||||
errorResponse("401", "Unauthorized",
|
||||
ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
|
||||
errorResponse("403", "Forbidden",
|
||||
ThingsboardErrorResponse.of("You don't have permission to perform this operation!",
|
||||
ThingsboardErrorCode.PERMISSION_DENIED, HttpStatus.FORBIDDEN)),
|
||||
errorResponse("404", "Not Found",
|
||||
ThingsboardErrorResponse.of("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND, HttpStatus.NOT_FOUND)),
|
||||
errorResponse("429", "Too Many Requests",
|
||||
ThingsboardErrorResponse.of("Too many requests for current tenant!",
|
||||
ThingsboardErrorCode.TOO_MANY_REQUESTS, HttpStatus.TOO_MANY_REQUESTS))
|
||||
);
|
||||
}
|
||||
|
||||
private List<Response> loginErrorResponses() {
|
||||
return List.of(
|
||||
errorResponse("401", "Unauthorized",
|
||||
List.of(
|
||||
errorExample("bad-credentials", "Bad credentials",
|
||||
ThingsboardErrorResponse.of("Invalid username or password", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
|
||||
errorExample("token-expired", "JWT token expired",
|
||||
ThingsboardErrorResponse.of("Token has expired", ThingsboardErrorCode.JWT_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED)),
|
||||
errorExample("account-disabled", "Disabled account",
|
||||
ThingsboardErrorResponse.of("User account is not active", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
|
||||
errorExample("account-locked", "Locked account",
|
||||
ThingsboardErrorResponse.of("User account is locked due to security policy", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
|
||||
errorExample("authentication-failed", "General authentication error",
|
||||
ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED))
|
||||
)
|
||||
),
|
||||
errorResponse("401 ", "Unauthorized (**Expired credentials**)",
|
||||
List.of(
|
||||
errorExample("credentials-expired", "Expired credentials",
|
||||
ThingsboardCredentialsExpiredResponse.of("User password expired!", RandomStringUtils.randomAlphanumeric(30)))
|
||||
), ThingsboardCredentialsExpiredResponse.class
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private Response errorResponse(String code, String description, ThingsboardErrorResponse example) {
|
||||
return errorResponse(code, description, List.of(errorExample("error-code-" + code, description, example)));
|
||||
}
|
||||
|
||||
private Response errorResponse(String code, String description, List<Example> examples) {
|
||||
return errorResponse(code, description, examples, ThingsboardErrorResponse.class);
|
||||
}
|
||||
|
||||
private Response errorResponse(String code, String description, List<Example> examples,
|
||||
Class<? extends ThingsboardErrorResponse> errorResponseClass) {
|
||||
return new ResponseBuilder()
|
||||
.code(code)
|
||||
.description(description)
|
||||
.examples(examples)
|
||||
.representation(MediaType.APPLICATION_JSON)
|
||||
.apply(classRepresentation(errorResponseClass, true))
|
||||
.build();
|
||||
}
|
||||
|
||||
private Example errorExample(String id, String summary, ThingsboardErrorResponse example) {
|
||||
return new ExampleBuilder()
|
||||
.mediaType(MediaType.APPLICATION_JSON_VALUE)
|
||||
.summary(summary)
|
||||
.id(id)
|
||||
.value(example).build();
|
||||
}
|
||||
|
||||
private Consumer<RepresentationBuilder> classRepresentation(Class<?> clazz, boolean isResponse) {
|
||||
return r -> r.model(
|
||||
m ->
|
||||
m.referenceModel(ref ->
|
||||
|
||||
@ -15,9 +15,12 @@
|
||||
*/
|
||||
package org.thingsboard.server.exception;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
|
||||
|
||||
@ApiModel
|
||||
public class ThingsboardCredentialsExpiredResponse extends ThingsboardErrorResponse {
|
||||
|
||||
private final String resetToken;
|
||||
@ -31,6 +34,7 @@ public class ThingsboardCredentialsExpiredResponse extends ThingsboardErrorRespo
|
||||
return new ThingsboardCredentialsExpiredResponse(message, resetToken);
|
||||
}
|
||||
|
||||
@ApiModelProperty(position = 5, value = "Password reset token", readOnly = true)
|
||||
public String getResetToken() {
|
||||
return resetToken;
|
||||
}
|
||||
|
||||
@ -15,11 +15,14 @@
|
||||
*/
|
||||
package org.thingsboard.server.exception;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@ApiModel
|
||||
public class ThingsboardErrorResponse {
|
||||
// HTTP Response Status Code
|
||||
private final HttpStatus status;
|
||||
@ -43,18 +46,35 @@ public class ThingsboardErrorResponse {
|
||||
return new ThingsboardErrorResponse(message, errorCode, status);
|
||||
}
|
||||
|
||||
@ApiModelProperty(position = 1, value = "HTTP Response Status Code", example = "401", readOnly = true)
|
||||
public Integer getStatus() {
|
||||
return status.value();
|
||||
}
|
||||
|
||||
@ApiModelProperty(position = 2, value = "Error message", example = "Authentication failed", readOnly = true)
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@ApiModelProperty(position = 3, value = "Platform error code:" +
|
||||
"\n* `2` - General error (HTTP: 500 - Internal Server Error)" +
|
||||
"\n\n* `10` - Authentication failed (HTTP: 401 - Unauthorized)" +
|
||||
"\n\n* `11` - JWT token expired (HTTP: 401 - Unauthorized)" +
|
||||
"\n\n* `15` - Credentials expired (HTTP: 401 - Unauthorized)" +
|
||||
"\n\n* `20` - Permission denied (HTTP: 403 - Forbidden)" +
|
||||
"\n\n* `30` - Invalid arguments (HTTP: 400 - Bad Request)" +
|
||||
"\n\n* `31` - Bad request params (HTTP: 400 - Bad Request)" +
|
||||
"\n\n* `32` - Item not found (HTTP: 404 - Not Found)" +
|
||||
"\n\n* `33` - Too many requests (HTTP: 429 - Too Many Requests)" +
|
||||
"\n\n* `34` - Too many updates (Too many updates over Websocket session)" +
|
||||
"\n\n* `40` - Subscription violation (HTTP: 403 - Forbidden)",
|
||||
example = "10", dataType = "integer",
|
||||
readOnly = true)
|
||||
public ThingsboardErrorCode getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
@ApiModelProperty(position = 4, value = "Timestamp", readOnly = true)
|
||||
public Date getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
2
pom.xml
2
pom.xml
@ -83,7 +83,7 @@
|
||||
<rabbitmq.version>4.8.0</rabbitmq.version>
|
||||
<surfire.version>2.19.1</surfire.version>
|
||||
<jar-plugin.version>3.0.2</jar-plugin.version>
|
||||
<springfox-swagger.version>3.0.1</springfox-swagger.version>
|
||||
<springfox-swagger.version>3.0.2</springfox-swagger.version>
|
||||
<swagger-annotations.version>1.6.3</swagger-annotations.version>
|
||||
<spatial4j.version>0.7</spatial4j.version>
|
||||
<jts.version>1.15.0</jts.version>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user