WIP: WebClient
[facebook-errors] / src / main / java / de / juplo / facebook / errors / GraphApiException.java
1 package de.juplo.facebook.errors;
2
3
4 import com.fasterxml.jackson.core.JsonProcessingException;
5 import com.fasterxml.jackson.databind.DeserializationFeature;
6 import com.fasterxml.jackson.databind.ObjectMapper;
7 import com.fasterxml.jackson.databind.SerializationFeature;
8 import java.io.ByteArrayInputStream;
9 import java.io.IOException;
10 import java.io.InputStream;
11 import org.slf4j.Logger;
12 import org.slf4j.LoggerFactory;
13 import org.springframework.core.io.buffer.DataBufferUtils;
14 import org.springframework.http.HttpHeaders;
15 import org.springframework.http.HttpStatus;
16 import org.springframework.web.reactive.function.BodyExtractors;
17 import org.springframework.web.reactive.function.client.ClientResponse;
18 import reactor.core.publisher.Mono;
19
20
21
22 /**
23  * Base exception for Facebook Graph-Api exceptions.
24  * 
25  * @author Kai Moritz
26  */
27 public class GraphApiException extends RuntimeException
28 {
29   public enum Type { OAuthException, GraphMethodException }
30
31
32   final static Logger LOG = LoggerFactory.getLogger(GraphApiException.class);
33   final static ObjectMapper OBJECT_MAPPER;
34
35   public final HttpStatus status;
36   public final HttpHeaders headers;
37   public final FacebookErrorMessage error;
38
39
40   static
41   {
42     OBJECT_MAPPER = new ObjectMapper();
43     OBJECT_MAPPER.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
44     OBJECT_MAPPER.configure(DeserializationFeature.ACCEPT_FLOAT_AS_INT, false);
45     OBJECT_MAPPER.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
46   }
47
48
49
50   public static Mono<GraphApiException> create(ClientResponse response)
51   {
52                 return DataBufferUtils.join(response.body(BodyExtractors.toDataBuffers()))
53                                 .map(dataBuffer -> {
54                                         byte[] bytes = new byte[dataBuffer.readableByteCount()];
55                                         dataBuffer.read(bytes);
56                                         DataBufferUtils.release(dataBuffer);
57                                         return bytes;
58                                 })
59                                 .defaultIfEmpty(new byte[0])
60                                 .map(bytes -> create(response.statusCode(), response.headers().asHttpHeaders(), bytes));
61   }
62
63   public static GraphApiException create(
64       HttpStatus status,
65       HttpHeaders headers,
66       InputStream in
67       )
68   {
69     try
70     {
71       return create(status, headers, OBJECT_MAPPER.readValue(in, FacebookErrorMessage.class));
72     }
73     catch (IOException | RuntimeException e)
74     {
75       return new ErrorResponseParsingErrorException(status, headers, e);
76     }
77   }
78
79   public static GraphApiException create(
80       HttpStatus status,
81       HttpHeaders headers,
82       byte[] message
83       )
84   {
85     return create(status, headers, new ByteArrayInputStream(message));
86   }
87
88   public static GraphApiException create(
89       HttpStatus status,
90       HttpHeaders headers,
91       FacebookErrorMessage error
92       )
93   {
94     // see: http://fbdevwiki.com/wiki/Error_codes
95     switch(error.code)
96     {
97       // 1..99: general errors
98       case 1:     return new UnknownErrorException(status, headers, error);
99       case 2:     return new UnexpectedErrorException(status, headers, error);
100       case 4:     return new ApplicationRequestLimitReachedException(status, headers, error);
101       case 10:    return new AuthorizationMissingException(status, headers, error);
102       case 12:    return new DeprecatedException(status, headers, error);
103       case 17:    return new AccountRequestLimitReachedException(status, headers, error);
104       case 21:    return new PageMigratedException(status, headers, error);
105       case 32:    return new PageRequestLimitReachedException(status, headers, error);
106       // 100..199: graph method errors
107       case 100:   return new UnsupportedGetRequestException(status, headers, error);
108       case 102:   return new UserAccessTokenRequiredException(status, headers, error);
109       case 104:   return new AccessTokenRequiredException(status, headers, error);
110       case 190:   return new AccessTokenExpiredException(status, headers, error);
111       // 200..299: permission errors
112       case 200:   return new ApplicationNotAuthorizedByUserException(status, headers, error);
113       case 201:
114       case 202:
115       case 203:
116       case 204:
117       case 205:
118       case 206:
119       case 207:
120       case 208:
121       case 209:
122       case 210:
123       case 211:
124       case 212:
125       case 213:
126       case 214:
127       case 215:
128       case 216:
129       case 217:
130       case 218:
131       case 219:
132       case 220:
133       case 221:
134       case 222:
135       case 223:
136       case 224:
137       case 225:
138       case 226:
139       case 227:
140       case 228:
141       case 229:
142       case 230:
143       case 231:
144       case 232:
145       case 233:
146       case 234:
147       case 235:
148       case 236:
149       case 237:
150       case 238:
151       case 239:
152       case 240:
153       case 241:
154       case 242:
155       case 243:
156       case 244:
157       case 245:
158       case 246:
159       case 247:
160       case 248:
161       case 249:
162       case 250:
163       case 251:
164       case 252:
165       case 253:
166       case 254:
167       case 255:
168       case 256:
169       case 257:
170       case 258:
171       case 259:
172       case 260:
173       case 261:
174       case 262:
175       case 263:
176       case 264:
177       case 265:
178       case 266:
179       case 267:
180       case 268:
181       case 269:
182       case 270:
183       case 271:
184       case 272:
185       case 273:
186       case 274:
187       case 275:
188       case 276:
189       case 277:
190       case 278:
191       case 279:
192       case 280:
193       case 281:
194       case 282:
195       case 283:
196       case 284:
197       case 285:
198       case 286:
199       case 287:
200       case 288:
201       case 289:
202       case 290:
203       case 291:
204       case 292:
205       case 293:
206       case 294:
207       case 295:
208       case 296:
209       case 297:
210       case 298:
211       case 299:   return new AuthorizationMissingException(status, headers, error);
212       // 200..299: permission errors
213       // 300..399: data editing errors ?
214       case 341:   return new TemporaryRateLimitExceededException(status, headers, error);
215       // 400..449: authentication error
216       // 450..499: session errors
217       // 500..599: application messaging errors ?
218       case 506:   return new MultipleConcurrentPostsException(status, headers, error);
219       // 600..699: FQL errors
220       case 613:   return new RateLimitExceededException(status, headers, error);
221       // 700..749: ref errors
222       // 750..799: application integration errors
223       // 900..949: application information errors
224       // 950..999: batch api errors
225       // 1000..1099: event api errors
226       // 1100..1199: live-message errors
227       case 1609005: return new LinkPostFailureException(status, headers, error);
228       case 2200:  return new CallbackVerificationFailedException(status, headers, error);
229
230       default:
231         GraphApiException e = new UnmappedErrorException(status, headers, error);
232         LOG.info("unmapped error: {}", e.toString());
233         return e;
234     }
235   }
236
237
238   protected GraphApiException(
239       HttpStatus status,
240       HttpHeaders headers,
241       FacebookErrorMessage error
242       )
243   {
244     super(error.message);
245     this.status = status;
246     this.headers = headers;
247     this.error = error;
248   }
249
250
251   public HttpStatus getStatus()
252   {
253     return status;
254   }
255
256   public HttpHeaders getHeaders()
257   {
258     return headers;
259   }
260
261   public Type getType()
262   {
263     return error.type == null ? null : Type.valueOf(error.type);
264   }
265
266   public Integer getCode()
267   {
268     return error.code;
269   }
270
271   public Integer getSubCode()
272   {
273     return error.subCode;
274   }
275
276   public String getUserTitle()
277   {
278     return error.userTitle;
279   }
280
281   public String getUserMessage()
282   {
283     return error.userMessage;
284   }
285
286   public String getTraceId()
287   {
288     return error.traceId;
289   }
290
291
292   @Override
293   public String toString()
294   {
295     try
296     {
297       return OBJECT_MAPPER.writeValueAsString(error);
298     }
299     catch(JsonProcessingException e)
300     {
301       // This should never happen. But in case of a mistake: be verbose!
302       LOG.error("could not convert message into JSON: {}", e);
303       return e.getMessage();
304     }
305   }
306 }