001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.oauth2.sdk;
019
020
021import java.net.URI;
022
023import com.nimbusds.oauth2.sdk.http.HTTPResponse;
024import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
025import net.jcip.annotations.Immutable;
026import net.minidev.json.JSONObject;
027
028
029/**
030 * Error object, used to encapsulate OAuth 2.0 and other errors.
031 *
032 * <p>Example error object as HTTP response:
033 *
034 * <pre>
035 * HTTP/1.1 400 Bad Request
036 * Content-Type: application/json;charset=UTF-8
037 * Cache-Control: no-store
038 * Pragma: no-cache
039 *
040 * {
041 *   "error" : "invalid_request"
042 * }
043 * </pre>
044 */
045@Immutable
046public class ErrorObject {
047        
048        
049        /**
050         * The error code, may not always be defined.
051         */
052        private final String code;
053
054
055        /**
056         * Optional error description.
057         */
058        private final String description;
059
060
061        /**
062         * Optional HTTP status code, 0 if not specified.
063         */
064        private final int httpStatusCode;
065
066
067        /**
068         * Optional URI of a web page that includes additional information 
069         * about the error.
070         */
071        private final URI uri;
072
073
074        /**
075         * Creates a new error with the specified code.
076         *
077         * @param code The error code, {@code null} if not specified.
078         */
079        public ErrorObject(final String code) {
080        
081                this(code, null, 0, null);
082        }
083        
084        
085        /**
086         * Creates a new error with the specified code and description.
087         *
088         * @param code        The error code, {@code null} if not specified.
089         * @param description The error description, {@code null} if not
090         *                    specified.
091         */
092        public ErrorObject(final String code, final String description) {
093        
094                this(code, description, 0, null);
095        }
096
097
098        /**
099         * Creates a new error with the specified code, description and HTTP 
100         * status code.
101         *
102         * @param code           The error code, {@code null} if not specified.
103         * @param description    The error description, {@code null} if not
104         *                       specified.
105         * @param httpStatusCode The HTTP status code, zero if not specified.
106         */
107        public ErrorObject(final String code, final String description, 
108                           final int httpStatusCode) {
109        
110                this(code, description, httpStatusCode, null);
111        }
112
113
114        /**
115         * Creates a new error with the specified code, description, HTTP 
116         * status code and page URI.
117         *
118         * @param code           The error code, {@code null} if not specified.
119         * @param description    The error description, {@code null} if not
120         *                       specified.
121         * @param httpStatusCode The HTTP status code, zero if not specified.
122         * @param uri            The error page URI, {@code null} if not
123         *                       specified.
124         */
125        public ErrorObject(final String code, final String description, 
126                           final int httpStatusCode, final URI uri) {
127        
128                this.code = code;
129                this.description = description;
130                this.httpStatusCode = httpStatusCode;
131                this.uri = uri;
132        }
133
134
135        /**
136         * Gets the error code.
137         *
138         * @return The error code, {@code null} if not specified.
139         */
140        public String getCode() {
141
142                return code;
143        }
144        
145        
146        /**
147         * Gets the error description.
148         *
149         * @return The error description, {@code null} if not specified.
150         */
151        public String getDescription() {
152        
153                return description;
154        }
155
156
157        /**
158         * Sets the error description.
159         *
160         * @param description The error description, {@code null} if not 
161         *                    specified.
162         *
163         * @return A copy of this error with the specified description.
164         */
165        public ErrorObject setDescription(final String description) {
166
167                return new ErrorObject(getCode(), description, getHTTPStatusCode(), getURI());
168        }
169
170
171        /**
172         * Appends the specified text to the error description.
173         *
174         * @param text The text to append to the error description, 
175         *             {@code null} if not specified.
176         *
177         * @return A copy of this error with the specified appended 
178         *         description.
179         */
180        public ErrorObject appendDescription(final String text) {
181
182                String newDescription;
183
184                if (getDescription() != null)
185                        newDescription = getDescription() + text;
186                else
187                        newDescription = text;
188
189                return new ErrorObject(getCode(), newDescription, getHTTPStatusCode(), getURI());
190        }
191
192
193        /**
194         * Gets the HTTP status code.
195         *
196         * @return The HTTP status code, zero if not specified.
197         */
198        public int getHTTPStatusCode() {
199
200                return httpStatusCode;
201        }
202
203
204        /**
205         * Sets the HTTP status code.
206         *
207         * @param httpStatusCode  The HTTP status code, zero if not specified.
208         *
209         * @return A copy of this error with the specified HTTP status code.
210         */
211        public ErrorObject setHTTPStatusCode(final int httpStatusCode) {
212
213                return new ErrorObject(getCode(), getDescription(), httpStatusCode, getURI());
214        }
215
216
217        /**
218         * Gets the error page URI.
219         *
220         * @return The error page URI, {@code null} if not specified.
221         */
222        public URI getURI() {
223
224                return uri;
225        }
226
227
228        /**
229         * Sets the error page URI.
230         *
231         * @param uri The error page URI, {@code null} if not specified.
232         *
233         * @return A copy of this error with the specified page URI.
234         */
235        public ErrorObject setURI(final URI uri) {
236
237                return new ErrorObject(getCode(), getDescription(), getHTTPStatusCode(), uri);
238        }
239
240
241        /**
242         * Returns a JSON object representation of this error object.
243         *
244         * <p>Example:
245         *
246         * <pre>
247         * {
248         *   "error"             : "invalid_grant",
249         *   "error_description" : "Invalid resource owner credentials"
250           }
251         * </pre>
252         *
253         * @return The JSON object.
254         */
255        public JSONObject toJSONObject() {
256
257                JSONObject o = new JSONObject();
258
259                if (code != null) {
260                        o.put("error", code);
261                }
262
263                if (description != null) {
264                        o.put("error_description", description);
265                }
266
267                if (uri != null) {
268                        o.put("error_uri", uri.toString());
269                }
270
271                return o;
272        }
273
274
275        /**
276         * @see #getCode
277         */
278        @Override
279        public String toString() {
280
281                return code != null ? code : "null";
282        }
283
284
285        @Override
286        public int hashCode() {
287
288                return code != null ? code.hashCode() : "null".hashCode();
289        }
290
291
292        @Override
293        public boolean equals(final Object object) {
294        
295                return object instanceof ErrorObject &&
296                       this.toString().equals(object.toString());
297        }
298
299
300        /**
301         * Parses an error object from the specified JSON object.
302         *
303         * @param jsonObject The JSON object to parse. Must not be
304         *                   {@code null}.
305         *
306         * @return The error object.
307         */
308        public static ErrorObject parse(final JSONObject jsonObject) {
309
310                String code = null;
311                String description = null;
312                URI uri = null;
313
314                try {
315                        code = JSONObjectUtils.getString(jsonObject, "error", null);
316                        description = JSONObjectUtils.getString(jsonObject, "error_description", null);
317                        uri = JSONObjectUtils.getURI(jsonObject, "error_uri", null);
318                } catch (ParseException e) {
319                        // ignore and continue
320                }
321
322                return new ErrorObject(code, description, 0, uri);
323        }
324
325
326        /**
327         * Parses an error object from the specified HTTP response.
328         *
329         * @param httpResponse The HTTP response to parse. Must not be
330         *                     {@code null}.
331         *
332         * @return The error object.
333         */
334        public static ErrorObject parse(final HTTPResponse httpResponse) {
335
336                JSONObject jsonObject;
337
338                try {
339                        jsonObject = httpResponse.getContentAsJSONObject();
340
341                } catch (ParseException e) {
342
343                        return new ErrorObject(null, null, httpResponse.getStatusCode());
344                }
345
346                ErrorObject intermediary = parse(jsonObject);
347
348                return new ErrorObject(
349                        intermediary.getCode(),
350                        intermediary.description,
351                        httpResponse.getStatusCode(),
352                        intermediary.getURI());
353        }
354}