Skip to content

Commit 0ab47bd

Browse files
committed
#775 - Clean up VndError implementation to match spec.
* Handle single, multiple, and nested errors.
1 parent 26c44dc commit 0ab47bd

File tree

9 files changed

+341
-252
lines changed

9 files changed

+341
-252
lines changed

src/main/java/org/springframework/hateoas/MediaTypes.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,15 @@ public class MediaTypes {
7676
* Public constant media type for {@code application/vnd.amundsen-uber+json}.
7777
*/
7878
public static final MediaType UBER_JSON = MediaType.parseMediaType(UBER_JSON_VALUE);
79+
80+
81+
/**
82+
* A String equivalent of {@link MediaTypes#VND_ERROR_JSON}.
83+
*/
84+
public static final String VND_ERROR_JSON_VALUE = "application/vnd.error+json";
85+
86+
/**
87+
* Public constant media type for {@code application/vnd.error+json}.
88+
*/
89+
public static final MediaType VND_ERROR_JSON = MediaType.valueOf(VND_ERROR_JSON_VALUE);
7990
}

src/main/java/org/springframework/hateoas/mediatype/vnderrors/VndErrors.java

Lines changed: 129 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,28 @@
1515
*/
1616
package org.springframework.hateoas.mediatype.vnderrors;
1717

18+
import lombok.EqualsAndHashCode;
19+
import lombok.Getter;
20+
import lombok.ToString;
21+
1822
import java.util.ArrayList;
1923
import java.util.Arrays;
24+
import java.util.Collection;
2025
import java.util.Iterator;
2126
import java.util.List;
2227

28+
import org.springframework.hateoas.CollectionModel;
2329
import org.springframework.hateoas.Link;
30+
import org.springframework.hateoas.Links;
2431
import org.springframework.hateoas.RepresentationModel;
25-
import org.springframework.lang.Nullable;
32+
import org.springframework.hateoas.server.core.Relation;
2633
import org.springframework.util.Assert;
27-
import org.springframework.util.StringUtils;
2834

2935
import com.fasterxml.jackson.annotation.JsonCreator;
36+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
37+
import com.fasterxml.jackson.annotation.JsonInclude;
3038
import com.fasterxml.jackson.annotation.JsonProperty;
31-
import com.fasterxml.jackson.annotation.JsonValue;
39+
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
3240

3341
/**
3442
* A representation model class to be rendered as specified for the media type {@code application/vnd.error}.
@@ -37,21 +45,35 @@
3745
* @author Oliver Gierke
3846
* @author Greg Turnquist
3947
*/
40-
public class VndErrors implements Iterable<VndErrors.VndError> {
48+
@JsonPropertyOrder({ "message", "logref", "total", "_links", "_embedded" })
49+
@JsonIgnoreProperties(ignoreUnknown = true)
50+
@EqualsAndHashCode
51+
@ToString
52+
public class VndErrors extends CollectionModel<VndErrors.VndError> {
4153

42-
private final List<VndError> vndErrors;
54+
/**
55+
* @deprecated Use {@link org.springframework.hateoas.IanaLinkRelations#HELP}
56+
*/
57+
@Deprecated
58+
public static final String REL_HELP = "help";
4359

4460
/**
45-
* Creates a new {@link VndErrors} instance containing a single {@link VndError} with the given logref, message and
46-
* optional {@link Link}s.
47-
*
48-
* @param logref must not be {@literal null} or empty.
49-
* @param message must not be {@literal null} or empty.
50-
* @param links
61+
* @deprecated Use {@link org.springframework.hateoas.IanaLinkRelations#DESCRIBES}
5162
*/
52-
public VndErrors(String logref, String message, Link... links) {
53-
this(new VndError(logref, message, links));
54-
}
63+
@Deprecated
64+
public static final String REL_DESCRIBES = "describes";
65+
66+
/**
67+
* @deprecated Use {@link org.springframework.hateoas.IanaLinkRelations#ABOUT}
68+
*/
69+
@Deprecated
70+
public static final String REL_ABOUT = "about";
71+
72+
private final List<VndError> errors;
73+
74+
@Getter @JsonInclude(value = JsonInclude.Include.NON_EMPTY) private final String message;
75+
76+
@Getter @JsonInclude(value = JsonInclude.Include.NON_EMPTY) private final Integer logref;
5577

5678
/**
5779
* Creates a new {@link VndErrors} wrapper for at least one {@link VndError}.
@@ -62,9 +84,11 @@ public VndErrors(VndError error, VndError... errors) {
6284

6385
Assert.notNull(error, "Error must not be null");
6486

65-
this.vndErrors = new ArrayList<>(errors.length + 1);
66-
this.vndErrors.add(error);
67-
this.vndErrors.addAll(Arrays.asList(errors));
87+
this.errors = new ArrayList<>(errors.length + 1);
88+
this.errors.add(error);
89+
this.errors.addAll(Arrays.asList(errors));
90+
this.message = null;
91+
this.logref = null;
6892
}
6993

7094
/**
@@ -73,138 +97,142 @@ public VndErrors(VndError error, VndError... errors) {
7397
* @param errors must not be {@literal null} or empty.
7498
*/
7599
@JsonCreator
76-
public VndErrors(List<VndError> errors) {
100+
public VndErrors(@JsonProperty("_embedded") List<VndError> errors, @JsonProperty("message") String message,
101+
@JsonProperty("logref") Integer logref, @JsonProperty("_links") Links links) {
102+
103+
Assert.notNull(errors, "Errors must not be null!"); // Retain for compatibility
104+
Assert.notEmpty(errors, "Errors must not be empty!");
105+
106+
this.errors = errors;
107+
this.message = message;
108+
this.logref = logref;
77109

78-
Assert.notNull(errors, "Errors must not be null!");
79-
Assert.isTrue(!errors.isEmpty(), "Errors must not be empty!");
80-
this.vndErrors = errors;
110+
if (links != null && !links.isEmpty()) {
111+
add(links);
112+
}
81113
}
82114

83-
/**
84-
* Protected default constructor to allow JAXB marshalling.
85-
*/
86-
protected VndErrors() {
87-
this.vndErrors = new ArrayList<>();
115+
public VndErrors withMessage(String message) {
116+
return new VndErrors(this.errors, message, this.logref, this.getLinks());
117+
}
118+
119+
public VndErrors withLogref(Integer logref) {
120+
return new VndErrors(this.errors, this.message, logref, this.getLinks());
121+
}
122+
123+
public VndErrors withErrors(List<VndError> errors) {
124+
125+
Assert.notNull(errors, "errors must not be null!");
126+
Assert.notEmpty(errors, "errors must not empty!");
127+
128+
return new VndErrors(errors, this.message, this.logref, this.getLinks());
129+
}
130+
131+
public VndErrors withError(VndError error) {
132+
133+
this.errors.add(error);
134+
return new VndErrors(this.errors, this.message, this.logref, this.getLinks());
135+
}
136+
137+
public VndErrors withLink(Link link) {
138+
139+
add(link);
140+
return new VndErrors(this.errors, this.message, this.logref, this.getLinks());
88141
}
89142

90143
/**
91-
* Adds an additional {@link VndError} to the wrapper.
144+
* Returns the underlying elements.
92145
*
93-
* @param error
146+
* @return the content will never be {@literal null}.
94147
*/
95-
public VndErrors add(VndError error) {
96-
this.vndErrors.add(error);
97-
return this;
148+
@Override
149+
public Collection<VndError> getContent() {
150+
return this.errors;
98151
}
99152

100153
/**
101-
* Dummy method to allow {@link JsonValue} to be configured.
102-
*
103-
* @return the vndErrors
154+
* Virtual attribute to generate JSON field of {@literal total}. Only generated when there are multiple errors.
104155
*/
105-
@JsonValue
106-
private List<VndError> getErrors() {
107-
return vndErrors;
156+
@JsonInclude(JsonInclude.Include.NON_NULL)
157+
public Integer getTotal() {
158+
return this.errors.size() > 1 //
159+
? this.errors.size() //
160+
: null; //
108161
}
109162

110-
/*
111-
* (non-Javadoc)
112-
* @see java.lang.Iterable#iterator()
113-
*/
114-
@Override
115-
public Iterator<VndErrors.VndError> iterator() {
116-
return this.vndErrors.iterator();
163+
public void setTotal(int __) {
164+
// Ignore this virtual attribute
117165
}
118166

119-
/*
120-
* (non-Javadoc)
121-
* @see java.lang.Object#toString()
167+
/**
168+
* Protected default constructor to allow JAXB marshalling.
122169
*/
123-
@Override
124-
public String toString() {
125-
return String.format("VndErrors[%s]", StringUtils.collectionToCommaDelimitedString(vndErrors));
170+
public VndErrors() {
171+
172+
this.errors = new ArrayList<>();
173+
this.message = null;
174+
this.logref = null;
126175
}
127176

128-
/*
129-
* (non-Javadoc)
130-
* @see java.lang.Object#hashCode()
177+
/**
178+
* Adds an additional {@link VndError} to the wrapper.
179+
*
180+
* @param error
131181
*/
132-
@Override
133-
public int hashCode() {
134-
return vndErrors.hashCode();
182+
public VndErrors add(VndError error) {
183+
return withError(error);
135184
}
136185

137186
/*
138187
* (non-Javadoc)
139-
* @see java.lang.Object#equals(java.lang.Object)
188+
* @see java.lang.Iterable#iterator()
140189
*/
141190
@Override
142-
public boolean equals(@Nullable Object obj) {
143-
144-
if (this == obj) {
145-
return true;
146-
}
147-
148-
if (!(obj instanceof VndErrors)) {
149-
return false;
150-
}
151-
152-
VndErrors that = (VndErrors) obj;
153-
return this.vndErrors.equals(that.vndErrors);
191+
public Iterator<VndErrors.VndError> iterator() {
192+
return this.errors.iterator();
154193
}
155194

156195
/**
157196
* A single {@link VndError}.
158197
*
159198
* @author Oliver Gierke
199+
* @author Greg Turnquist
160200
*/
201+
@JsonPropertyOrder({ "message", "path", "logref" })
202+
@Relation(collectionRelation = "errors")
203+
@EqualsAndHashCode
161204
public static class VndError extends RepresentationModel<VndError> {
162205

163-
@JsonProperty private final String logref;
164-
@JsonProperty private final String message;
206+
@Getter //
207+
private final String message;
208+
209+
@Getter(onMethod = @__(@JsonInclude(JsonInclude.Include.NON_EMPTY))) //
210+
private final String path;
211+
212+
@Getter(onMethod = @__(@JsonInclude(JsonInclude.Include.NON_EMPTY))) //
213+
private final Integer logref;
165214

166215
/**
167-
* Creates a new {@link VndError} with the given logref, a message as well as some {@link Link}s.
216+
* Creates a new {@link VndError} with a message and optional a path and a logref.
168217
*
169218
* @param logref must not be {@literal null} or empty.
170219
* @param message must not be {@literal null} or empty.
171220
* @param links
172221
*/
173-
public VndError(String logref, String message, Link... links) {
222+
@JsonCreator
223+
public VndError(@JsonProperty("message") String message, @JsonProperty("path") String path,
224+
@JsonProperty("logref") Integer logref, @JsonProperty("_links") List<Link> links) {
174225

175-
Assert.hasText(logref, "Logref must not be null or empty!");
176226
Assert.hasText(message, "Message must not be null or empty!");
177227

178-
this.logref = logref;
179228
this.message = message;
180-
this.add(Arrays.asList(links));
181-
}
182-
183-
/**
184-
* Protected default constructor to allow JAXB marshalling.
185-
*/
186-
protected VndError() {
187-
188-
this.logref = null;
189-
this.message = null;
190-
}
191-
192-
/**
193-
* Returns the logref of the error.
194-
*
195-
* @return the logref
196-
*/
197-
public String getLogref() {
198-
return logref;
229+
this.path = path;
230+
this.logref = logref;
231+
this.add(links);
199232
}
200233

201-
/**
202-
* Returns the message of the error.
203-
*
204-
* @return the message
205-
*/
206-
public String getMessage() {
207-
return message;
234+
public VndError(String message, String path, Integer logref, Link... link) {
235+
this(message, path, logref, Arrays.asList(link));
208236
}
209237

210238
/*
@@ -215,40 +243,5 @@ public String getMessage() {
215243
public String toString() {
216244
return String.format("VndError[logref: %s, message: %s, links: [%s]]", logref, message, getLinks().toString());
217245
}
218-
219-
/*
220-
* (non-Javadoc)
221-
* @see org.springframework.hateoas.ResourceSupport#hashCode()
222-
*/
223-
@Override
224-
public int hashCode() {
225-
226-
int result = 17;
227-
228-
result += 31 * logref.hashCode();
229-
result += 31 * message.hashCode();
230-
231-
return result;
232-
}
233-
234-
/*
235-
* (non-Javadoc)
236-
* @see org.springframework.hateoas.ResourceSupport#equals(java.lang.Object)
237-
*/
238-
@Override
239-
public boolean equals(@Nullable Object obj) {
240-
241-
if (obj == this) {
242-
return true;
243-
}
244-
245-
if (!(obj instanceof VndError)) {
246-
return false;
247-
}
248-
249-
VndError that = (VndError) obj;
250-
251-
return this.logref.equals(that.logref) && this.message.equals(that.message);
252-
}
253246
}
254247
}

0 commit comments

Comments
 (0)