Skip to content

Commit 4f3be11

Browse files
committed
#775 - Clean up VndError implementation to match spec.
* Handle single, multiple, and nested errors. * Retain backward compatibility with existing constructors.
1 parent 6bb2310 commit 4f3be11

File tree

9 files changed

+360
-250
lines changed

9 files changed

+360
-250
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: 148 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -15,42 +15,80 @@
1515
*/
1616
package org.springframework.hateoas.mediatype.vnderrors;
1717

18+
import lombok.EqualsAndHashCode;
19+
import lombok.Getter;
20+
1821
import java.util.ArrayList;
1922
import java.util.Arrays;
23+
import java.util.Collection;
24+
import java.util.Collections;
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;
2734
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
/**
34-
* A representation model class to be rendered as specified for the media type {@code application/vnd.error}.
43+
* A representation model class to be rendered as specified for the media type {@code application/vnd.error+json}.
3544
*
3645
* @see https://github.com/blongden/vnd.error
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+
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 public static final String REL_HELP = "help";
58+
59+
/**
60+
* @deprecated Use {@link org.springframework.hateoas.IanaLinkRelations#DESCRIBES}
61+
*/
62+
@Deprecated public static final String REL_DESCRIBES = "describes";
63+
64+
/**
65+
* @deprecated Use {@link org.springframework.hateoas.IanaLinkRelations#ABOUT}
66+
*/
67+
@Deprecated public static final String REL_ABOUT = "about";
68+
69+
private final List<VndError> errors;
70+
71+
@Getter //
72+
@JsonInclude(value = JsonInclude.Include.NON_EMPTY) //
73+
private final String message;
74+
75+
@Getter //
76+
@JsonInclude(value = JsonInclude.Include.NON_EMPTY) //
77+
private final Integer logref;
78+
79+
public VndErrors() {
80+
81+
this.errors = new ArrayList<>();
82+
this.message = null;
83+
this.logref = null;
84+
}
4385

4486
/**
4587
* Creates a new {@link VndErrors} instance containing a single {@link VndError} with the given logref, message and
4688
* 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
5189
*/
5290
public VndErrors(String logref, String message, Link... links) {
53-
this(new VndError(logref, message, links));
91+
this(new VndError(message, null, Integer.parseInt(logref), links));
5492
}
5593

5694
/**
@@ -62,9 +100,11 @@ public VndErrors(VndError error, VndError... errors) {
62100

63101
Assert.notNull(error, "Error must not be null");
64102

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

70110
/**
@@ -73,38 +113,84 @@ public VndErrors(VndError error, VndError... errors) {
73113
* @param errors must not be {@literal null} or empty.
74114
*/
75115
@JsonCreator
76-
public VndErrors(List<VndError> errors) {
116+
public VndErrors(@JsonProperty("_embedded") List<VndError> errors, @JsonProperty("message") String message,
117+
@JsonProperty("logref") Integer logref, @JsonProperty("_links") Links links) {
118+
119+
Assert.notNull(errors, "Errors must not be null!"); // Retain for compatibility
120+
Assert.notEmpty(errors, "Errors must not be empty!");
121+
122+
this.errors = errors;
123+
this.message = message;
124+
this.logref = logref;
125+
126+
if (links != null && !links.isEmpty()) {
127+
add(links);
128+
}
129+
}
130+
131+
public VndErrors withMessage(String message) {
132+
return new VndErrors(this.errors, message, this.logref, this.getLinks());
133+
}
77134

78-
Assert.notNull(errors, "Errors must not be null!");
79-
Assert.isTrue(!errors.isEmpty(), "Errors must not be empty!");
80-
this.vndErrors = errors;
135+
public VndErrors withLogref(Integer logref) {
136+
return new VndErrors(this.errors, this.message, logref, this.getLinks());
137+
}
138+
139+
public VndErrors withErrors(List<VndError> errors) {
140+
141+
Assert.notNull(errors, "errors must not be null!");
142+
Assert.notEmpty(errors, "errors must not empty!");
143+
144+
return new VndErrors(errors, this.message, this.logref, this.getLinks());
145+
}
146+
147+
public VndErrors withError(VndError error) {
148+
149+
this.errors.add(error);
150+
return new VndErrors(this.errors, this.message, this.logref, this.getLinks());
151+
}
152+
153+
public VndErrors withLink(Link link) {
154+
155+
add(link);
156+
return new VndErrors(this.errors, this.message, this.logref, this.getLinks());
157+
}
158+
159+
public VndErrors withLinks(Link... links) {
160+
161+
add(links);
162+
return new VndErrors(this.errors, this.message, this.logref, this.getLinks());
81163
}
82164

83165
/**
84-
* Protected default constructor to allow JAXB marshalling.
166+
* Returns the underlying elements.
167+
*
168+
* @return the content will never be {@literal null}.
85169
*/
86-
protected VndErrors() {
87-
this.vndErrors = new ArrayList<>();
170+
@Override
171+
public Collection<VndError> getContent() {
172+
return this.errors;
88173
}
89174

90175
/**
91-
* Adds an additional {@link VndError} to the wrapper.
92-
*
93-
* @param error
176+
* Virtual attribute to generate JSON field of {@literal total}. Only generated when there are multiple errors.
94177
*/
95-
public VndErrors add(VndError error) {
96-
this.vndErrors.add(error);
97-
return this;
178+
@JsonInclude(JsonInclude.Include.NON_NULL)
179+
public Integer getTotal() {
180+
return this.errors.size() > 1 //
181+
? this.errors.size() //
182+
: null; //
98183
}
99184

100185
/**
101-
* Dummy method to allow {@link JsonValue} to be configured.
186+
* Adds an additional {@link VndError} to the wrapper.
102187
*
103-
* @return the vndErrors
188+
* @param error
189+
* @deprecated Use {{@link #withError(VndError)}}
104190
*/
105-
@JsonValue
106-
private List<VndError> getErrors() {
107-
return vndErrors;
191+
@Deprecated
192+
public VndErrors add(VndError error) {
193+
return withError(error);
108194
}
109195

110196
/*
@@ -113,142 +199,70 @@ private List<VndError> getErrors() {
113199
*/
114200
@Override
115201
public Iterator<VndErrors.VndError> iterator() {
116-
return this.vndErrors.iterator();
202+
return this.errors.iterator();
117203
}
118204

119-
/*
120-
* (non-Javadoc)
121-
* @see java.lang.Object#toString()
122-
*/
123205
@Override
124206
public String toString() {
125-
return String.format("VndErrors[%s]", StringUtils.collectionToCommaDelimitedString(vndErrors));
126-
}
127-
128-
/*
129-
* (non-Javadoc)
130-
* @see java.lang.Object#hashCode()
131-
*/
132-
@Override
133-
public int hashCode() {
134-
return vndErrors.hashCode();
135-
}
136-
137-
/*
138-
* (non-Javadoc)
139-
* @see java.lang.Object#equals(java.lang.Object)
140-
*/
141-
@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);
207+
return String.format("VndErrors[%s]", StringUtils.collectionToCommaDelimitedString(this.errors));
154208
}
155209

156210
/**
157211
* A single {@link VndError}.
158212
*
159213
* @author Oliver Gierke
214+
* @author Greg Turnquist
160215
*/
216+
@JsonPropertyOrder({ "message", "path", "logref" })
217+
@Relation(collectionRelation = "errors")
218+
@EqualsAndHashCode
161219
public static class VndError extends RepresentationModel<VndError> {
162220

163-
@JsonProperty private final String logref;
164-
@JsonProperty private final String message;
221+
@Getter //
222+
private final String message;
223+
224+
@Getter(onMethod = @__(@JsonInclude(JsonInclude.Include.NON_EMPTY))) //
225+
private final String path;
226+
227+
@Getter(onMethod = @__(@JsonInclude(JsonInclude.Include.NON_EMPTY))) //
228+
private final Integer logref;
165229

166230
/**
167-
* Creates a new {@link VndError} with the given logref, a message as well as some {@link Link}s.
231+
* Creates a new {@link VndError} with a message and optional a path and a logref.
168232
*
169-
* @param logref must not be {@literal null} or empty.
170233
* @param message must not be {@literal null} or empty.
234+
* @param path
235+
* @param logref must not be {@literal null} or empty.
171236
* @param links
172237
*/
173-
public VndError(String logref, String message, Link... links) {
238+
@JsonCreator
239+
public VndError(@JsonProperty("message") String message, @JsonProperty("path") String path,
240+
@JsonProperty("logref") Integer logref, @JsonProperty("_links") List<Link> links) {
174241

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

178-
this.logref = logref;
179244
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;
245+
this.path = path;
246+
this.logref = logref;
247+
this.add(links);
190248
}
191249

192-
/**
193-
* Returns the logref of the error.
194-
*
195-
* @return the logref
196-
*/
197-
public String getLogref() {
198-
return logref;
250+
public VndError(String message, String path, Integer logref, Link... link) {
251+
this(message, path, logref, Arrays.asList(link));
199252
}
200253

201254
/**
202-
* Returns the message of the error.
203-
*
204-
* @return the message
255+
* @deprecated Use {@link #VndError(String, String, Integer, Link...)} (with proper ordering of arguments)
205256
*/
206-
public String getMessage() {
207-
return message;
257+
@Deprecated
258+
public VndError(String logref, String message, Link... links) {
259+
this(message, null, Integer.parseInt(logref), Arrays.asList(links));
208260
}
209261

210-
/*
211-
* (non-Javadoc)
212-
* @see org.springframework.hateoas.ResourceSupport#toString()
213-
*/
214262
@Override
215263
public String toString() {
216-
return String.format("VndError[logref: %s, message: %s, links: [%s]]", logref, message, getLinks().toString());
217-
}
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);
264+
return String.format("VndError[logref: %s, message: %s, links: [%s]]", this.logref, this.message,
265+
getLinks().toString());
252266
}
253267
}
254268
}

0 commit comments

Comments
 (0)