001/* 002 * Copyright 2024 Vonage 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.vonage.client; 017 018import com.fasterxml.jackson.annotation.JsonIgnore; 019import com.fasterxml.jackson.annotation.JsonProperty; 020import com.fasterxml.jackson.annotation.JsonSetter; 021import com.fasterxml.jackson.core.JsonProcessingException; 022import com.fasterxml.jackson.databind.introspect.AnnotatedMember; 023import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; 024import java.net.URI; 025import java.util.List; 026import java.util.Objects; 027 028/** 029 * Base class for exceptions which represent an error API response conforming to 030 * <a href=https://www.rfc-editor.org/rfc/rfc7807>RFC 7807</a> standard. 031 */ 032public class VonageApiResponseException extends VonageClientException implements Jsonable { 033 protected URI type; 034 protected String title, detail, instance, code, errorCodeLabel, errorCode; 035 protected List<?> errors, invalidParameters; 036 @JsonIgnore protected int statusCode; 037 038 protected VonageApiResponseException() { 039 } 040 041 protected VonageApiResponseException(String message) { 042 super(message); 043 } 044 045 protected VonageApiResponseException(String message, Throwable cause) { 046 super(message, cause); 047 } 048 049 protected VonageApiResponseException(Throwable cause) { 050 super(cause); 051 } 052 053 @JsonSetter("error-code") 054 private void setErrorCode(String errorCode) { 055 if (errorCode != null && !errorCode.trim().isEmpty()) { 056 statusCode = Integer.parseInt(errorCode); 057 } 058 } 059 060 /** 061 * The status code description. 062 * 063 * @return The error code label, or {@code null} if unknown. 064 */ 065 @JsonProperty("error-code-label") 066 public String getErrorCodeLabel() { 067 return errorCodeLabel; 068 } 069 070 /** 071 * Link to the <a href=https://developer.vonage.com/en/api-errors>API error type</a>. 072 * 073 * @return URL of the error type / description. 074 */ 075 @JsonProperty("type") 076 public URI getType() { 077 return type; 078 } 079 080 /** 081 * Brief description / name of the error. 082 * 083 * @return The error title. 084 */ 085 @JsonProperty("title") 086 public String getTitle() { 087 return title; 088 } 089 090 /** 091 * Extended description of the error, explaining the cause. 092 * 093 * @return The detailed error description. 094 */ 095 @JsonProperty("detail") 096 public String getDetail() { 097 return detail; 098 } 099 100 /** 101 * Internal trace ID of the request. 102 * 103 * @return The instance ID. 104 */ 105 @JsonProperty("instance") 106 public String getInstance() { 107 return instance; 108 } 109 110 /** 111 * Additional description of problems encountered with the request. 112 * This is typically only applicable to 400 or 409 error codes. 113 * 114 * @return The list of errors returned from the server (could be a Map or String), 115 * or {@code null} if none / not applicable. 116 */ 117 @JsonProperty("errors") 118 public List<?> getErrors() { 119 return errors; 120 } 121 122 /** 123 * If the request was rejected due to invalid parameters, this will return the 124 * offending parameters, sometimes along with a description of the errors. 125 * 126 * @return The list of invalid parameters, typically as a Map, or {@code null} if not applicable. 127 * 128 * @since 7.7.0 129 */ 130 @JsonProperty("invalid_parameters") 131 public List<?> getInvalidParameters() { 132 return invalidParameters; 133 } 134 135 /** 136 * Name of the error code. 137 * 138 * @return The Vonage error code as a string, or {@code null} if unknown / not applicable. 139 */ 140 @JsonProperty("code") 141 public String getCode() { 142 return code; 143 } 144 145 /** 146 * The API response status code, usually 4xx or 500. 147 * 148 * @return The status code. 149 */ 150 @JsonIgnore 151 public int getStatusCode() { 152 return statusCode; 153 } 154 155 @JsonIgnore 156 @Override 157 public String getMessage() { 158 if (statusCode > 0 && title != null) { 159 String message = statusCode+" ("+title+")"; 160 if (detail != null) { 161 message += ": "+detail; 162 } 163 return message; 164 } 165 else return super.getMessage(); 166 } 167 168 private static class IgnoreInheritedIntrospector extends JacksonAnnotationIntrospector { 169 @Override 170 public boolean hasIgnoreMarker(final AnnotatedMember m) { 171 return m.getDeclaringClass().equals(Throwable.class) || super.hasIgnoreMarker(m); 172 } 173 } 174 175 @Override 176 public boolean equals(Object o) { 177 if (this == o) return true; 178 if (o == null || getClass() != o.getClass()) return false; 179 VonageApiResponseException response = (VonageApiResponseException) o; 180 return statusCode == response.statusCode && 181 Objects.equals(type, response.type) && 182 Objects.equals(title, response.title) && 183 Objects.equals(detail, response.detail) && 184 Objects.equals(instance, response.instance); 185 } 186 187 @Override 188 public int hashCode() { 189 return Objects.hash(type, title, detail, instance, statusCode); 190 } 191 192 /** 193 * Generates JSON from this object. Excludes fields inherited from superclasses. 194 * 195 * @return The JSON representation of this response object. 196 */ 197 @Override 198 public String toJson() { 199 try { 200 return Jsonable.createDefaultObjectMapper() 201 .setAnnotationIntrospector(new IgnoreInheritedIntrospector()) 202 .writeValueAsString(this); 203 } 204 catch (JsonProcessingException e) { 205 throw new VonageUnexpectedException("Failed to produce JSON from "+getClass().getSimpleName(), e); 206 } 207 } 208 209 protected static <E extends VonageApiResponseException> E fromJson(Class<E> clazz, String json) { 210 if (json == null || json.length() < 2) { 211 try { 212 return clazz.getConstructor().newInstance(); 213 } 214 catch (Exception ex) { 215 throw new VonageUnexpectedException(ex); 216 } 217 } 218 else return Jsonable.fromJson(json, clazz); 219 } 220}