I have a controller action to serve my react front-end. It requires the validation messages in the special format:
#Transactional
#Post( uri = '{/id}', consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON )
HttpResponse save( #PathVariable #Nullable Long id, #Body Map body ){
def o = bindFromIdAndBody id, body
if( o.save( flush:true ) ){
log.info "version >> $o.version"
HttpResponse.ok o
}else{
log.info '-------------------------'
List errors = o.errors.fieldErrors.collect{ FieldError fe ->
fe.codes.findResult{ String c ->
messageSource.getMessage c, fe.arguments, null, Locale.default
} ?: fe.codes.last()
}
log.info "save failed for $o: $errors"
HttpResponse.badRequest( errors:errors )
}
}
When I call the action, I'm getting 400 Bad Request in my client, but instead of { errors:[ {..}, {..}, {..} ] style JSON, I see rather:
{
"message":"Validation Error(s) occurred during save() : Field error in object ... default message [Property [{0}] of class [{1}] cannot be blank]\r\n",
"path":"fullName",
"_links":{"self":{"href":"/person/42","templated":false}}
}
Also the else{} block is never reached, I don't get any further logs.
Any hints?
It appears, that in GORM configuration for Micronaut done via
compile 'io.micronaut.configuration:micronaut-hibernate-gorm'
the failOnError is set to true by default. That led to ValidationException being thrown on save() instead of populating o.errors.
To fix the issue I added the line
grails.gorm.failOnError: false
to my application.yml and now it's working like charm.
Related
I'm trying to test a GET to get all the StatusMapping objects created, however, I'm not sure what's the best approach to test this.
The response is returning a map whereas I was expecting a list of StatusMapping objects instead.
Should I convert the requests to a map?
Here's the Service code:
fun getAll(): ResponseEntity<List<StatusMapping>> {
return ResponseEntity<List<StatusMapping>>(statusMappingRepository.findAll(), HttpStatus.OK)
}
Here's the test
#Test
fun `Get all mappings created`() {
val requests = listOf(
StatusMapping("available", "available"),
StatusMapping("unavailable", "unavailable")
)
requests.forEach { statusMappingService.createMapping(it.toStatusMappingRequest()) }
val response = restTemplate.getForEntity(getRootUrl(), List::class.java)
assertEquals(response.body, requests)
}
Here's the error that I'm getting:
Expected :[{source=available, target=available}, {source=unavailable, target=unavailable}]
Actual :[StatusMapping(source=available, target=available), StatusMapping(source=unavailable, target=unavailable)]
Please start with replacing
val response = restTemplate.getForEntity(getRootUrl(), List::class.java)
with
val response = restTemplate.exchange(
getRootUrl(),
HttpMethod.GET,
null,
object : ParameterizedTypeReference<List<StatusMapping>>() {})
Assuming that restTemplate is instance of TestRestTemplate
I have a project set up using Spring Boot with Kotlin to make REST APIs.
I'm trying to use the #RequestHeader to recognize the User-Agent. The said header is required=true -
#PostMapping("details", produces = ["application/json"])
fun addInfo(#RequestHeader(name = "User-Agent", required = true) userAgent: String,
#Valid #RequestBody podEntity: PodEntity): ResponseEntity<String> {
pod.addPod(podcastEntity)
return ResponseEntity<String>("{ \"status\":\"Added\" }", HttpStatus.CREATED)
}
Problems -
However, even if I'm not sending the User-Agent header, the API is working and adding data. But, if I change the header to any other non default names, like, user or request-source, the required=true requirement is enforced to and the API does not work. Does it mean that default headers cannot be made mandatory using the required tag?
The other problem is that in the case of custom header, when the required header is missing for the request, the API fails by giving 400 error code but does not throw any exception. I was expecting HttpClientErrorException for my junit test case but on checking the console, I see no exception. Adding #Throws is also not helping. How do enable my function to throw an exception when the required header is missing?
Unit test -
#Test
fun test_getAll_fail_missingHeaders() {
val url = getRootUrl() + "/details/all"
val headers = HttpHeaders()
val request = HttpEntity(pod, headers)
try {
restTemplate!!.postForEntity(url, request, String::class.java)
fail()
} catch (ex: HttpClientErrorException) {
assertEquals(400, ex.rawStatusCode);
assertEquals(true, ex.responseBodyAsString.contains("Missing request header"))
}
}
After upgrading grails 2.5.2 to 3.3.11, I am getting Method Not Allowed response while hitting POST request. The GET method works fine.
Controller:
package omapi
import grails.rest.RestfulController
import org.springframework.http.HttpStatus
import static org.springframework.http.HttpStatus.NOT_FOUND
import static org.springframework.http.HttpStatus.NO_CONTENT
class PhaseController extends RestfulController {
PhaseController(){
super(Phase)
}
static responseFormats = ['json', 'xml']
static allowedMethods = [show: 'GET', index:'GET',productAcronym:'GET', phaseByName: 'GET',save:'POST',update:'PUT',delete:'DELETE', deleteGroup: 'DELETE', deletePhase: 'DELETE']
def phaseService
def index(){
def phases = phaseService.getAllPhases()
respond phases
}
def show(){
def phase = phaseService.getPhaseById(params.id)
respond phase
}
def phaseByName(){
def phase = phaseService.getPhaseByName(params?.name)
respond phase
}
def productAcronym() {
def phase = phaseService.getPhaseByProductAcronym(params?.acronym)
log.info("==== phase by product Acronym =====$params.acronym==");
respond phase;
}
}
URL Mapping:
package omapi
import grails.core.GrailsApplication
import java.nio.file.AccessDeniedException
class UrlMappings {
static mappings = {
"/applications/$id/processingGroup"(controller: "application",action: "processingGroup")
"/accounts/$id/applications/"(controller: "accounts", action: "applications")
"/accounts/$aId/application/$id/products/"(controller: "application", action: "products")
"/ftpServer/connection"(controller: 'ftpServer', action: 'testConnection')
"/application/$id/jobs/"(controller: "job",action: "jobByApplication")
"/application/cycleDates"(controller: "cycleDates",action: "getCycleDatesByApplication")
"/ProcessingBranch/branch/"(controller: "processingTicketBranch",action: "branch")
"/application/$appCode/Offset"(controller: "ReportClientOffset",action: "offsetValue")
"/$controller/$action?/$id?(.$format)?"{
constraints {
// apply constraints here
}
}
"/$controller/$id"(parseRequest: true){
action = [GET:"show",PUT:"update",POST:"404",DELETE:"delete"]
constraints {
id matches: /\d+/
}
}
"/$controller"{
action = [GET:"index",POST: "save",PUT:"update",DELETE:"delete"]
constraints {
}
}
"/"(view:"/index")
"403"(controller: "error", action: "error403")
"404"(controller: "error", action: "error404")
"409"(controller: "error", action: "error409")
"500"(controller: "error", action: "error500")
"500"(controller: "error", action: "error403", exception: AccessDeniedException)
}
}
Request: [POST] localhost:5555/OMApi/phase
Response:
{
"timestamp": 1594295030496,
"status": 405,
"error": "Method Not Allowed",
"message": "No message available",
"path": "/OMApi/phase"
}
For grails 2.5.2, everything works fine. It looks like a Spring related issue. All the searches on this matter provided no results. Any idea? Is it due to some error in UrlMapping or other problems like CORS Interceptor not working?
After upgrading grails 2.5.2 to 3.3.11, I am getting Method Not
Allowed response while hitting POST request. The GET method works
fine.
It looks like you have /OMApi/phase mapped to the index action in PhaseController which is configured with index: 'GET' in allowedMethods which means the index action is only accessible via a 'GET' request. Any other verb should result in a 405 for that action. If you want to allow both GET and POST for the index action (unclear why you want to do that) then change your allowedMethods to include index: ['GET', 'POST'] instead of index: 'GET'.
I hope that helps.
I am trying to geocode addresses with HERE API. I am not free plan. I try following code (Spring Boot in Kotlin):
override fun geocode(address: Address): Coordinate? {
val uriString = UriComponentsBuilder
.fromHttpUrl(endpoint)
.queryParam("app_id", appId)
.queryParam("app_code", appCode)
.queryParam("searchtext", addressToSearchText(address))
.toUriString()
logger.info("Geocode requested with url {}", uriString)
val response = restTemplate.getForEntity(uriString, String::class.java)
return response.body?.let {
Klaxon().parse<GeocodeResponse>(it)
}?.let {
it.Response.View.firstOrNull()?.Result?.firstOrNull()
}?.let {
Coordinate(
latitude = it.Location.DisplayPosition.Latitude,
longitude = it.Location.DisplayPosition.Longitude
)
}.also {
if (it == null) {
logger.warn("Geocode failed: {}", response.body)
}
}
}
It turned out that when I call this method many times in a row, some requests returns empty responses, like this:
{
"Response":{
"MetaInfo":{
"Timestamp":"2019-04-18T11:33:17.756+0000"
},
"View":[
]
}
}
I could not figure out any rule why some requests fail. It seems to be just random.
However, when I try to call same URLs with curl of in my browser, everything works just fine.
I guess there is some limit for amount requests per seconds, but I could not find anything in HERE documentation.
Does anyone have an idea about the limit? Or may it be something else?
Actually, there was a problem with my code. Requests were failing for addresses having "special" symbols like ü and ö. The problem was with building request URL
val uriString = UriComponentsBuilder
.fromHttpUrl(endpoint)
.queryParam("app_id", appId)
.queryParam("app_code", appCode)
.queryParam("searchtext", addressQueryParam(address))
.build(false) // <= this was missed
.toUriString()
I am new to spring 5.
1) How I can log the method params which are Mono and flux type without blocking them?
2) How to map Models at API layer to Business object at service layer using Map-struct?
Edit 1:
I have this imperative code which I am trying to convert into a reactive code. It has compilation issue at the moment due to introduction of Mono in the argument.
public Mono<UserContactsBO> getUserContacts(Mono<LoginBO> loginBOMono)
{
LOGGER.info("Get contact info for login: {}, and client: {}", loginId, clientId);
if (StringUtils.isAllEmpty(loginId, clientId)) {
LOGGER.error(ErrorCodes.LOGIN_ID_CLIENT_ID_NULL.getDescription());
throw new ServiceValidationException(
ErrorCodes.LOGIN_ID_CLIENT_ID_NULL.getErrorCode(),
ErrorCodes.LOGIN_ID_CLIENT_ID_NULL.getDescription());
}
if (!loginId.equals(clientId)) {
if (authorizationFeignClient.validateManagerClientAccess(new LoginDTO(loginId, clientId))) {
loginId = clientId;
} else {
LOGGER.error(ErrorCodes.LOGIN_ID_VALIDATION_ERROR.getDescription());
throw new AuthorizationException(
ErrorCodes.LOGIN_ID_VALIDATION_ERROR.getErrorCode(),
ErrorCodes.LOGIN_ID_VALIDATION_ERROR.getDescription());
}
}
UserContactDetailEntity userContactDetail = userContactRepository.findByLoginId(loginId);
LOGGER.debug("contact info returned from DB{}", userContactDetail);
//mapstruct to map entity to BO
return contactMapper.userEntityToUserContactBo(userContactDetail);
}
You can try like this.
If you want to add logs you may use .map and add logs there. if filters are not passed it will return empty you can get it with swichifempty
loginBOMono.filter(loginBO -> !StringUtils.isAllEmpty(loginId, clientId))
.filter(loginBOMono1 -> loginBOMono.loginId.equals(clientId))
.filter(loginBOMono1 -> authorizationFeignClient.validateManagerClientAccess(new LoginDTO(loginId, clientId)))
.map(loginBOMono1 -> {
loginBOMono1.loginId = clientId;
return loginBOMono1;
})
.flatMap(o -> {
return userContactRepository.findByLoginId(o.loginId);
})