Avoiding Ambiguous handler methods mapped for HTTP path With Regex - java

I have to similar controllers ONe get by Id and the other get By name
both using PathVariables I read an article where it explained and how to resolve by using regex but it seems not to work properly
#GetMapping(value = "/trucks/{truckId:[0-9]+}")
#ResponseStatus(HttpStatus.FOUND)
#ResponseBody
public final TruckDto getTruckId(#PathVariable(value = "truckId")
final String truckId) {
LOGGER.debug("test: truckId({})", truckId);
Truck truck = truckService.getTruckById(Integer.parseInt(truckId));
return mappingService.map(truck, TruckDto.class);
}
/**
* #return Truck with an average.
*/
#ResponseStatus(HttpStatus.FOUND)
#ResponseBody
#GetMapping(value = "/trucks/{truckCode:[a-zA-Z0-9]+}")
public final TruckWithAvgPetrolDto getTruckByTruckCode(#PathVariable(value = "truckCode")
final String truckCode) {
LOGGER.debug("test: getTruckByTruckCode({})", truckCode);
TruckWithAvgDto truck = truckService.getTruckByTruckCode(truckCode);
return mappingService.map(truck, TruckWithAvgPetrolDto.class);
}
WIthout putting regex both of fail my test but after putting regex the method getTruckByTruckCode that accepts numbers and letters pass but get by truckId still gives the error ambigous handler method , Am i missing something or why isnt it working

Related

unable to mock a constructor with Mockito

Using Mockito version 4.8.0
The controller method I need to test
#GetMapping(value = "getStringBuiltByComplexProcess")
public String getStringBuiltByComplexProcess(#RequestParam String firstName, #RequestParam String lastName ) {
Author a = new Author();
return a.methodWhichMakesNetworkAndDatabaseCalls(firstName, lastName);
}
here is the test method
#Test
public void testGetStringBuiltByComplexProcess01() {
final String firstName = "firstName";
final String lastName = "lastName";
try (MockedConstruction<Author> mock = mockConstruction(Author.class)) {
Author authorMock = new Author();
when(authorMock.methodWhichMakesNetworkAndDatabaseCalls(eq(firstName), eq(lastName))).thenReturn("when worked");
assertEquals("when worked", ut.getStringBuiltByComplexProcess(firstName, lastName), "Strings should match");
verify(authorMock).methodWhichMakesNetworkAndDatabaseCalls(eq(firstName), eq(lastName));
}
}
fails with a message of
org.opentest4j.AssertionFailedError: strings should match ==> expected: <when worked> but was: <null>
In this simplified example the controller method has more code but the core of what is not working is mocking the object which the controller method constructs.
The object you create on line
Author authorMock = new Author();
is different than the one created in the getBooksByAuthor() function. A debugger should show you that.
You can use mock.constructed().get(0) to get the object created in getBooksByAuthor(), but by the time you can do this, getBooksByAuthor() has already finished and you can't do much with that mock.
It's not exactly clear what your objective is. I guess you want to check that the Author object is created in a certain way, and the lines involving getFullName() aren't part of the actual code, just something you added to experiment, because they don't do anything.
If you want to verity that the object passed to dataAccessService satisfies some conditions, what you need is an ArgumentCaptor. Something like
ArgumentCaptor<Author> authorCaptor = ArgumentCaptor.forClass(Author.class);
when(dataAccessServiceMock.getBooks(authorCaptor.capture())).thenReturn(books);
List<Book> result = ut.getBooksByAuthor(firstName, lastName);
Author author = authorCaptor.value();
assertEquals(firstName, author.getFirstName());
If you use MockInitializer for stubbing , it should solve your problem :
try (MockedConstruction<Author> mocked = mockConstruction(Author.class, (mock, context) -> {
when(authorMock.methodWhichMakesNetworkAndDatabaseCalls(eq(firstName), eq(lastName))).thenReturn("when worked");
})) {
assertEquals("when worked", ut.getStringBuiltByComplexProcess(firstName, lastName), "Strings should match");
verify(authorMock).methodWhichMakesNetworkAndDatabaseCalls(eq(firstName), eq(lastName));
}
}
But a much better way to test the controller is to use MockMvc. It allows you to test for a given HTTP request , do you configure spring-mvc properly such that it can do the following things correctly :
if the HTTP request can be parsed properly to execute the expected controller method with the expected paramater
if it can deserialize the object returned from the controller method into a expected JSON structure.
it allows to configure the current user who make the HTTP call and verify if he has enough permission to call this API and if not, will it return the expected error response
etc.
All of these things cannot be tested by your fragile mocking constructor approach.
For more details about MockMvc, refer to this guide.

Evaluate FHIR Expression in Java against an object

I'm very new to the FHIR standard and I could use some help figuring out how to evaluate a ruleExpression against an object.
Here is my object:
#ResourceDef(name = "TestObj", profile = "http://hl7.org/fhir/StructureDefinition/TestObj")
#Data
public class TestObj extends DomainResource {
private static final long serialVersionUID = 1L;
#Child(name = "numMarbles")
#Extension(url = "http://hl7.org/fhir/CustomExtension/numMarbles", definedLocally = true, isModifier = false)
#Description(shortDefinition = "The number of marbles I have in my pocket")
private IntegerType numMarbles;
}
I'm trying to figure out how to run a rule evaluation on it. For example:
String ruleExp = "%numMarbles > 3"
In order to try options.. I've setup the following integration test:
#Test
void doRuleEval() throws Exception {
TestObj t = new TestObj();
t.setNumMarbles(4);
String ruleExp = "%numMarbles > '3'";
FhirPathR4 path = new FhirPathR4(fhirContext);
// ?????
Object something = path.evaluate(t, ruleExp, null);
// Line above always fails: "unknown fixed constant %numMarbles"
log.info("something: " + something.toString());
}
I've scoured the FHIR documentation and can't find java examples for how to evaluate dynamic rules against FHIR models. In the Javascript library we used the "compile" method but I can't find the equivalent Java method.
I feel like I'm missing something fundamental here.
The "evaluate" method is not documented - it takes in an "IBase" object which is any FHIR object from what I can tell, and returns some sort of list... who knows.
The FhirPathR4 object also contains a "parse" method that returns nothing and is also un-documented.
Thanks for any help or tips to point me in the right direction for evaluating a "ruleExpression" against an object's fields.
I would go onto the HAPI FHIR Google group as ask about the evaluate method there.
https://groups.google.com/g/hapi-fhir?pli=1

#RequestParam name includes square brackets []

I am writing a feign client to consume an endpoint of a PHP API.
I need to call an endpoint which is like :
www.myapp.com/number.php?number[]=1
My Feign Client looks like this:
#FeignClient(name = "testProxy", url = "${service.url}")
public interface NumberProxy {
#RequestMapping(value = INumber.URL, method = RequestMethod.GET)
public String getEvents(#RequestParam("numbers[]") Integer number);
}
The problems is number[].
If I see the feign log to check the GET URL, this is what I see.
GET [https://www.myapp.com/number.php?number[]={number[]}][1] HTTP/1.1
The number[] is not replaced by the actual value and that is what the API call is failing.
Is there a way to deal with this?
P.S.
I know that the PHP API should not have a query parameter like this, but it is what it is and I can not change that.
And I have also tried with List<Integer> for the number variable, but output is same.
Are we talking about a org.springframework.web.bind.annotation.RequestParam if so it shouldn't be the problem of square brackets. For me it works fine:
#RequestMapping(value = "/api/somepath", method = RequestMethod.POST)
public void uploadData(
#RequestPart("file") MultipartFile fileUpload, HttpServletRequest request,
HttpServletResponse response, #RequestParam(name = "param[]") Integer number) {
LOGGER.trace("paramValue=[{}]", number)
}
logs a value passed through a client
What if the problem is in parameter naming? In the first occurence you write numberS[] but further it named as number[]
You should name your parameter just as numbers without the brackets and change the type to a List:
#RequestMapping(value = INumber.URL, method = RequestMethod.GET)
public String getEvents(#RequestParam("numbers") List<Integer> number);

Is it possible to store pathparams as a list?

I have a Rest Service that I want to respond to requests with the following paths
1) /v1/config/type/service
2) /v1/config/type/service, service2
What I'd like is to be able to store the path param serviceName as a List where each element is delimited by a comma. For example, if someone types v1/config/foo/bar1,bar2,bar3 I'd like serviceName to be a List with 3 elements (bar1, bar2, bar3). Right now it just returns a list with 1 element that contains all three service strings. Is that even possible? Or is that something I'll simply have to parse. The code I have is shown below, it's pretty rough as I'm in the beginning stages of the project:
#ApplicationPath("/")
#Path("/v1/config")
public class ServiceRetriever extends Application {
#GET
#Produces(MediaType.TEXT_PLAIN)
public String getHelloWorld() {
return "Hello World";
}
#GET
#Path("{type}/{serviceName}")
#Produces("application/zip")
public Response getServices(#PathParam("type") String type, #PathParam("serviceName")List<String> serviceNames,
#QueryParam("with_config") boolean withConfig, #QueryParam("with_drive") boolean withDriver) throws IOException
{
//some random file i made to test that we can return a zip
File file = new File(System.getProperty("user.home")+"/dummy.zip");
System.out.println(serviceNames.size()); //returns 1
//we can change the zip file name to be whatever
return Response.ok(file).header("Content-Type","application/zip").
header("Content-Disposition", "attachment; filename="+file.getName()).build();
}
The problems is that you have to alter the deserialization process of that variable. Typically only query parameters are lists so this might not be compatible with some libraries.
You could:
Capture the parameter as a string and parse it internally via helper method (obvious)
Create your own annotation like #PathParamMutli and return Arrays.asList(parameter.split(","));. Ideally you should have access to the framework source code and branching privileges.
Use a query parameter instead

JAX-RS: Providing a "default" Sub-resource locator

I am using JAX-RS to develop a RESTful API. A simplified version of my API is as follows:
GET /appapi/v1.0/users
POST /appapi/v1.1/users
... ... and so on
As you can see, the pattern follows {api_name}/v{major_version}.{minor_version}/{rest_of_the_path}.
I have an additional requirement:
If the version is not specified, then the latest version should be used by default - i.e.,
GET /appapi/users should be equivalent to GET /appapi/v1.1/users (assuming 1.1 is the latest version of users).
This is how I have implemented this using JAX RS.
#Path("/appapi")
public class AppApiRootResource {
#Path("/{version: [v]\\d+[[.]\\d+]?}/")
public AppApiSubResource getVersionedSubResource
(#PathParam("version") String version){
System.out.println("Version: "+version);
String versionString = version.substring(1); //Discard the leading 'v'
String majorVersion = "";
String minorVersion = "0";
if(versionString.contains(".")){
String [] splits = versionString.split(".");
majorVersion = splits[0];
minorVersion = splits[1];
} else {
majorVersion = versionString;
}
return SubResourceFactory.getSubResource(majorVersion, minorVersion);
}
#Path("/{^([v]\\d+[[.]\\d+]?)}/") //Is This Correct??
public AppApiSubResource getDefaultApiResource(){
/*
* Need help defining the regular expression here
* so that this is used for anything that doesn't match
* the "version" regexp above
*/
System.out.println("API version not specified; Using default version");
return SubResourceFactory.getLatestVersionSubResource();
}
}
My Sub-Resource class is defined as follows. I have sub-classes of this class to deal with multiple versions of the API.
The implementation of SubResourceFactory is not relevant for the purposes of this discussion. It just returns an instance of AppApiSubResource or its sub-class
public class AppApiSubResource {
/**
* Create a new user
*/
#POST
#Path("users")
#Consumes(MediaType.APPLICATION_XML)
public Response createUser(String newUser, #Context UriInfo uriInfo) {
URI uri = uriInfo.getAbsolutePathBuilder().path("10")).build();
return Response.created(uri).build();
}
/**
* Get a user
*/
#GET
#Path("users/{id}")
#Produces(MediaType.APPLICATION_XML)
public Response getUser(#PathParam("id") String userId
) {
return Response.ok().entity("<user></user>").build();
}
}
Problem statement:
If I comment out getDefaultApiResource(), then things work as expected when there is a version specifier in the API. However, if I un-comment getDefaultApiResource(), it is always being invoked, irrespective of whether I have the v1.0 in the request or not.
Also, if I un-comment getDefaultApiResource(), I get a 404 when I do a GET /appapi/users (i.e, without the version specifier); but things work fine if I use GET /appapi/v1.0/users (i.e., with a version specifier)
So, how do I set up my sub-resource locator paths/regexps such that the method is invoked when there is no version specifier in the request?
I'm using Restlet framework, but this question is implementation-agnostic.
The reason getDefaultApiResource always gets invoked is that its URI pattern is the same regular expression as that of getVersionedSubResource, and when more than one pattern matches the request URI, the longest pattern (literally, the one with the most characters) wins. ("version: " is not considered part of the pattern.) See section 3.7.2 of the JAX-RS specification for all the details.
I've never tried this, but I think #Path("") will do what you want.
By the way, it appears your regular expression isn't quite right:
[v]\\d+[[.]\\d+]?
That says "lowercase v, followed by one or more digits, optionally followed by a single period, digit, or plus sign." It should be:
[v]\\d+([.]\\d+)?

Categories