Improve the abstraction that's used for link extraction
Previously, the link extraction abstraction was inadequate as it made assumptions about the format of the links that did not apply to all media types. For example, it was suited to the links returned in a application/hal+json response, but was not suited to the Atom-style links often found in application/json responses. This commit improves the abstraction so that the extracted links are decoupled from the format of the response. In addition to the existing support for extracting HAL links a new extractor for Atom-style links has been introduced. Both extractors are now available via static methods on the new LinkExtractors class. The sample applications have been updated accordingly. Closes #6
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* Copyright 2014-2015 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* Copyright 2014-2015 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.
|
||||
@@ -19,7 +19,7 @@ package com.example.notes;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.springframework.restdocs.core.RestDocumentation.document;
|
||||
import static org.springframework.restdocs.core.RestDocumentation.halLinks;
|
||||
import static org.springframework.restdocs.core.LinkExtractors.halLinks;
|
||||
import static org.springframework.restdocs.core.RestDocumentation.linkWithRel;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* Copyright 2014-2015 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.
|
||||
@@ -18,8 +18,8 @@ package com.example.notes;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.springframework.restdocs.core.LinkExtractors.halLinks;
|
||||
import static org.springframework.restdocs.core.RestDocumentation.document;
|
||||
import static org.springframework.restdocs.core.RestDocumentation.halLinks;
|
||||
import static org.springframework.restdocs.core.RestDocumentation.linkWithRel;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2014-2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.core;
|
||||
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
||||
/**
|
||||
* Representation of a link used in a Hypermedia-based API
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class Link {
|
||||
|
||||
private final String rel;
|
||||
|
||||
private final String href;
|
||||
|
||||
/**
|
||||
* Creates a new {@code Link} with the given {@code rel} and {@code href}
|
||||
*
|
||||
* @param rel The link's rel
|
||||
* @param href The link's href
|
||||
*/
|
||||
public Link(String rel, String href) {
|
||||
this.rel = rel;
|
||||
this.href = href;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the link's {@code rel}
|
||||
* @return the link's {@code rel}
|
||||
*/
|
||||
public String getRel() {
|
||||
return rel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the link's {@code href}
|
||||
* @return the link's {@code href}
|
||||
*/
|
||||
public String getHref() {
|
||||
return href;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + href.hashCode();
|
||||
result = prime * result + rel.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Link other = (Link) obj;
|
||||
if (!href.equals(other.href)) {
|
||||
return false;
|
||||
}
|
||||
if (!rel.equals(other.rel)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("rel", this.rel)
|
||||
.append("href", this.href).toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* Copyright 2014-2015 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.
|
||||
@@ -16,10 +16,30 @@
|
||||
|
||||
package org.springframework.restdocs.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
/**
|
||||
* A {@code LinkExtractor} is used to extract {@link Link links} from a JSON response. The
|
||||
* expected format of the links in the response is determined by the implementation.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*
|
||||
*/
|
||||
public interface LinkExtractor {
|
||||
|
||||
Map<String, Object> extractLinks(Map<String, Object> responseJson);
|
||||
/**
|
||||
* Extract the links from the given response, returning a {@code Map} of links where
|
||||
* the keys are the link rels.
|
||||
*
|
||||
* @param response The response from which the links are to be extracted
|
||||
* @return The extracted links, keyed by rel
|
||||
* @throws IOException if link extraction fails
|
||||
*/
|
||||
Map<String, List<Link>> extractLinks(MockHttpServletResponse response)
|
||||
throws IOException;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2014-2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Static factory methods provided a selection of {@link LinkExtractor link extractors}
|
||||
* for use when documentating a hypermedia-based API.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*
|
||||
*/
|
||||
public class LinkExtractors {
|
||||
|
||||
/**
|
||||
* Returns a {@code LinkExtractor} capable of extracting links in Hypermedia
|
||||
* Application Language (HAL) format where the links are found in a map named
|
||||
* {@code _links}.
|
||||
*
|
||||
* @return The extract for HAL-style links
|
||||
*/
|
||||
public static LinkExtractor halLinks() {
|
||||
return new HalLinkExtractor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code LinkExtractor} capable of extracting links in Atom format where
|
||||
* the links are found in an array named {@code links}.
|
||||
*
|
||||
* @return The extractor for Atom-style links
|
||||
*/
|
||||
public static LinkExtractor atomLinks() {
|
||||
return new AtomLinkExtractor();
|
||||
}
|
||||
|
||||
private static abstract class JsonContentLinkExtractor implements LinkExtractor {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, List<Link>> extractLinks(MockHttpServletResponse response)
|
||||
throws IOException {
|
||||
Map<String, Object> jsonContent = this.objectMapper.readValue(
|
||||
response.getContentAsString(), Map.class);
|
||||
return extractLinks(jsonContent);
|
||||
}
|
||||
|
||||
protected abstract Map<String, List<Link>> extractLinks(Map<String, Object> json);
|
||||
}
|
||||
|
||||
private static class HalLinkExtractor extends JsonContentLinkExtractor {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Map<String, List<Link>> extractLinks(Map<String, Object> json) {
|
||||
Map<String, List<Link>> extractedLinks = new HashMap<>();
|
||||
Object possibleLinks = json.get("_links");
|
||||
if (possibleLinks instanceof Map) {
|
||||
Map<String, Object> links = (Map<String, Object>) possibleLinks;
|
||||
for (Entry<String, Object> entry : links.entrySet()) {
|
||||
String rel = (String) entry.getKey();
|
||||
extractedLinks.put(rel, convertToLinks(entry.getValue(), rel));
|
||||
}
|
||||
}
|
||||
return extractedLinks;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<Link> convertToLinks(Object object, String rel) {
|
||||
List<Link> links = new ArrayList<>();
|
||||
if (object instanceof Collection) {
|
||||
Collection<Object> hrefObjects = (Collection<Object>) object;
|
||||
for (Object hrefObject : hrefObjects) {
|
||||
maybeAddLink(maybeCreateLink(rel, hrefObject), links);
|
||||
}
|
||||
}
|
||||
else {
|
||||
maybeAddLink(maybeCreateLink(rel, object), links);
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
private static Link maybeCreateLink(String rel, Object possibleHref) {
|
||||
if (possibleHref instanceof String) {
|
||||
return new Link(rel, (String) possibleHref);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void maybeAddLink(Link possibleLink, List<Link> links) {
|
||||
if (possibleLink != null) {
|
||||
links.add(possibleLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class AtomLinkExtractor extends JsonContentLinkExtractor {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Map<String, List<Link>> extractLinks(Map<String, Object> json) {
|
||||
Map<String, List<Link>> extractedLinks = new HashMap<>();
|
||||
Object possibleLinks = json.get("links");
|
||||
if (possibleLinks instanceof Collection) {
|
||||
Collection<Object> linksCollection = (Collection<Object>) possibleLinks;
|
||||
for (Object linkObject : linksCollection) {
|
||||
if (linkObject instanceof Map) {
|
||||
Link link = maybeCreateLink((Map<String, Object>) linkObject);
|
||||
maybeStoreLink(link, extractedLinks);
|
||||
}
|
||||
}
|
||||
}
|
||||
return extractedLinks;
|
||||
}
|
||||
|
||||
private static Link maybeCreateLink(Map<String, Object> linkMap) {
|
||||
Object hrefObject = linkMap.get("href");
|
||||
Object relObject = linkMap.get("rel");
|
||||
if (relObject instanceof String && hrefObject instanceof String) {
|
||||
return new Link((String) relObject, (String) hrefObject);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void maybeStoreLink(Link link,
|
||||
Map<String, List<Link>> extractedLinks) {
|
||||
if (link != null) {
|
||||
List<Link> linksForRel = extractedLinks.get(link.getRel());
|
||||
if (linksForRel == null) {
|
||||
linksForRel = new ArrayList<Link>();
|
||||
extractedLinks.put(link.getRel(), linksForRel);
|
||||
}
|
||||
linksForRel.add(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* Copyright 2014-2015 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.
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.springframework.restdocs.core;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class RestDocumentation {
|
||||
|
||||
public static RestDocumentationResultHandler document(String outputDir)
|
||||
@@ -29,14 +27,4 @@ public class RestDocumentation {
|
||||
return new LinkDescriptor(rel);
|
||||
}
|
||||
|
||||
public static LinkExtractor halLinks() {
|
||||
return new LinkExtractor() {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Map<String, Object> extractLinks(Map<String, Object> responseJson) {
|
||||
return (Map<String, Object>) responseJson.get("_links");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* Copyright 2014-2015 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.
|
||||
@@ -41,8 +41,6 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
public abstract class RestDocumentationResultHandlers {
|
||||
|
||||
public static CurlResultHandler documentCurlRequest(String outputDir) {
|
||||
@@ -245,8 +243,6 @@ public abstract class RestDocumentationResultHandlers {
|
||||
|
||||
static class LinkDocumentingResultHandler extends RestDocumentationResultHandler {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private final Map<String, LinkDescriptor> descriptorsByRel = new HashMap<String, LinkDescriptor>();
|
||||
|
||||
private final LinkExtractor extractor;
|
||||
@@ -262,12 +258,10 @@ public abstract class RestDocumentationResultHandlers {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
void handle(MvcResult result, DocumentationWriter writer) throws Exception {
|
||||
Map<String, Object> json = this.objectMapper.readValue(result.getResponse()
|
||||
.getContentAsString(), Map.class);
|
||||
Map<String, Object> links = this.extractor.extractLinks(json);
|
||||
Map<String, List<Link>> links = this.extractor.extractLinks(result
|
||||
.getResponse());
|
||||
|
||||
Set<String> actualRels = links.keySet();
|
||||
Set<String> expectedRels = this.descriptorsByRel.keySet();
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2014-2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.core;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* Tests for {@link LinkExtractors}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class LinkExtractorsTests {
|
||||
|
||||
private final LinkExtractor linkExtractor;
|
||||
|
||||
private final String linkType;
|
||||
|
||||
@Parameters
|
||||
public static Collection<Object[]> data() {
|
||||
return Arrays.asList(new Object[] { LinkExtractors.halLinks(), "hal" },
|
||||
new Object[] { LinkExtractors.atomLinks(), "atom" });
|
||||
}
|
||||
|
||||
public LinkExtractorsTests(LinkExtractor linkExtractor, String linkType) {
|
||||
this.linkExtractor = linkExtractor;
|
||||
this.linkType = linkType;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singleLink() throws IOException {
|
||||
Map<String, List<Link>> links = this.linkExtractor
|
||||
.extractLinks(createResponse("single-link"));
|
||||
assertLinks(Arrays.asList(new Link("alpha", "http://alpha.example.com")), links);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleLinksWithDifferentRels() throws IOException {
|
||||
Map<String, List<Link>> links = this.linkExtractor
|
||||
.extractLinks(createResponse("multiple-links-different-rels"));
|
||||
assertLinks(Arrays.asList(new Link("alpha", "http://alpha.example.com"),
|
||||
new Link("bravo", "http://bravo.example.com")), links);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleLinksWithSameRels() throws IOException {
|
||||
Map<String, List<Link>> links = this.linkExtractor
|
||||
.extractLinks(createResponse("multiple-links-same-rels"));
|
||||
assertLinks(Arrays.asList(new Link("alpha", "http://alpha.example.com/one"),
|
||||
new Link("alpha", "http://alpha.example.com/two")), links);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noLinks() throws IOException {
|
||||
Map<String, List<Link>> links = this.linkExtractor
|
||||
.extractLinks(createResponse("no-links"));
|
||||
assertLinks(Collections.<Link> emptyList(), links);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linksInTheWrongFormat() throws IOException {
|
||||
Map<String, List<Link>> links = this.linkExtractor
|
||||
.extractLinks(createResponse("wrong-format"));
|
||||
assertLinks(Collections.<Link> emptyList(), links);
|
||||
}
|
||||
|
||||
private void assertLinks(List<Link> expectedLinks, Map<String, List<Link>> actualLinks) {
|
||||
Map<String, List<Link>> expectedLinksByRel = new HashMap<>();
|
||||
for (Link expectedLink : expectedLinks) {
|
||||
List<Link> expectedlinksWithRel = expectedLinksByRel.get(expectedLink
|
||||
.getRel());
|
||||
if (expectedlinksWithRel == null) {
|
||||
expectedlinksWithRel = new ArrayList<>();
|
||||
expectedLinksByRel.put(expectedLink.getRel(), expectedlinksWithRel);
|
||||
}
|
||||
expectedlinksWithRel.add(expectedLink);
|
||||
}
|
||||
assertEquals(expectedLinksByRel, actualLinks);
|
||||
}
|
||||
|
||||
private MockHttpServletResponse createResponse(String contentName) throws IOException {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FileCopyUtils.copy(new FileReader(getPayloadFile(contentName)),
|
||||
response.getWriter());
|
||||
return response;
|
||||
}
|
||||
|
||||
private File getPayloadFile(String name) {
|
||||
return new File("src/test/resources/link-payloads/" + linkType + "/" + name
|
||||
+ ".json");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"links": [ {
|
||||
"rel": "alpha",
|
||||
"href": "http://alpha.example.com"
|
||||
}, {
|
||||
"rel": "bravo",
|
||||
"href": "http://bravo.example.com"
|
||||
} ]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"links": [ {
|
||||
"rel": "alpha",
|
||||
"href": "http://alpha.example.com/one"
|
||||
}, {
|
||||
"rel": "alpha",
|
||||
"href": "http://alpha.example.com/two"
|
||||
} ]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{ }
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"links": [ {
|
||||
"rel": "alpha",
|
||||
"href": "http://alpha.example.com"
|
||||
} ]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"_links": {
|
||||
"alpha": ["http://alpha.example.com/one", "http://alpha.example.com/two"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"_links": {
|
||||
"alpha": "http://alpha.example.com",
|
||||
"bravo": "http://bravo.example.com"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"_links": {
|
||||
"alpha": ["http://alpha.example.com/one", "http://alpha.example.com/two"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{ }
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"_links": {
|
||||
"alpha": "http://alpha.example.com"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"_links": [ {
|
||||
"rel": "alpha",
|
||||
"href": "http://alpha.example.com/one"
|
||||
}, {
|
||||
"rel": "alpha",
|
||||
"href": "http://alpha.example.com/two"
|
||||
} ]
|
||||
}
|
||||
Reference in New Issue
Block a user