Skip to content

Support for non-composite request parameter rendering on link creation #1575

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
fernion opened this issue Jul 12, 2021 · 7 comments
Closed
Assignees
Labels
in: core Core parts of the project type: enhancement
Milestone

Comments

@fernion
Copy link

fernion commented Jul 12, 2021

Using Spring HATEOAS does not seem to allow to match specs configured to explode arrays:

- name: make
  in: query
  schema:
	type: array
	items:
	  type: string
  style: form
  explode: false

I am generating the links as follows:

@GetMapping(value = "/cars")
public HttpEntity<?> getCars(@RequestParam(name = "make", required = false) List<String> make) {
	Link selfLink = linkTo(methodOn(InstrumentController.class).getCars(make)).withSelfRel();

	CollectionModel<Car> someCars = searchCars(make);
	someCars.add(selfLink);

	return new ResponseEntity<>(someCars, HttpStatus.OK);
}

Calling the endpoint with http://localhost:8080/cars?make=Renault,BMW gives the response response like:

{
    "_embedded": {
        "carList": [
            {
                "make": "BMW",
                "model": "i3"
            },
            {
                "make": "Renault",
                "model": "Clio"
            }
        ]
    },
    "_links": {
        "self": {
            "href": "http://localhost:8080/cars?make=Renault&make=BMW"
        }
    }
}

The link is http://localhost:8080/cars?make=Renault&make=BMW but should be http://localhost:8080/cars?make=Renault,BMW"

Debugging the code from org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo reaches HierarchicalUriComponents.getQuery which always duplicates the query variable name for each value.

Trying to change the behaviour of WebMvcLinkBuilder by creating a custom one seems to need a lot of code duplication. Maybe there is a better way to achieve this, but it would be better to have it done in a simple way (e.g. like using a spring configuration).

@odrotbohm
Copy link
Member

Can you elaborate what kind of specification you're referring to and where this YAML excerpt comes from?

I don't think the format you expect is actually a valid expectation as there's nothing in any URI spec that specifies a comma separated String to be translated into an array. The closest thing to something specified is ServletRequest.getParameterValues(…) which is parsed from exactly the format we produce.

This isn't even Spring HATEOAS specific. If you issue a request using a comma separated String that hits the method you declared, you should see the List contain a single element with the comma separated value.

@fernion
Copy link
Author

fernion commented Jul 13, 2021

The spec I mentioned was an OpenAPI spec for the project, as described in OpenAPI - Parameter Serialization.

As for the URI spec, it is mentioned for example in the following sections:
rfc6570 - 3.2.1

For a variable that is a list of values, expansion depends on both the expression type and the presence of an explode modifier. If there is no explode modifier, the expansion consists of a comma-separated concatenation of the defined member string values.

rfc6570 - 3.2.8

{?list} ?list=red,green,blue
{?list*} ?list=red&list=green&list=blue

I was aiming for the first example for lists (<parameter>=<comma separated values>).

If I should open the issue for spring itself please let me know.

Thank you for the quick reply and support.

@odrotbohm
Copy link
Member

We definitely have to talk to the core framework team here. There currently is no way to actually produce a comma separated parameter list via UriComponentsBuilder, except producing that comma separated list explicitly before handing the resulting String into the ….queryParam(…) method. I would like to avoid that to prevent us having to potentially, manually URI-encode the individual values. Also, from the method declaration, we have no way to detect which style is supposed to be used.

Just for reference: the sections you quote are from the URI Template RFC, not from the URI one. I only checked the latter as I assumed the URI parameter parsing within the HttpServletRequest implementation would solely be guided by that. It turns out that repetition of the keys creates a value array that's not further expanded. Examples (quotation marks to signify string elements):

  • ?foo=value1,value2 -> ["value1", "value2"]
  • ?foo=value&foo=value2-> ["value1", "value2"]
  • ?foo=value1,value2&foo=value3,value4 -> ["value1,value2", "value3,value4"]

The latter two are symmetric. Passing the value directly into ….queryParam("foo", value) yields the same parameter structure. Interestingly, expanding a template variable using the value collection results in a plain toString() deduced value. I.e. if you expand ?foo={foo} with the first collection value, you get ?foo=[value1, value2] which does not yield the expected result either, but is somewhat expected as, contrary to the resolutions side, no ConversionService is in play here.

Regarding OpenAPI spec. There's no out of the box integration with that spec on neither MVC nor HATEOAS level. Are you using any kind of third-party integration library?

@fernion
Copy link
Author

fernion commented Jul 13, 2021

We definitely have to talk to the core framework team here.

Should I open an issue there and link to this one? Or how should I proceed with this?

Also, from the method declaration, we have no way to detect which style is supposed to be used.

Yes, this is something that would be missing. Ideally it should be a configuration for each parameter, but I would assume that implementations for an API would be consistent and use just one approach. So a global setting for this behavior could be enough.

Are you using any kind of third-party integration library?

No, we are doing it by hand (not even generating java code from the spec).

@fernion
Copy link
Author

fernion commented Jul 28, 2021

@odrotbohm How can we proceed with this?

@odrotbohm
Copy link
Member

With #1583 in place, we might be able to solve this using a custom annotation (e.g. @NonComposite) to be used with @RequestParam annotated ones to tweak the rendering accordingly.

odrotbohm added a commit that referenced this issue Jul 28, 2021
This commit introduces @NonComposite, an annotation to be used with collection or array typed @RequestParam handler method parameters. Using the annotation causes the rendering of request parameters to use the non-composite way of rendering URI template values (param=value1,value2) rather than the default, composite flavor of param=value1&param=value2.

A bit of polish in TemplateVariable, which now also exposes a prepareAndEncode(Object) method that renders a given value according to the rules defined in the URI template spec for the particular variable type and state (composite VS. non-composite).
@odrotbohm
Copy link
Member

The 1.4 snapshots should support this via @NonComposite. See the added section of the reference documentation for details.

@odrotbohm odrotbohm self-assigned this Jul 28, 2021
@odrotbohm odrotbohm added this to the 1.4 M2 milestone Jul 28, 2021
@odrotbohm odrotbohm changed the title Arrays in links using WebMvcLinkBuilder Support for non-composite request parameter rendering on link creation Jul 28, 2021
@odrotbohm odrotbohm added in: core Core parts of the project type: enhancement labels Jul 28, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Core parts of the project type: enhancement
Projects
None yet
Development

No branches or pull requests

2 participants