Skip to content

Commit f1138d8

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

File tree

9 files changed

+349
-251
lines changed

9 files changed

+349
-251
lines changed

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

+11
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

+137-135
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,29 @@
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;
25+
import java.util.Collections;
2026
import java.util.Iterator;
2127
import java.util.List;
2228

29+
import org.springframework.hateoas.CollectionModel;
2330
import org.springframework.hateoas.Link;
31+
import org.springframework.hateoas.Links;
2432
import org.springframework.hateoas.RepresentationModel;
25-
import org.springframework.lang.Nullable;
33+
import org.springframework.hateoas.server.core.Relation;
2634
import org.springframework.util.Assert;
27-
import org.springframework.util.StringUtils;
2835

2936
import com.fasterxml.jackson.annotation.JsonCreator;
37+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
38+
import com.fasterxml.jackson.annotation.JsonInclude;
3039
import com.fasterxml.jackson.annotation.JsonProperty;
31-
import com.fasterxml.jackson.annotation.JsonValue;
40+
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
3241

3342
/**
3443
* A representation model class to be rendered as specified for the media type {@code application/vnd.error}.
@@ -37,20 +46,50 @@
3746
* @author Oliver Gierke
3847
* @author Greg Turnquist
3948
*/
40-
public class VndErrors implements Iterable<VndErrors.VndError> {
49+
@JsonPropertyOrder({ "message", "logref", "total", "_links", "_embedded" })
50+
@JsonIgnoreProperties(ignoreUnknown = true)
51+
@EqualsAndHashCode
52+
@ToString
53+
public class VndErrors extends CollectionModel<VndErrors.VndError> {
54+
55+
/**
56+
* @deprecated Use {@link org.springframework.hateoas.IanaLinkRelations#HELP}
57+
*/
58+
@Deprecated public static final String REL_HELP = "help";
59+
60+
/**
61+
* @deprecated Use {@link org.springframework.hateoas.IanaLinkRelations#DESCRIBES}
62+
*/
63+
@Deprecated public static final String REL_DESCRIBES = "describes";
64+
65+
/**
66+
* @deprecated Use {@link org.springframework.hateoas.IanaLinkRelations#ABOUT}
67+
*/
68+
@Deprecated public static final String REL_ABOUT = "about";
69+
70+
private final List<VndError> errors;
71+
72+
@Getter //
73+
@JsonInclude(value = JsonInclude.Include.NON_EMPTY) //
74+
private final String message;
75+
76+
@Getter //
77+
@JsonInclude(value = JsonInclude.Include.NON_EMPTY) //
78+
private final Integer logref;
4179

42-
private final List<VndError> vndErrors;
80+
public VndErrors() {
81+
82+
this.errors = new ArrayList<>();
83+
this.message = null;
84+
this.logref = null;
85+
}
4386

4487
/**
4588
* Creates a new {@link VndErrors} instance containing a single {@link VndError} with the given logref, message and
4689
* 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
5190
*/
5291
public VndErrors(String logref, String message, Link... links) {
53-
this(new VndError(logref, message, links));
92+
this(new VndError(message, null, Integer.parseInt(logref), links));
5493
}
5594

5695
/**
@@ -62,9 +101,11 @@ public VndErrors(VndError error, VndError... errors) {
62101

63102
Assert.notNull(error, "Error must not be null");
64103

65-
this.vndErrors = new ArrayList<>(errors.length + 1);
66-
this.vndErrors.add(error);
67-
this.vndErrors.addAll(Arrays.asList(errors));
104+
this.errors = new ArrayList<>();
105+
this.errors.add(error);
106+
Collections.addAll(this.errors, errors);
107+
this.message = null;
108+
this.logref = null;
68109
}
69110

70111
/**
@@ -73,138 +114,134 @@ public VndErrors(VndError error, VndError... errors) {
73114
* @param errors must not be {@literal null} or empty.
74115
*/
75116
@JsonCreator
76-
public VndErrors(List<VndError> errors) {
117+
public VndErrors(@JsonProperty("_embedded") List<VndError> errors, @JsonProperty("message") String message,
118+
@JsonProperty("logref") Integer logref, @JsonProperty("_links") Links links) {
119+
120+
Assert.notNull(errors, "Errors must not be null!"); // Retain for compatibility
121+
Assert.notEmpty(errors, "Errors must not be empty!");
77122

78-
Assert.notNull(errors, "Errors must not be null!");
79-
Assert.isTrue(!errors.isEmpty(), "Errors must not be empty!");
80-
this.vndErrors = errors;
123+
this.errors = errors;
124+
this.message = message;
125+
this.logref = logref;
126+
127+
if (links != null && !links.isEmpty()) {
128+
add(links);
129+
}
81130
}
82131

83-
/**
84-
* Protected default constructor to allow JAXB marshalling.
85-
*/
86-
protected VndErrors() {
87-
this.vndErrors = new ArrayList<>();
132+
public VndErrors withMessage(String message) {
133+
return new VndErrors(this.errors, message, this.logref, this.getLinks());
88134
}
89135

90-
/**
91-
* Adds an additional {@link VndError} to the wrapper.
92-
*
93-
* @param error
94-
*/
95-
public VndErrors add(VndError error) {
96-
this.vndErrors.add(error);
97-
return this;
136+
public VndErrors withLogref(Integer logref) {
137+
return new VndErrors(this.errors, this.message, logref, this.getLinks());
98138
}
99139

100-
/**
101-
* Dummy method to allow {@link JsonValue} to be configured.
102-
*
103-
* @return the vndErrors
104-
*/
105-
@JsonValue
106-
private List<VndError> getErrors() {
107-
return vndErrors;
140+
public VndErrors withErrors(List<VndError> errors) {
141+
142+
Assert.notNull(errors, "errors must not be null!");
143+
Assert.notEmpty(errors, "errors must not empty!");
144+
145+
return new VndErrors(errors, this.message, this.logref, this.getLinks());
108146
}
109147

110-
/*
111-
* (non-Javadoc)
112-
* @see java.lang.Iterable#iterator()
148+
public VndErrors withError(VndError error) {
149+
150+
this.errors.add(error);
151+
return new VndErrors(this.errors, this.message, this.logref, this.getLinks());
152+
}
153+
154+
public VndErrors withLink(Link link) {
155+
156+
add(link);
157+
return new VndErrors(this.errors, this.message, this.logref, this.getLinks());
158+
}
159+
160+
public VndErrors withLinks(Link... links) {
161+
162+
add(links);
163+
return new VndErrors(this.errors, this.message, this.logref, this.getLinks());
164+
}
165+
166+
/**
167+
* Returns the underlying elements.
168+
*
169+
* @return the content will never be {@literal null}.
113170
*/
114171
@Override
115-
public Iterator<VndErrors.VndError> iterator() {
116-
return this.vndErrors.iterator();
172+
public Collection<VndError> getContent() {
173+
return this.errors;
117174
}
118175

119-
/*
120-
* (non-Javadoc)
121-
* @see java.lang.Object#toString()
176+
/**
177+
* Virtual attribute to generate JSON field of {@literal total}. Only generated when there are multiple errors.
122178
*/
123-
@Override
124-
public String toString() {
125-
return String.format("VndErrors[%s]", StringUtils.collectionToCommaDelimitedString(vndErrors));
179+
@JsonInclude(JsonInclude.Include.NON_NULL)
180+
public Integer getTotal() {
181+
return this.errors.size() > 1 //
182+
? this.errors.size() //
183+
: null; //
126184
}
127185

128-
/*
129-
* (non-Javadoc)
130-
* @see java.lang.Object#hashCode()
186+
/**
187+
* Adds an additional {@link VndError} to the wrapper.
188+
*
189+
* @param error
131190
*/
132-
@Override
133-
public int hashCode() {
134-
return vndErrors.hashCode();
191+
public VndErrors add(VndError error) {
192+
return withError(error);
135193
}
136194

137195
/*
138196
* (non-Javadoc)
139-
* @see java.lang.Object#equals(java.lang.Object)
197+
* @see java.lang.Iterable#iterator()
140198
*/
141199
@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);
200+
public Iterator<VndErrors.VndError> iterator() {
201+
return this.errors.iterator();
154202
}
155203

156204
/**
157205
* A single {@link VndError}.
158206
*
159207
* @author Oliver Gierke
208+
* @author Greg Turnquist
160209
*/
210+
@JsonPropertyOrder({ "message", "path", "logref" })
211+
@Relation(collectionRelation = "errors")
212+
@EqualsAndHashCode
161213
public static class VndError extends RepresentationModel<VndError> {
162214

163-
@JsonProperty private final String logref;
164-
@JsonProperty private final String message;
215+
@Getter //
216+
private final String message;
217+
218+
@Getter(onMethod = @__(@JsonInclude(JsonInclude.Include.NON_EMPTY))) //
219+
private final String path;
220+
221+
@Getter(onMethod = @__(@JsonInclude(JsonInclude.Include.NON_EMPTY))) //
222+
private final Integer logref;
165223

166224
/**
167-
* Creates a new {@link VndError} with the given logref, a message as well as some {@link Link}s.
225+
* Creates a new {@link VndError} with a message and optional a path and a logref.
168226
*
169227
* @param logref must not be {@literal null} or empty.
170228
* @param message must not be {@literal null} or empty.
171229
* @param links
172230
*/
173-
public VndError(String logref, String message, Link... links) {
231+
@JsonCreator
232+
public VndError(@JsonProperty("message") String message, @JsonProperty("path") String path,
233+
@JsonProperty("logref") Integer logref, @JsonProperty("_links") List<Link> links) {
174234

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

178-
this.logref = logref;
179237
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;
238+
this.path = path;
239+
this.logref = logref;
240+
this.add(links);
199241
}
200242

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

210247
/*
@@ -215,40 +252,5 @@ public String getMessage() {
215252
public String toString() {
216253
return String.format("VndError[logref: %s, message: %s, links: [%s]]", logref, message, getLinks().toString());
217254
}
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-
}
253255
}
254256
}

0 commit comments

Comments
 (0)