Skip to content

Commit

Permalink
#775 - Overhaul VndErrors to match spec.
Browse files Browse the repository at this point in the history
Handles:

* single item error
* multiple errors
* nested errors

See https://github.com/blongden/vnd.error for details on the vnd.error spec.
  • Loading branch information
gregturn committed Dec 19, 2018
1 parent a597960 commit 286f5b2
Show file tree
Hide file tree
Showing 9 changed files with 336 additions and 267 deletions.
10 changes: 10 additions & 0 deletions src/main/java/org/springframework/hateoas/MediaTypes.java
Expand Up @@ -76,4 +76,14 @@ public class MediaTypes {
* Public constant media type for {@code application/vnd.collection+json}.
*/
public static final MediaType COLLECTION_JSON = MediaType.valueOf(COLLECTION_JSON_VALUE);

/**
* A stringl equivalent of {@link MediaTypes#VND_ERROR_JSON}.
*/
public static final String VND_ERROR_JSON_VALUE = "application/vnd.error+json";

/**
* Public constant media type for {@code application/vnd.error+json}.
*/
public static final MediaType VND_ERROR_JSON = MediaType.valueOf(VND_ERROR_JSON_VALUE);
}
262 changes: 102 additions & 160 deletions src/main/java/org/springframework/hateoas/VndErrors.java
Expand Up @@ -15,239 +15,181 @@
*/
package org.springframework.hateoas;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.experimental.Wither;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

import org.springframework.hateoas.core.Relation;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

/**
* A representation model class to be rendered as specified for the media type {@code application/vnd.error}.
* A representation model class to be rendered as specified for the media type {@code application/vnd.error+json}.
*
* @see https://github.com/blongden/vnd.error
* @author Oliver Gierke
* @author Greg Turnquist
*/
public class VndErrors implements Iterable<VndErrors.VndError> {
@JsonPropertyOrder({"message", "logref", "total", "_links", "_embedded"})
@JsonIgnoreProperties(ignoreUnknown = true)
@EqualsAndHashCode
@ToString
public class VndErrors extends Resources<VndErrors.VndError> {

private final List<VndError> vndErrors;
public static final String REL_HELP = "help";
public static final String REL_DESCRIBES = "describes";
public static final String REL_ABOUT = "about";

/**
* Creates a new {@link VndErrors} instance containing a single {@link VndError} with the given logref, message and
* optional {@link Link}s.
*
* @param logref must not be {@literal null} or empty.
* @param message must not be {@literal null} or empty.
* @param links
*/
public VndErrors(String logref, String message, Link... links) {
this(new VndError(logref, message, links));
}
private final List<VndError> errors;

/**
* Creates a new {@link VndErrors} wrapper for at least one {@link VndError}.
*
* @param errors must not be {@literal null}.
* @param errors
*/
public VndErrors(VndError error, VndError... errors) {
@Getter
@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
private final String message;

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

this.vndErrors = new ArrayList<VndError>(errors.length + 1);
this.vndErrors.add(error);
this.vndErrors.addAll(Arrays.asList(errors));
}
@Getter
@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
private final Integer logref;

/**
* Creates a new {@link VndErrors} wrapper for the given {@link VndErrors}.
*
* @param errors must not be {@literal null} or empty.
*/
@JsonCreator
public VndErrors(List<VndError> errors) {
public VndErrors(@JsonProperty("_embedded") List<VndError> errors, @JsonProperty("message") String message,
@JsonProperty("logref") Integer logref, @JsonProperty("_links") List<Link> links) {

Assert.notNull(errors, "Errors must not be null!");
Assert.isTrue(!errors.isEmpty(), "Errors must not be empty!");
this.vndErrors = errors;
Assert.notEmpty(errors, "Errors must not be empty!");

this.errors = errors;
this.message = message;
this.logref = logref;
if (links != null && !links.isEmpty()) {
add(links);
}
}

/**
* Protected default constructor to allow JAXB marshalling.
*/
protected VndErrors() {
this.vndErrors = new ArrayList<VndError>();
public VndErrors() {

this.errors = new ArrayList<>();
this.message = null;
this.logref = null;
}

/**
* Adds an additional {@link VndError} to the wrapper.
*
* @param error
*/
public VndErrors add(VndError error) {
this.vndErrors.add(error);
return this;
public VndErrors withMessage(String message) {
return new VndErrors(this.errors, message, this.logref, this.getLinks());
}

/**
* Dummy method to allow {@link JsonValue} to be configured.
*
* @return the vndErrors
*/
@JsonValue
private List<VndError> getErrors() {
return vndErrors;
public VndErrors withLogref(Integer logref) {
return new VndErrors(this.errors, this.message, logref, this.getLinks());
}

/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<VndErrors.VndError> iterator() {
return this.vndErrors.iterator();
public VndErrors withErrors(List<VndError> errors) {

Assert.notNull(errors, "errors must not be null!");
Assert.notEmpty(errors, "errors must not empty!");

return new VndErrors(errors, this.message, this.logref, this.getLinks());
}

/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("VndErrors[%s]", StringUtils.collectionToCommaDelimitedString(vndErrors));
public VndErrors withError(VndError error) {

this.errors.add(error);
return new VndErrors(this.errors, this.message, this.logref, this.getLinks());
}

/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return vndErrors.hashCode();
public VndErrors withLink(Link link) {

add(link);
return new VndErrors(this.errors, this.message, this.logref, this.getLinks());
}

/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
/**
* Returns the underlying elements.
*
* @return the content will never be {@literal null}.
*/
@Override
public boolean equals(Object obj) {

if (this == obj) {
return true;
}

if (!(obj instanceof VndErrors)) {
return false;
}
public Collection<VndError> getContent() {
return this.errors;
}

VndErrors that = (VndErrors) obj;
return this.vndErrors.equals(that.vndErrors);
/**
* Virtual attribute to generate JSON field of {@literal total}.
*/
public int getTotal() {
return this.errors.size();
}

/**
* A single {@link VndError}.
*
* @author Oliver Gierke
* @author Greg Turnquist
*/
@JsonPropertyOrder({"message", "path", "logref"})
@Relation(collectionRelation = "errors")
@EqualsAndHashCode
public static class VndError extends ResourceSupport {

@JsonProperty private final String logref;
@JsonProperty private final String message;
@Getter
private final String message;

@Getter
@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
private final String path;

@Getter
@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
private final Integer logref;

/**
* Creates a new {@link VndError} with the given logref, a message as well as some {@link Link}s.
* Creates a new {@link VndError} with a message and optional a path and a logref.
*
* @param logref must not be {@literal null} or empty.
* @param message must not be {@literal null} or empty.
* @param links
*/
public VndError(String logref, String message, Link... links) {
@JsonCreator
public VndError(@JsonProperty("message") String message, @JsonProperty("path") String path,
@JsonProperty("logref") Integer logref, @JsonProperty("_links") List<Link> links) {

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

this.logref = logref;
this.message = message;
this.add(Arrays.asList(links));
}

/**
* Protected default constructor to allow JAXB marshalling.
*/
protected VndError() {

this.logref = null;
this.message = null;
}
this.path = path;
this.logref = logref;

/**
* Returns the logref of the error.
*
* @return the logref
*/
public String getLogref() {
return logref;
this.add(links);
}

/**
* Returns the message of the error.
*
* @return the message
* Convenience constructor
*/
public String getMessage() {
return message;
public VndError(String message, String path, Integer logref, Link... links) {
this(message, path, logref, Arrays.asList(links));
}

/*
* (non-Javadoc)
* @see org.springframework.hateoas.ResourceSupport#toString()
*/
@Override
public String toString() {
return String.format("VndError[logref: %s, message: %s, links: [%s]]", logref, message,
StringUtils.collectionToCommaDelimitedString(getLinks()));
}

/*
* (non-Javadoc)
* @see org.springframework.hateoas.ResourceSupport#hashCode()
*/
@Override
public int hashCode() {

int result = 17;

result += 31 * logref.hashCode();
result += 31 * message.hashCode();

return result;
}

/*
* (non-Javadoc)
* @see org.springframework.hateoas.ResourceSupport#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {

if (obj == this) {
return true;
}

if (!(obj instanceof VndError)) {
return false;
}

VndError that = (VndError) obj;

return this.logref.equals(that.logref) && this.message.equals(that.message);
return "VndError{" +
"message='" + message + '\'' +
", path='" + path + '\'' +
", logref=" + logref +
", links=" + getLinks() +
'}';
}
}
}

0 comments on commit 286f5b2

Please sign in to comment.