how to return a specific status code in Kotlin - spring-boot

I've set up a route that when I get a name in my post body I will search the DB and return an ID value.
What I want to do is once there is no ID present in the DB return a 204 status code.
But should that be handled in the service or in my controller?
and
How do I return my specific status code?
#ResponseStatus(HttpStatus.OK)
#PostMapping("/ID_values/")
fun getID(
#RequestBody
name: String
): ResponseEntity<String> = ResponseEntity.ok(IDLookupService.lookupIDValue(name))
}
#Service
class EmailLookupService(
private val IDRepo: IDRepo
) : Logging {
fun lookupIDValue(name: String): String {
val IDLookupResult = IDRepo.findById(name)
return if (IDLookupResult.isPresent) {
IDLookupResult.get().ID.toString()
} else {
// return status code 204
}
}
}

First, you should omit the #ResponseStatus(HttpStatus.OK) annotation if you do not wish to always return a status code of 200. Using that annotation, it would suffice to only specify the response body as return value (i.e specify return type String and then return only result in your example), and Spring would automatically wrap that into a response entity with HTTP-status OK.
Second, you need some way to tell the caller of IDLookupService.lookupIDValue (which should probably be called on an instance of IDLookupService and not the class itself) that there was nothing found. This could be done for instance by changing the return type to String? and return null if nothing was found.
Then you can change getID to return
val result = idLookupService.lookupIDValue(name)
return if(result != null) ResponseEntity.ok(result)
else ResponseEntity("not found", HttpStatus.NO_CONTENT)
If you wish to return something different than a String in the case there was nothing found (like an error object with detailed information; in the example here it is simply the text "not found"), you can change the response type of getID to ResponseEntity<*>.

Related

Can #PathVariable recieve variable defined own orignal Data type?

I made controller method.
I want the method to receive variable defined by own original Data type.
like Below,
data class UserId(
val value: UUID
)
#GetMapping("user/{userId}")
fun getUser(
#PathVariable userId: UserId
) {
userService.getUser(userId)
}
Of course, I know how to receive variable of String.
#GetMapping("user/{userId}")
fun getUser(
#PathVariable userId: String
) {
// I think this code is redundancy.
val id = UserId.fromString(userId)
userService.getUser(userId)
}
Can I receive variable defined own original Data Type?
Do you know any idea?
The main question is, how do you see this working? Would you receive the data class as a serializable JSON object? If so, shouldn't that be inputted as the request body?
If there's another way you envision this working, you can always manually serialize the object later, something like:
Controller:
#GetMapping("user/{userId}")
fun getUser(
#PathVariable userIdSerialized: String
) {
userService.getUser(userIdSerialized)
}
Service:
fun getUser(userIdSerialized: String) {
// Using Jackson
val deserialized: UserId = mapper.readValueFromString(userIdSerialized, UserId::class.java)
}
But again, this should really be a request body.

Spring , update value if not null

I am working with Redmine API, and want to update an issue.
The issue has 30 variables, Subject, Description, Author, and Assignee ...
I have no problem updating the issue Subject and Description like this:
#RequestMapping(value = "/issues/update/{id}", method = RequestMethod.PUT)
public ResponseEntity<Object> issueUpdate(#RequestBody ObjectNode json, #PathVariable int id) throws RedmineException, RuntimeException, IllegalArgumentException {
String apiKey = json.get("API_KEY").asText();
RedmineManager mgr = RedmineManagerFactory.createWithApiKey("http://localhost:3001/", apiKey);
IssueManager issueManager = mgr.getIssueManager();
Issue issue = issueManager.getIssueById(id);
issue.setSubject(json.get("SUBJECT").asText())
.setDescription(json.get("DESCRIPTION").asText())
.update();
return new ResponseEntity<>(HttpStatus.OK);
}
The problem with this way is I am only allowed to change these 2 values and I have to include, SUBJECT and DESCRIPTION in my JSON request body.
If SUBJECT is not in JSON, then it will be considered as null and I get NullPointerException.
I want to make something more flexible and elegant, to allow the change of each value, and if not exist, don't set to null but keep the old values.
something logical to this but a bit smarter
if json.get("SUBJECT").asText() != null {
issue.setSubject(json.get("SUBJECT").asText()) //set if mentioned
} else {
issue.setSubject(issue.getSubject()) //set the old value
}
The idea is, now am available to have setSubject and all the other available setters in case it's mentioned in my JSON request or not.

Spring cache for specific values #Cacheable annotation

I want to cache a result of a method only when the attribute of the result contains specific values. For example
Class APIOutput(code: Int, message: String)
sealed class Response<out T : Any> : Serializable {
data class Success<out T : Any>(val data: T) : Response<T>()
data class Error(val errorText: String, val errorCode: Int) : Response<Nothing>()
}
#Cacheable(
key = "api-key",
unless = "do something here"
)
fun doApicall(uniqueId: Long): Response<APIOutput> {
//make API call
val output = callAPI(uniqueId)
return Response.Success(output)
}
In the above method, I want to cache the response only when Response.Success.data.code == (long list of codes).
Please note, in the previous line data is nothing but APIOutput object. How could I achieve it using unless or any other approach. I was thinking of writing a function that takes a doApicall method result as input and would return true or false and call that method it as unless="call a method". But I'm not sure how to do it. Any help is highly appreciated.
You can specify an expression to be evaluated in unless using SpEL. The returned value is available as result so you can do something like -
#Cacheable(
key = "api-key",
unless = "#result!=null or #result.success.data.code!=200"
)
fun doApicall(uniqueId: Long): Response<APIOutput> {
//make API call
val output = callAPI(uniqueId)
return Response.Success(output)
}
You can even use Regex in SpEL and can create custom Expression parsers if the existing functionality is not enough for your usecase.
Thanks Yatharth and John! Below is the condition that worked for me. resultcodes in the below expression is a list
#Cacheable(
key = "api-key",
unless = "!(#result instanceof T(com.abc.Response\$Success))
or (#result instanceof T(com.abc.Response\$Success)
and !(T(com.abc.APIStatus).resultCodes.contains(#result.data.code)))"
)
fun doApicall(uniqueId: Long): Response<APIOutput> {
//make API call
val output = callAPI(uniqueId)
return Response.Success(output)
}

Spring boot: mockMvc testing controller: Expected: Entity, Actuall: null

How looks my json:
event object
{
...
"game": {
//game fields
}
}
...
}
I am trying to do:
event.setGame(new Game());
And check if there is my value by mockMvc
.andExpect(jsonPath("$.game").value(event.getGame()))
But i am getting error:
java.lang.AssertionError: JSON path "$.game"
Expected : Event(id= null, name= null, ...)
Actual :null
Why i am getting null, if i should get just empty game?
P.S. even if i set fields to game, i will get null
I make .andDo(print), and get :
Body = // event
{
"id":"5f087eec-8bf0-11eb-8dcd-0242ac130003",
"game":
{
"id":null,
"team1":null,
"team2":null,
"gameDate":null,
},
"user":
{
//user fields
}
}
How looks controller:
#GetMapping("/{id}")
public ResponseEntity<GetEventById> getEventById #PathVariable("id") String id) {
GetEventByIResponse dto= service.getEventById(id);
return ResponseEntity
.status(HttpStatus.OK)
.body(dto);
}
In my test i am creating GetEventByIResponse, how it looks:
public class Event {
private String id;
private Game game;
...
}
The JsonPath assert works as follows:
It first parses the path result into a Map/List/Object, to see if the origin was an array/object/simple type.
Then, if it's a Map (like in your case) it tries to parse the path result into the same type as the expected object.
Finally it compare the created object to the expected object using equals().
In your case I see several problems:
The AssertionError talks about an Event although you seem to hand in a Game.
We do not know if the serialization/deserialization works at all
Maybe you should try one of the following:
Place a breakpoint right here to watch the assert steps in your debugger:
Start with simple type comparisons:
.andExpect(jsonPath("$.id").value("foo"))
.andExpect(jsonPath("$.game.id").value("bar"))
Seems like the json path referencing the game child object is incorrect, try below:
.andExpect(jsonPath("$.event.game").value(event.getGame()))

if/else doese not work in my POST Method of SpringBoot rest api

I use Spring Boot rest api with MongoDB.
In the POST Method, if there is not scoreID and there is not a player with specific date in my collection, because at the same time a player cannot play different games and bring score, then if the specific player and gamecode exist, create a score.
In fact, in the POST Method I used Nested IF-ELSE conditions.
But, in the Postman when I execute POST Request with this data:
{
"scoreid":"s11",
"score":1000,
"player":"sahari",
"gamecode":"g12",
"date":"2020-01-01"
}
always, I recieve an error, in the Postman, 400 Bad Request!, which i defined in the last line of my IF-ELSE statements.
I do not know, what is my mistake and why my program doese not execute IF conditions correct.
The POST Method:
//Create Score
#PostMapping
public ResponseEntity<?> createScore(#RequestBody #JsonView(Views.class) #Valid Score score) {
String p = srepo.findByPlayerName(score.getPlayer());
String g = srepo.findByGameCode(score.getGamecode());
String scoreid = srepo.findByScoreid(score.getScoreid());
Query query = new Query();
query.addCriteria(new Criteria().andOperator(Criteria.where("player").is(score.getPlayer()),
Criteria.where("date").is(score.getDate())));
if((scoreid != null)) {
return ResponseEntity.status(409).body("Conflict!"); }
else
if(mongoTemplate.exists(query, Score.class))
return ResponseEntity.status(409).body("There is not Possible at same time one player brings different Scores!");
else
if((p!= null)&&(g!= null))
{
history = new ArrayList<History>();
h = new History();
h.setScore(score.getScore());
h.setDate(score.getDate());
history.add(h);
hrepo.save(h);
score.setHistory(history);
srepo.insert(score);
return ResponseEntity.ok(score);
}
else
{
return ResponseEntity.status(400).body("Bad Request!");
}
}
The Score Repository:
#Repository
public interface ScoreRepository extends MongoRepository<Score, String>{
#Query("{'scoreid':?0}")
public String findByScoreid(String scoreid);
#Query("{'Player.nickname':?0}")
public String findByPlayerName(String player);
#Query("{'Games.code':?0}")
public String findByGameCode(String game);
}
The problem is not for my IF-ELSE statements.The problem is in the Score Repository
I must return a List instead of String for findByPlayerName and findByGameCode and for findByScoreid which is for checking duplicate in the POST Method I must return type of Score

Resources