|
20 | 20 | */ |
21 | 21 | package com.inrupt.client; |
22 | 22 |
|
23 | | -import com.inrupt.client.spi.JsonService; |
24 | | -import com.inrupt.client.spi.ServiceProvider; |
25 | | - |
26 | | -import java.io.ByteArrayInputStream; |
27 | | -import java.io.IOException; |
28 | | -import java.io.Serializable; |
29 | 23 | import java.net.URI; |
30 | | -import java.util.Optional; |
31 | 24 |
|
32 | 25 | /** |
33 | 26 | * A data class representing a structured problem description sent by the server on error response. |
34 | 27 | * |
35 | 28 | * @see <a href="https://www.rfc-editor.org/rfc/rfc9457">RFC 9457 Problem Details for HTTP APIs</a> |
36 | 29 | */ |
37 | | -public class ProblemDetails implements Serializable { |
38 | | - |
39 | | - private static final long serialVersionUID = -4597170432270957765L; |
| 30 | +public interface ProblemDetails { |
40 | 31 |
|
41 | 32 | /** |
42 | 33 | * The <a href="https://www.rfc-editor.org/rfc/rfc9457">RFC9457</a> default MIME type. |
43 | 34 | */ |
44 | | - public static final String MIME_TYPE = "application/problem+json"; |
45 | | - /** |
46 | | - * The <a href="https://www.rfc-editor.org/rfc/rfc9457">RFC9457</a> default problem type. |
47 | | - */ |
48 | | - public static final String DEFAULT_TYPE = "about:blank"; |
49 | | - private final URI type; |
50 | | - private final String title; |
51 | | - private final String details; |
52 | | - private final int status; |
53 | | - private final URI instance; |
54 | | - private static JsonService jsonService; |
55 | | - private static boolean isJsonServiceInitialized; |
| 35 | + String MIME_TYPE = "application/problem+json"; |
56 | 36 |
|
57 | 37 | /** |
58 | | - * Build a ProblemDetails instance providing the expected fields as described in |
59 | | - * <a href="https://www.rfc-editor.org/rfc/rfc9457">RFC9457</a>. |
60 | | - * @param type the problem type |
61 | | - * @param title the problem title |
62 | | - * @param details the problem details |
63 | | - * @param status the error response status code |
64 | | - * @param instance the problem instance |
| 38 | + * The <a href="https://www.rfc-editor.org/rfc/rfc9457">RFC9457</a> default problem type. |
65 | 39 | */ |
66 | | - public ProblemDetails( |
67 | | - final URI type, |
68 | | - final String title, |
69 | | - final String details, |
70 | | - final int status, |
71 | | - final URI instance |
72 | | - ) { |
73 | | - this.type = type; |
74 | | - this.title = title; |
75 | | - this.details = details; |
76 | | - this.status = status; |
77 | | - this.instance = instance; |
78 | | - } |
| 40 | + String DEFAULT_TYPE = "about:blank"; |
79 | 41 |
|
80 | 42 | /** |
81 | 43 | * The problem type. |
82 | 44 | * @return the type |
83 | 45 | */ |
84 | | - public URI getType() { |
85 | | - return this.type; |
86 | | - } |
| 46 | + URI getType(); |
87 | 47 |
|
88 | 48 | /** |
89 | 49 | * The problem title. |
90 | 50 | * @return the title |
91 | 51 | */ |
92 | | - public String getTitle() { |
93 | | - return this.title; |
94 | | - } |
| 52 | + String getTitle(); |
95 | 53 |
|
96 | 54 | /** |
97 | 55 | * The problem details. |
98 | 56 | * @return the details |
99 | 57 | */ |
100 | | - public String getDetails() { |
101 | | - return this.details; |
102 | | - } |
| 58 | + String getDetails(); |
103 | 59 |
|
104 | 60 | /** |
105 | 61 | * The problem status code. |
106 | 62 | * @return the status code |
107 | 63 | */ |
108 | | - public int getStatus() { |
109 | | - return this.status; |
110 | | - } |
| 64 | + int getStatus(); |
111 | 65 |
|
112 | 66 | /** |
113 | 67 | * The problem instance. |
114 | 68 | * @return the instance |
115 | 69 | */ |
116 | | - public URI getInstance() { |
117 | | - return this.instance; |
118 | | - } |
119 | | - |
120 | | - /** |
121 | | - * This inner class is only ever used for JSON deserialization. Please do not use in any other context. |
122 | | - */ |
123 | | - public static class Data { |
124 | | - /** |
125 | | - * The problem type. |
126 | | - */ |
127 | | - public URI type; |
128 | | - /** |
129 | | - * The problem title. |
130 | | - */ |
131 | | - public String title; |
132 | | - /** |
133 | | - * The problem details. |
134 | | - */ |
135 | | - public String details; |
136 | | - /** |
137 | | - * The problem status code. |
138 | | - */ |
139 | | - public int status; |
140 | | - /** |
141 | | - * The problem instance. |
142 | | - */ |
143 | | - public URI instance; |
144 | | - } |
145 | | - |
146 | | - private static JsonService getJsonService() { |
147 | | - if (ProblemDetails.isJsonServiceInitialized) { |
148 | | - return ProblemDetails.jsonService; |
149 | | - } |
150 | | - // It is a legitimate use case that this is loaded in a context where no implementation of the JSON service is |
151 | | - // available. On SPI lookup failure, the ProblemDetails exceptions will fall back to default and not be parsed. |
152 | | - try { |
153 | | - ProblemDetails.jsonService = ServiceProvider.getJsonService(); |
154 | | - } catch (IllegalStateException e) { |
155 | | - ProblemDetails.jsonService = null; |
156 | | - } |
157 | | - ProblemDetails.isJsonServiceInitialized = true; |
158 | | - return ProblemDetails.jsonService; |
159 | | - } |
160 | | - |
161 | | - /** |
162 | | - * Builds a {@link ProblemDetails} instance from an HTTP error response. |
163 | | - * @param statusCode the HTTP error response status code |
164 | | - * @param headers the HTTP error response headers |
165 | | - * @param body the HTTP error response body |
166 | | - * @return a {@link ProblemDetails} instance |
167 | | - */ |
168 | | - public static ProblemDetails fromErrorResponse( |
169 | | - final int statusCode, |
170 | | - final Headers headers, |
171 | | - final byte[] body |
172 | | - ) { |
173 | | - final JsonService jsonService = getJsonService(); |
174 | | - if (jsonService == null |
175 | | - || (headers != null && !headers.allValues("Content-Type").contains(ProblemDetails.MIME_TYPE))) { |
176 | | - return new ProblemDetails( |
177 | | - URI.create(ProblemDetails.DEFAULT_TYPE), |
178 | | - null, |
179 | | - null, |
180 | | - statusCode, |
181 | | - null |
182 | | - ); |
183 | | - } |
184 | | - try { |
185 | | - final Data pdData = jsonService.fromJson( |
186 | | - new ByteArrayInputStream(body), |
187 | | - Data.class |
188 | | - ); |
189 | | - final URI type = Optional.ofNullable(pdData.type) |
190 | | - .orElse(URI.create(ProblemDetails.DEFAULT_TYPE)); |
191 | | - // JSON mappers map invalid integers to 0, which is an invalid value in our case anyway. |
192 | | - final int status = Optional.of(pdData.status).filter(s -> s != 0).orElse(statusCode); |
193 | | - return new ProblemDetails( |
194 | | - type, |
195 | | - pdData.title, |
196 | | - pdData.details, |
197 | | - status, |
198 | | - pdData.instance |
199 | | - ); |
200 | | - } catch (IOException e) { |
201 | | - return new ProblemDetails( |
202 | | - URI.create(ProblemDetails.DEFAULT_TYPE), |
203 | | - null, |
204 | | - null, |
205 | | - statusCode, |
206 | | - null |
207 | | - ); |
208 | | - } |
209 | | - } |
| 70 | + URI getInstance(); |
210 | 71 | } |
0 commit comments