From 91446847681a78eb1c7a19aa663b7e50fba3ce2d Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Thu, 29 May 2025 22:24:43 +0200 Subject: [PATCH 1/2] Support link extraction with official HAL and HAL-FORMS media types Register the HalLinkExtractor for both the official HAL media type (application/vnd.hal+json) and the HAL-FORMS media type (application/prs.hal-forms+json) See gh-965 --- .../hypermedia/ContentTypeLinkExtractor.java | 7 +++++- .../restdocs/hypermedia/HalLinkExtractor.java | 2 ++ .../ContentTypeLinkExtractorTests.java | 22 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractor.java index ef2a9e8e..20a281de 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractor.java @@ -30,6 +30,7 @@ import org.springframework.restdocs.operation.OperationResponse; * content type. * * @author Andy Wilkinson + * @author Oliver Drotbohm */ class ContentTypeLinkExtractor implements LinkExtractor { @@ -37,7 +38,11 @@ class ContentTypeLinkExtractor implements LinkExtractor { ContentTypeLinkExtractor() { this.linkExtractors.put(MediaType.APPLICATION_JSON, new AtomLinkExtractor()); - this.linkExtractors.put(HalLinkExtractor.HAL_MEDIA_TYPE, new HalLinkExtractor()); + + LinkExtractor halLinkExtractor = new HalLinkExtractor(); + this.linkExtractors.put(HalLinkExtractor.HAL_MEDIA_TYPE, halLinkExtractor); + this.linkExtractors.put(HalLinkExtractor.VND_HAL_MEDIA_TYPE, halLinkExtractor); + this.linkExtractors.put(HalLinkExtractor.HAL_FORMS_MEDIA_TYPE, halLinkExtractor); } ContentTypeLinkExtractor(Map linkExtractors) { diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/HalLinkExtractor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/HalLinkExtractor.java index f93c572a..82d47f0b 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/HalLinkExtractor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/HalLinkExtractor.java @@ -34,6 +34,8 @@ import org.springframework.http.MediaType; class HalLinkExtractor extends AbstractJsonLinkExtractor { static final MediaType HAL_MEDIA_TYPE = new MediaType("application", "hal+json"); + static final MediaType VND_HAL_MEDIA_TYPE = new MediaType("application", "vnd.hal+json"); + static final MediaType HAL_FORMS_MEDIA_TYPE = new MediaType("application", "prs.hal-forms+json"); @Override public Map> extractLinks(Map json) { diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractorTests.java index 2e51b0ac..f48bcbe4 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractorTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractorTests.java @@ -18,6 +18,7 @@ package org.springframework.restdocs.hypermedia; import java.io.IOException; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; @@ -28,6 +29,7 @@ import org.springframework.http.MediaType; import org.springframework.restdocs.operation.OperationResponse; import org.springframework.restdocs.operation.OperationResponseFactory; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -41,6 +43,8 @@ class ContentTypeLinkExtractorTests { private final OperationResponseFactory responseFactory = new OperationResponseFactory(); + private final String halBody = "{ \"_links\" : { \"someRel\" : { \"href\" : \"someHref\" }} }"; + @Test void extractionFailsWithNullContentType() { assertThatIllegalStateException().isThrownBy(() -> new ContentTypeLinkExtractor() @@ -71,4 +75,22 @@ class ContentTypeLinkExtractorTests { verify(extractor).extractLinks(response); } + @Test + public void extractsLinksFromVndHalMediaType() throws IOException { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.parseMediaType("application/vnd.hal+json")); + OperationResponse response = this.responseFactory.create(HttpStatus.OK, httpHeaders, this.halBody.getBytes()); + Map> links = new ContentTypeLinkExtractor().extractLinks(response); + assertThat(links).containsKey("someRel"); + } + + @Test + public void extractsLinksFromHalFormsMediaType() throws IOException { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.parseMediaType("application/prs.hal-forms+json")); + OperationResponse response = this.responseFactory.create(HttpStatus.OK, httpHeaders, this.halBody.getBytes()); + Map> links = new ContentTypeLinkExtractor().extractLinks(response); + assertThat(links).containsKey("someRel"); + } + } From cd16feba50652ad408786d99ea02286d850e72e3 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 3 Jun 2025 19:24:18 +0100 Subject: [PATCH 2/2] Polish "Support link extraction with official HAL and HAL-FORMS media types" See gh-965 --- .../restdocs/hypermedia/ContentTypeLinkExtractor.java | 3 +-- .../restdocs/hypermedia/HalLinkExtractor.java | 5 ++++- .../restdocs/hypermedia/ContentTypeLinkExtractorTests.java | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractor.java index 20a281de..bdf5772c 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,6 @@ class ContentTypeLinkExtractor implements LinkExtractor { ContentTypeLinkExtractor() { this.linkExtractors.put(MediaType.APPLICATION_JSON, new AtomLinkExtractor()); - LinkExtractor halLinkExtractor = new HalLinkExtractor(); this.linkExtractors.put(HalLinkExtractor.HAL_MEDIA_TYPE, halLinkExtractor); this.linkExtractors.put(HalLinkExtractor.VND_HAL_MEDIA_TYPE, halLinkExtractor); diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/HalLinkExtractor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/HalLinkExtractor.java index 82d47f0b..e56f32ba 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/HalLinkExtractor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/hypermedia/HalLinkExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,11 +30,14 @@ import org.springframework.http.MediaType; * format. * * @author Andy Wilkinson + * @author Oliver Drotbohm */ class HalLinkExtractor extends AbstractJsonLinkExtractor { static final MediaType HAL_MEDIA_TYPE = new MediaType("application", "hal+json"); + static final MediaType VND_HAL_MEDIA_TYPE = new MediaType("application", "vnd.hal+json"); + static final MediaType HAL_FORMS_MEDIA_TYPE = new MediaType("application", "prs.hal-forms+json"); @Override diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractorTests.java index f48bcbe4..eb5875f8 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractorTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/hypermedia/ContentTypeLinkExtractorTests.java @@ -76,7 +76,7 @@ class ContentTypeLinkExtractorTests { } @Test - public void extractsLinksFromVndHalMediaType() throws IOException { + void extractsLinksFromVndHalMediaType() throws IOException { HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setContentType(MediaType.parseMediaType("application/vnd.hal+json")); OperationResponse response = this.responseFactory.create(HttpStatus.OK, httpHeaders, this.halBody.getBytes()); @@ -85,7 +85,7 @@ class ContentTypeLinkExtractorTests { } @Test - public void extractsLinksFromHalFormsMediaType() throws IOException { + void extractsLinksFromHalFormsMediaType() throws IOException { HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setContentType(MediaType.parseMediaType("application/prs.hal-forms+json")); OperationResponse response = this.responseFactory.create(HttpStatus.OK, httpHeaders, this.halBody.getBytes());