Skip to content

Commit

Permalink
Add equals/hashCode methods to ProblemDetail
Browse files Browse the repository at this point in the history
Closes gh-29606
  • Loading branch information
jhoeller committed Dec 1, 2022
1 parent e47978e commit 1e4c10c
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 48 deletions.
123 changes: 75 additions & 48 deletions spring-web/src/main/java/org/springframework/http/ProblemDetail.java
Expand Up @@ -22,6 +22,7 @@

import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

/**
* Representation for an RFC 7807 problem detail. Includes spec-defined
Expand All @@ -40,8 +41,8 @@
* {@link org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler}.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 6.0
*
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7807">RFC 7807</a>
* @see org.springframework.web.ErrorResponse
* @see org.springframework.web.ErrorResponseException
Expand Down Expand Up @@ -108,6 +109,13 @@ public void setType(URI type) {
this.type = type;
}

/**
* Return the configured {@link #setType(URI) problem type}.
*/
public URI getType() {
return this.type;
}

/**
* Setter for the {@link #getTitle() problem title}.
* <p>By default, if not explicitly set and the status is well-known, this
Expand All @@ -118,6 +126,20 @@ public void setTitle(@Nullable String title) {
this.title = title;
}

/**
* Return the configured {@link #setTitle(String) problem title}.
*/
@Nullable
public String getTitle() {
if (this.title == null) {
HttpStatus httpStatus = HttpStatus.resolve(this.status);
if (httpStatus != null) {
return httpStatus.getReasonPhrase();
}
}
return this.title;
}

/**
* Setter for the {@link #getStatus() problem status}.
* @param httpStatus the problem status
Expand All @@ -134,6 +156,14 @@ public void setStatus(int status) {
this.status = status;
}

/**
* Return the status associated with the problem, provided either to the
* constructor or configured via {@link #setStatus(int)}.
*/
public int getStatus() {
return this.status;
}

/**
* Setter for the {@link #getDetail() problem detail}.
* <p>By default, this is not set.
Expand All @@ -143,6 +173,14 @@ public void setDetail(@Nullable String detail) {
this.detail = detail;
}

/**
* Return the configured {@link #setDetail(String) problem detail}.
*/
@Nullable
public String getDetail() {
return this.detail;
}

/**
* Setter for the {@link #getInstance() problem instance}.
* <p>By default, when {@code ProblemDetail} is returned from an
Expand All @@ -153,6 +191,14 @@ public void setInstance(@Nullable URI instance) {
this.instance = instance;
}

/**
* Return the configured {@link #setInstance(URI) problem instance}.
*/
@Nullable
public URI getInstance() {
return this.instance;
}

/**
* Set a "dynamic" property to be added to a generic {@link #getProperties()
* properties map}.
Expand All @@ -168,52 +214,6 @@ public void setProperty(String name, Object value) {
this.properties.put(name, value);
}


/**
* Return the configured {@link #setType(URI) problem type}.
*/
public URI getType() {
return this.type;
}

/**
* Return the configured {@link #setTitle(String) problem title}.
*/
@Nullable
public String getTitle() {
if (this.title == null) {
HttpStatus httpStatus = HttpStatus.resolve(this.status);
if (httpStatus != null) {
return httpStatus.getReasonPhrase();
}
}
return this.title;
}

/**
* Return the status associated with the problem, provided either to the
* constructor or configured via {@link #setStatus(int)}.
*/
public int getStatus() {
return this.status;
}

/**
* Return the configured {@link #setDetail(String) problem detail}.
*/
@Nullable
public String getDetail() {
return this.detail;
}

/**
* Return the configured {@link #setInstance(URI) problem instance}.
*/
@Nullable
public URI getInstance() {
return this.instance;
}

/**
* Return a generic map of properties that are not known ahead of time,
* possibly {@code null} if no properties have been added. To add a property,
Expand All @@ -229,6 +229,33 @@ public Map<String, Object> getProperties() {
}


@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof ProblemDetail otherDetail)) {
return false;
}
return (this.type.equals(otherDetail.type) &&
ObjectUtils.nullSafeEquals(this.title, otherDetail.title) &&
this.status == otherDetail.status &&
ObjectUtils.nullSafeEquals(this.detail, otherDetail.detail) &&
ObjectUtils.nullSafeEquals(this.instance, otherDetail.instance) &&
ObjectUtils.nullSafeEquals(this.properties, otherDetail.properties));
}

@Override
public int hashCode() {
int result = this.type.hashCode();
result = 31 * result + ObjectUtils.nullSafeHashCode(this.title);
result = 31 * result + this.status;
result = 31 * result + ObjectUtils.nullSafeHashCode(this.detail);
result = 31 * result + ObjectUtils.nullSafeHashCode(this.instance);
result = 31 * result + ObjectUtils.nullSafeHashCode(this.properties);
return result;
}

@Override
public String toString() {
return getClass().getSimpleName() + "[" + initToStringContent() + "]";
Expand All @@ -239,7 +266,7 @@ public String toString() {
* Subclasses can override this to append additional fields.
*/
protected String initToStringContent() {
return "type='" + this.type + "'" +
return "type='" + getType() + "'" +
", title='" + getTitle() + "'" +
", status=" + getStatus() +
", detail='" + getDetail() + "'" +
Expand Down
@@ -0,0 +1,53 @@
/*
* Copyright 2002-2022 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
*
* https://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.http;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Unit tests for {@link ProblemDetail}.
*
* @author Juergen Hoeller
*/
class ProblemDetailTests {

@Test
void equalsAndHashCode() {
ProblemDetail pd1 = ProblemDetail.forStatus(500);
ProblemDetail pd2 = ProblemDetail.forStatus(HttpStatus.INTERNAL_SERVER_ERROR);
ProblemDetail pd3 = ProblemDetail.forStatus(HttpStatus.NOT_FOUND);
ProblemDetail pd4 = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, "some detail");

assertThat(pd1).isEqualTo(pd2);
assertThat(pd2).isEqualTo(pd1);
assertThat(pd1.hashCode()).isEqualTo(pd2.hashCode());

assertThat(pd3).isNotEqualTo(pd4);
assertThat(pd4).isNotEqualTo(pd3);
assertThat(pd3.hashCode()).isNotEqualTo(pd4.hashCode());

assertThat(pd1).isNotEqualTo(pd3);
assertThat(pd1).isNotEqualTo(pd4);
assertThat(pd2).isNotEqualTo(pd3);
assertThat(pd2).isNotEqualTo(pd4);
assertThat(pd1.hashCode()).isNotEqualTo(pd3.hashCode());
assertThat(pd1.hashCode()).isNotEqualTo(pd4.hashCode());
}

}

0 comments on commit 1e4c10c

Please sign in to comment.