Post Validation didn't work with Spring-Boot Swagger RestAPI - spring-boot

I'm currently working on a Cross Platform App to record the expenses and the incomes. So far so good. However, when I want to add a Backend Post Validation (so nobody can save incomes like €19,99999 etc.) the Post Validation does not work. It would be great if somebody could help me. Here are the details:
I am adding the springfox-bean-validation to my Backend
Entity
#ApiModel(
value = "Expense",
description =
"Information of all expenses with cost, expense date as well as the purpose to which these expenses went"
)
#Entity
#Table(name = "Expenses")
data class Expense(
#ApiModelProperty(notes = "Cost of the Expense in €")
#DecimalMin(value = "0.0", inclusive = false, message = "Price have to be higher than 0.0")
#Digits(integer = Int.MAX_VALUE, fraction = 2, message = "Price have only 2 decimal places")
var cost: BigDecimal,
#ApiModelProperty(notes = "Creation date of the Expense")
#NotNull(message = "Date may not be Null")
var date: LocalDate,
#ApiModelProperty(notes = "Purpose of the expense")
var purpose: String?
) : DbEntity() {
constructor() : this(BigDecimal.ZERO, LocalDate.now(), "")
}
Controller
#CrossOrigin
#RestController
#Api(value = "/finance", description = "FinanceController", produces = "application/json")
#RequestMapping("/api/finance")
class FinanceController(
private val expenseRepository: ExpenseRepository
) {
#ApiOperation(value = "Create an Expense", response = Expense::class)
#PostMapping("/expanse")
fun createExpense(#RequestBody expenseInfo: Expense): Expense {
return expenseRepository.save(expenseInfo)
}
}
SpringFoxConfig
#Configuration
#EnableSwagger2
#Import(BeanValidatorPluginsConfiguration::class)
class SwaggerConfiguration {
#Bean
fun api(): Docket = Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
}
And that is the resulting Output from the swagger UI. Why are there no annotations like #NotNull or #Digits(...)?
When I test a Post request, I get 200 as HTTP Response Code and no Error!
{
"cost": 19.99999999,
"date": "2020-06-06",
"purpose": "test",
"id": 15
}

Related

Spring boot serialize kotlin enum by custom property

I have an Enum and I would like to serialize it using custom property. It works in my tests but not when I make request.
Enum should be mapped using JsonValue
enum class PlantProtectionSortColumn(
#get:JsonValue val propertyName: String,
) {
NAME("name"),
REGISTRATION_NUMBER("registrationNumber");
}
In test the lowercase case works as expected.
class PlantProtectionSortColumnTest : ServiceSpec() {
#Autowired
lateinit var mapper: ObjectMapper
data class PlantProtectionSortColumnWrapper(
val sort: PlantProtectionSortColumn,
)
init {
// this works
test("Deserialize PlantProtectionSortColumn enum with custom name ") {
val json = """
{
"sort": "registrationNumber"
}
"""
val result = mapper.readValue(json, PlantProtectionSortColumnWrapper::class.java)
result.sort shouldBe PlantProtectionSortColumn.REGISTRATION_NUMBER
}
// this one fails
test("Deserialize PlantProtectionSortColumn enum with enum name ") {
val json = """
{
"sort": "REGISTRATION_NUMBER"
}
"""
val result = mapper.readValue(json, PlantProtectionSortColumnWrapper::class.java)
result.sort shouldBe PlantProtectionSortColumn.REGISTRATION_NUMBER
}
}
}
But in controller, when i send request with lowercase I get 400. But when the request matches the enum name It works, but response is returned with lowercase. So Spring is not using the objectMapper only for request, in response it is used.
private const val RESOURCE_PATH = "$API_PATH/plant-protection"
#RestController
#RequestMapping(RESOURCE_PATH, produces = [MediaType.APPLICATION_JSON_VALUE])
class PlantProtectionController() {
#GetMapping("/test")
fun get(
#RequestParam sortColumn: PlantProtectionSortColumn,
) = sortColumn
}
I believe kqr's answer is correct and you need to configure converter, not JSON deserializer.
It could look like:
#Component
class StringToPlantProtectionSortColumnConverter : Converter<String, PlantProtectionSortColumn> {
override fun convert(source: String): PlantProtectionSortColumn {
return PlantProtectionSortColumn.values().firstOrNull { it.propertyName == source }
?: throw NotFoundException(PlantProtectionSortColumn::class, source)
}}
In your endpoint you are not parsing json body but query parameters, which are not in json format.

How to expose data to SSE from a #Tailable query in Spring Data Mongo

After reading the docs of #Tailable of Spring Data MongoDB, I think it is good to use it for message notifications.
#SpringBootApplication
class ServerApplication {
#Bean
fun runner(template: ReactiveMongoTemplate) = CommandLineRunner {
println("running CommandLineRunner...")
template.executeCommand("{\"convertToCapped\": \"messages\", size: 100000}");
}
fun main(args: Array<String>) {
runApplication<ServerApplication>(*args)
}
}
---------
#RestController()
#RequestMapping(value = ["messages"])
#CrossOrigin(origins = ["http://localhost:4200"])
class MessageController(private val messages: MessageRepository) {
#PostMapping
fun hello(p: String) =
this.messages.save(Message(body = p, sentAt = Instant.now())).log().then()
#GetMapping(produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
fun messageStream(): Flux<Message> = this.messages.getMessagesBy().log()
}
-----------
interface MessageRepository : ReactiveMongoRepository<Message, String> {
#Tailable
fun getMessagesBy(): Flux<Message>
}
------------
#Document(collection = "messages")
data class Message(#Id var id: String? = null, var body: String, var sentAt: Instant = Instant.now())
How to implement it?
Done it by myself, check my solution.
I have resolved this issue myself, check the sample codes.
Also, published a post on medium to demonstrate how to use it a SPA client written in Angular.

spring type conversion not working - Giving Type Mismatch

I have tried creating a custom converter as specified in - https://www.baeldung.com/spring-type-conversions#creating-a-custom-converter but I am still getting type mismatch when I am carrying out the conversion.
I am creating a converter class (PageToApiResponse) which converts from Page<*> to ResponseList. This converter is then added to registry in the WebConfig class. However when I want to implicitly convert from a Page< ProductDemand > to ResponseList in the controller I get a type mismatch (The service returns Page< ProductDemand >). I have tried annotating the converter class with #Component but that doesn't work.
The error is as follows:
Required: ResponseList.
Found: Page< ProductDemand >
Any help would be greatly appreciated.
Here are the classes involved -
data class ResponseList(
val total: Int,
val limit: Int,
val offset: Int,
val results: List<*>
)
class PageToApiResponse : Converter<Page<*>, ResponseList> {
override fun convert(source: Page<*>) = ResponseList(source.numberOfElements, source.size, source.number, source.content)
}
#Configuration
class WebConfig : WebMvcConfigurer {
override fun addFormatters(registry: FormatterRegistry) {
registry.addConverter(PageToApiResponse())
}
}
#ApiOperation(value = "Find demand by GPI")
#ApiResponses(
ApiResponse(code = 200, message = "Demand results", response = ProductDemand::class, responseContainer = "List"),
ApiResponse(code = 400, message = "Bad input parameter"),
ApiResponse(code = 401, message = "Unauthorized"),
ApiResponse(code = 429, message = "Too Many Requests")
)
#PreAuthorize("hasAnyAuthority('${Authorities.EXT_USAGE_VIEW}')")
#GetMapping("")
fun getDemand(
#RequestParam(required = true) gpi: String,
#PageableDefault(
page = 0,
size = 50,
sort = ["molecule"],
direction = Sort.Direction.DESC
) pageable: Pageable
): ResponseList = service.getDemand(gpi, pageable)

Swagger shows Mongo ObjectId as complex JSON instead of String

Project setup
I have a Kotlin Spring Boot 2.0 project that exposes a #RestController API that returns MongoDB models. For example, this model and controller:
#RestController
#RequestMapping("/api/accounts")
class AccountsController() {
#GetMapping
fun list(): List<Account> {
return listOf(Account(ObjectId(), "Account 1"), Account(ObjectId(), "Account 2"), Account(ObjectId(), "Account 3"))
}
}
#Document
data class Account(
#Id val id: ObjectId? = null,
val name: String
)
These models have ObjectId identifiers, but in the API I want them to be treated as plain String (i.e. instead of a complex JSON, the default behaviour).
To achieve this, I created these components to configure Spring Boot parameter binding and JSON parsing:
#JsonComponent
class ObjectIdJsonSerializer : JsonSerializer<ObjectId>() {
override fun serialize(value: ObjectId?, gen: JsonGenerator?, serializers: SerializerProvider?) {
if (value == null || gen == null) return
gen.writeString(value.toHexString())
}
}
#JsonComponent
class ObjectIdJsonDeserializer : JsonDeserializer<ObjectId>() {
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): ObjectId? {
if (p == null) return null
val text = p.getCodec().readTree<TextNode>(p).textValue()
return ObjectId(text)
}
}
#Component
class StringToObjectIdConverter : Converter<String, ObjectId> {
override fun convert(source: String): ObjectId? {
return ObjectId(source)
}
}
So far this works as intended, calls to the API return this JSON:
[
{
"id": "5da454f4307b0a8b30838839",
"name": "Account 1"
},
{
"id": "5da454f4307b0a8b3083883a",
"name": "Account 2"
},
{
"id": "5da454f4307b0a8b3083883b",
"name": "Account 3"
}
]
Issue
The problem comes when integrating Swagger into the project, the documentation shows that calling this method returns a complex JSON instead of a plain String as the id property:
Adding #ApiModelProperty(dataType = "string") to the id field made no difference, and I can't find a way to solve it without changing all the id fields in the project to String. Any help would be appreciated.
I couldn't get #ApiModelProperty(dataType = "") to work, but I found a more convenient way configuring a direct substitute in the Swagger configuration using directModelSubstitute method of the Docket instance in this response.
#Configuration
#EnableSwagger2
class SwaggerConfig() {
#Bean
fun api(): Docket {
return Docket(DocumentationType.SWAGGER_2)
.directModelSubstitute(ObjectId::class.java, String::class.java)
}
}
Java equivalent:
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.directModelSubstitute(ObjectId.class, String.class);
}
For OpenApi (Swagger 3.0) and SpringDoc the following global configuration could be used.
static {
SpringDocUtils.getConfig().replaceWithSchema(ObjectId.class, new StringSchema());
}

error when using my own input with swagger 2 and spring boot 2

I'm new to spring boot and trying to set up a swagger documentation for a web app I'm working on. It works quite well when my endpoints only require strings, list or other basic request bodies but whenever I use custom inputs I get this error : could not resolve reference because of could not resolve pointer does not exist in document
the error
I created a small project with just one end point to search more easily. Those are my swagger dependencies :
compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'
compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'
Here is the swagger config :
#Configuration
#EnableSwagger2
class SwaggerConfig {
#Bean
Docket productApi() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("io.example.bss.testswagger"))
.paths(any())
.build()
.apiInfo(metaInfo())
}
private ApiInfo metaInfo() {
ApiInfo apiInfo = new ApiInfo(
"API",
"A description",
"0.0.1",
"urn:tos",
new Contact("someone", "https://www.google.com",
"an.email#gmail.com"),
"",
"",
new ArrayList<VendorExtension>()
)
return apiInfo
}
}
Then there is the controller :
#Controller
class ControllerHello {
#PostMapping("/")
def hello(#RequestBody Profile profile){
return "hello $profile.name $profile.surname, I see you're $profile.age years old"
}
}
And finally,the DTO :
#ApiModel(value = "the profile containing the name surname and age of a person")
#Document
class Profile {
#ApiModelProperty(notes = "the name of the person", required = true)
String name
#ApiModelProperty(notes = "the surname of the person", required = true)
String surname
#ApiModelProperty(notes = "the age of the person", required = true)
int age
}
In a similar post someone was advised to use alternateTypeRules in Docket, but I'm not sure it would work for my issue and I don't know how to set it up.
It turns out that swagger-ui was having trouble because my Profile class was written in groovy and it was taking the metaclass into account when displaying the model.
The problem was solved by adding this line .ignoredParameterTypes(groovy.lang.MetaClass.class) like so :
#Bean
Docket productApi() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("io.example.bss.testswagger"))
.paths(any())
.build()
.apiInfo(metaInfo())
.ignoredParameterTypes(groovy.lang.MetaClass.class)
}
This is my source: https://github.com/springfox/springfox/issues/752

Resources