diff --git a/src/main/java/org/springframework/hateoas/Link.java b/src/main/java/org/springframework/hateoas/Link.java index e55313897..ba150f52d 100755 --- a/src/main/java/org/springframework/hateoas/Link.java +++ b/src/main/java/org/springframework/hateoas/Link.java @@ -22,6 +22,7 @@ import lombok.experimental.Wither; import java.io.Serializable; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -49,7 +50,7 @@ */ @XmlType(name = "link", namespace = Link.ATOM_NAMESPACE) @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonIgnoreProperties(value = "templated", ignoreUnknown = true) +@JsonIgnoreProperties(value = "templated", ignoreUnknown = true) @AllArgsConstructor(access = AccessLevel.PACKAGE) @Getter @EqualsAndHashCode(of = { "rel", "href", "hreflang", "media", "title", "deprecation", "affordances" }) @@ -131,7 +132,7 @@ protected Link() { /** * Returns safe copy of {@link Affordance}s. - * + * * @return */ public List getAffordances() { @@ -166,7 +167,7 @@ public Link andAffordance(Affordance affordance) { /** * Create new {@link Link} with additional {@link Affordance}s. - * + * * @param affordances must not be {@literal null}. * @return */ @@ -181,7 +182,7 @@ public Link andAffordances(List affordances) { /** * Creats a new {@link Link} with the given {@link Affordance}s. - * + * * @param affordances must not be {@literal null}. * @return */ @@ -227,7 +228,12 @@ public boolean isTemplated() { * @return */ public Link expand(Object... arguments) { - return new Link(getUriTemplate().expand(arguments).toString(), getRel()); + + URI uri = (arguments == null || arguments.length == 0) ? + getUriTemplate().checkRequiredParams() : + getUriTemplate().expand(arguments); + + return new Link(uri.toString(), getRel()); } /** @@ -242,7 +248,7 @@ public Link expand(Map arguments) { /** * Returns whether the current {@link Link} has the given link relation. - * + * * @param rel must not be {@literal null} or empty. * @return */ diff --git a/src/main/java/org/springframework/hateoas/UriTemplate.java b/src/main/java/org/springframework/hateoas/UriTemplate.java index e1a651019..614545175 100644 --- a/src/main/java/org/springframework/hateoas/UriTemplate.java +++ b/src/main/java/org/springframework/hateoas/UriTemplate.java @@ -203,6 +203,22 @@ public URI expand(Object... parameters) { return builder.build().toUri(); } + /** + * Checks the {@link UriTemplate} for unset but required params. + * + * @return + */ + public URI checkRequiredParams() { + for (TemplateVariable variable : getOptionalVariables()) { + if (variable.isRequired()) { + throw new IllegalArgumentException( + String.format("Template variable %s is required but no value was given!", variable.getName())); + } + } + + return URI.create(baseUri); + } + /** * Expands the {@link UriTemplate} using the given parameters. * diff --git a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java index 25e316190..2d9f75b7e 100755 --- a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java @@ -231,6 +231,42 @@ public void linksToMethodWithPathVariableAndRequestParams() { assertThat(queryParams.get("offset"), contains("10")); } + @Test + public void linksToMethodWithPathVariableAndRequestParamsWithNull() { + + Link link = linkTo(methodOn(ControllerWithMethods.class).methodForNextPage("1", null, 5)).withSelfRel(); + + UriComponents components = toComponents(link); + assertThat(components.getPath()).isEqualTo("/something/1/foo"); + + MultiValueMap queryParams = components.getQueryParams(); + assertThat(queryParams.get("limit"), contains("5")); + } + + @Test + public void linksToMethodWithPathVariableWithBlankAndRequestParamsWithNull() { + + Link link = linkTo(methodOn(ControllerWithMethods.class).methodForNextPage("with blank", null, 5)).withSelfRel(); + + UriComponents components = toComponents(link); + assertThat(components.getPath()).isEqualTo("/something/with%20blank/foo"); + + MultiValueMap queryParams = components.getQueryParams(); + assertThat(queryParams.get("limit"), contains("5")); + } + + @Test + public void linksToMethodWithRequestParam() { + + Link link = linkTo(methodOn(ControllerWithMethods.class).methodForNextPage("with blank", null, 5)).withSelfRel(); + + UriComponents components = toComponents(link); + assertThat(components.getPath()).isEqualTo("/something/with%20blank/foo"); + + MultiValueMap queryParams = components.getQueryParams(); + assertThat(queryParams.get("limit"), contains("5")); + } + /** * @see #26, #39 */ @@ -474,6 +510,15 @@ public void encodesRequestParameterWithSpecialValue() { assertThat(link.getHref()).endsWith("/something/foo?id=Spring%23%0A"); } + @Test + public void encodesAndExpandsPathvariableWithSpecialValueAndRequestParameterWithNull() { + + Link link = linkTo(methodOn(ControllerWithMethods.class).methodForNextPage("Spring#\n", null, 10)).withSelfRel().expand(); + + assertThat(link.getRel()).isEqualTo(Link.REL_SELF); + assertThat(link.getHref()).endsWith("/something/Spring%23%0A/foo?limit=10"); + } + /** * @see #169 */