I'm working on a Spring Boot application. I have the following REST endpoint(s):
#GetMapping(value = { "/person/{name}", "/person/{age}" })
public PersonData getPersonData(#PathVariable(required = false) String name,
#PathVariable(required = false) Integer age) {
}
This endpoint can be called with either a name variable or an age variable, however it looks like it can't differentiate between them. If I was to call '.../person/20', it would not call "/person/{age}", but it always calls "/person/{name}".
I know I could make something like:
#GetMapping(value = { "/person/name/{name}", "/person/age/{age}" })
However are there any other way to solve it without adding anything to the path?
A path variable is something like a primary key usually.
Like:
/person/{id}
What you try is to search for data and this should be done with query parameters.
Example:
#GetMapping(value = { "/person/{name}", "/person/{age}" })
public PersonData getPersonData(#RequestParam String name,
#RequestParam Integer age) {
}
Then you can call it
/person?age=40
/person?name=Peter
age and name are logically not the same thing; the chosen best answer correctly suggests to keep them as distinguished parameters.
However you can check if the value is numeric and treat it like an age,
#GetMapping("/person/{value}")
public String getPerson(#PathVariable String value) {
if (value.matches("[0-9]|[0-9][0-9]"))
return "Age";
else
return "Name";
}
but this is ambiguous and error prone (e.g. how'll you distinguish when adding other numerical params like shoeSize or numberOfPartners?).
In your case I would make 2 different endpoints to be more clear, each one requiring it's own query parameter to be served.
#GetMapping(value = "/person")
public PersonData getPersonDataByName(#RequestParam(required = true) String name) {
....
}
#GetMapping(value = "/person")
public PersonData getPersonDataByAge(#RequestParam(required = true) Integer age) {
....
}
required = true can be omitted from the annotation as this is the default value, I used it just to point that each endpoint will be fulfilled only for that specific query parameter
Related
I have the following interface:
public interface TestRequestView {
String getCountryCode();
String getRegionCode();
}
It's used in several end points like so:
#PostMapping("/my/path/{id}")
public String test(#RequestBody TestRequestView request, #PathVariable String id) {
...
}
I would like to add a property to the TestRequestView that is only used in one new endpoint without breaking the rest, how can I mark that one property as ignorable? Something like:
public interface TestRequestView {
String getCountryCode();
String getRegionCode();
String getEmail(); // make this not required
}
Usually it is better to use 1 such a model per endpoint so they are independent. If you share models between endpoints this should be useful
This may help
You can use the #JsonIgnoreProperties annotation
Example:
#JsonIgnoreProperties(value = {"email"})
public interface TestRequestView {
String getCountryCode();
String getRegionCode();
#JsonProperty(required = false)
String getEmail();
}
Add annotation #JsonInclude(Include.NON_NULL) at class level for your TestRequestView. When passing your param you can pass it without that value and in your controller it will be received with that param as null. You just need to make sure that your controller can handle such case
Is there a way to custom validate 2 of request parameters coming into endpoint in Spring? I would like to be able to validate them with my custom function. Something like add annotation to the request params or on the function where these params are and force these params to be validated by another custom written function.
I need to take both params at the same time, because the validation output of one is dependent on the value of the other one.
I have searched and found some solutions with custom constraint annotations but from what I've read it doesn't seem to solve my problem.
As rightly mentioned, using valiktor is the best option. I have used it in our product as well and it works like a charm.
Below is a snippet example as how you are use it to compare two properties of the same class.
fun isValid(myObj: Myobj): Boolean {
validate(myObj) {
validate(MyObj::prop1).isGreaterThanOrEqualTo(myobj.prop2)
}
Valiktor throws exception with proper message if the validation fails. It also enables you to create custom exception messages if you want to.
Now all you need to do is, create a class for your requestBody and check your conditions with isValid() method explicitly or move it into init block and do it implicitly.
Valiktor has a large number of validations as compared to JSR380, where creating custom validation is a little messy as compared to Valiktor.
If you're going to use the request params to create a POJO, then you can simply use the Javax Validation API.
public class User {
private static final long serialVersionUID = 1167460040423268808L;
#NotBlank(message = "ID cannot be to empty/null")
private int id;
#NotBlank(message = "Group ID cannot be to empty/null")
private String role;
#NotBlank(message = "Email cannot be to empty/null")
private String email;
#NotNull(message = "Password cannot be to null")
private String password;
}
To validate -
#PostMapping("/new")
public String save(#ModelAttribute #Validated User user, BindingResult bindingResult, ModelMap modelMap) throws UnknownHostException {
if (!bindingResult.hasErrors()) {
// Proceed with business logic
} else {
Set<ConstraintViolation<User>> violations = validator.validate(user);
List<String> messages = new ArrayList<>();
if (!violations.isEmpty()) {
violations.stream().forEach(staffConstraintViolation -> messages.add(staffConstraintViolation.getMessageTemplate()));
modelMap.addAttribute("errors", messages);
Collections.sort(messages);
}
return "new~user";
}
}
You can write custom validator by using Validator
Check :: https://docs.spring.io/spring-framework/docs/3.0.0.RC3/reference/html/ch05s02.html
Example :: https://www.baeldung.com/spring-data-rest-validators
valiktor is really good library to validate.
You can do somenthing like:
data class ValidatorClass(val field1: Int, val field2: Int) {
init {
validate(this) {
validate(ValidatorClass::field1).isPositive()
validate(ValidatorClass::field2).isGreaterThan(field1)
}
}
}
make request parameter not required:
#RequestMapping(path = ["/path"])
fun fooEndPoint(#RequestParam("field1", required = false) field1: Int,
#RequestParam("field2", required = false) field2: Int) {
ValidatorClass(field1, field2) //it will throw an exception if validation fail
}
You can handle exception using try-catch or using and ExceptionHandler defined by valiktor.
Using valiktor you can validate fields depending on other fields. You can create one kotlin file where you write all classes that you use to validate fields from requests and in the same way you can use valiktor in you #RequestBody models to validate it.
what I am trying to do is,
If I take one pojo class like
#Entity
#Table(name = "property_table")
public class Property {
#Id
#Column(name = "property_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int propertyId;
#Column(name = "property_name")
private String propertyName;
#Column(name = "property_type")
private String propertyType;
}
In RestController I wrote Two Methods like
#GetMapping(value = "/getProperties", produces = { "application/json",
"application/xml" }, consumes = { "application/xml", "application/json" })
#ResponseBody
public List<Property> getProperties() {
//some code
}
#GetMapping(value = "/getPropertyById", produces = { "application/json",
"application/xml" }, consumes = { "application/xml", "application/json" })
#ResponseBody
public Property getPropertyById() {
//some code
}
So, hear what I am trying to do is
for first api method I want return json like some parameters from Property pojo class i.e., like
for getProperties api method
{
"property":[
{
"propertyId":001,
"propertyName":"PROPERTY 1"
},
{
"propertyId":002,
"propertyName":"PROPERTY 2"
}
],
In the Above json I want to return only two parameters i.e propertyId,propertyName and remaining parameter i.e propertyType I dont want to retun in json.
How to return like that?
and for the second api method I want to return all three parameters. i.e., like below
for getPropertyById api method
{
"propertyId":001,
"propertyName":"PROPERTY 1",
"propertyType:"PROPERTY_TYPE 1"
},
how to maintain different json response using same pojo class with different parameters for different api methods.
please help me to solve this isuue.
Thanks.
REST API under/over-fetching is a well-known problem. There's only two (classical ways) to handle that.
The first one is to build one model per each attribute visibility state. So, in your case, you'll need to create two different models (this kind of models are called DTO - Data Transfert Object). One model will have a propertyType attribute, the other will not. The model Property you've shared shows that you use the same class as entity and as transfert object. This solution will add some complexity to your app because you will have to implement some mappers to convert your entity to a corresponding DTO.
The second one is to accept that you send an attribute that will not be useful (be aware of the over-fetching). This solution is often the most adopted one. The cons of this solution is when you don't want to send something to your client (imagine a User model, you want to get the password from your client but you don't want to sent it back to it). Another obvious negative point is that the transactions will be larger but it is negligible in most cases
I would strongly advice you to keep your #Entity isolated in the 'db' layer. So that changes on the database side don't affect your API and vice versa. Also, you will have much better control over what data is exposed in your API. For your needs you can create 2 true DTOs, like PropertyDto and PropertyDetailsDto (or using private fields and getters/setters).
public class PropertyDto {
public String propertyId;
public String propertyName;
}
public class PropertyDetailsDto extends PropertyDto {
public String propertyType;
}
Map your #Entity to a specific dto corresponding to your needs.
EDIT
public List<PropertyDto> getProperties() {
return toPropertyDtos(repository.findAll());
}
public PropertyDetailsDto getPropertyById(Long id) {
return toPropertyDetailsDto(repository.findBy(id));
}
in some Mapper.java
...
public static List<PropertyDto> toPropertyDtos(List<Property> properties) {
return properties.stream()
.map(Mapper::toPropertyDto)
.collect(toList());
}
private static PropertyDto toPropertyDto(Property property) {
PropertyDto dto = new PropertyDto();
dto.propertyId = property.propertyId;
dto.propertyName = property.propertyName;
return dto;
}
// same stuff for `toPropertyDetailsDto`, you could extract common mapping parts in a separate private method inside `Mapper`
...
I am trying to do a simple POST request but for some reason my Integer parameter is null. This is something so basic, but I don't see what I am doing wrong here.
Here is what I have tried so far:
$rootScope.addUser = function(userId) {
$http.post('/addUser', {
params: {
user_id: userId
}
}).then(function(result) {
$rootScope.userId = undefined;
});
};
Controller
#PostMapping("/addUser")
public void addTournament(#RequestParam(required = false) final Integer userId) {
LOG.info("addUser: {}" , userId);
}
I have also tried doing #RequestParam(name = "user_id") final Integer userId, but even that does not work either!
In the end I will removed the 'required = false' parameter, but I left it there for now just to verify that the userId is indeed null.
The input is being grabbed from the user they input a number and click a button.
The #RequestParam name does not match with the name of the attribute posted: user_id vs userId. Use #RequestParam(name = "user_id") final Integer userId to match them, or use the same request attribute and #RequestParam name.
[edit]
When I read params: { ... } I expected them to be additional request parameters instead of being part of the POST body. Could you try POSTing the data using:
$http.post('/addUser', { userId: userId }
And using the following #PostMapping:
#PostMapping("/addUser")
public void addTournament(#RequestBody User user) {
LOG.info("addUser: {}" , user.getUserId());
}
...
class User {
private String userId;
// getter and setter
}
Is it possible to set possible values for #RequestParam(value = "id") String id)
?
The ideal is: I give some List<String> allowedValues and it automatically validate it. (This list will be loaded from database).
use an enum instead of a String and define your enum values
#RequestMapping(value = "yourRestEndPoint", method = RequestMethod.GET)
public anyReturnType methodName(#RequestParam(defaultValue = "ONE") IdValue id) {
....
}
and have a separate class defined e.g
public enum IdValue {
ONE, TWO, THREE
}
It is possible but not some magical way but by simple Validation checks.
Eg:
#RequestMapping(value = "yourRestEndPoint", method = RequestMethod.GET)
public anyReturnType methodName(#RequestParam(value = "id") String id){
List<String> allowedValues = getAllowedValuesFromDB();
if(allowedValues.contains(id)){//check if incoming ID belongs to Allowed Values
//Do your action....
}
}
private List<String> getAllowedValuesFromDB(){
List<String> allowedValues = new ArrayList<>();
//fetch list from DB
//set fetched values to above List
return allowedValues;
}
Second Way
If you want to do it like we do Bean Validation, using #Valid, which is for Bean not just a single parameter and also required Validator to be configured, then check out this & this answer.
Hope this helps.