content: /**
   * Copyright 2010-present Facebook.
   *
   * 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
   *
   *    http://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 com.facebook;
  
  import com.facebook.android.R;
  import com.facebook.internal.Utility;
  import org.json.JSONException;
  import org.json.JSONObject;
  
  import java.net.HttpURLConnection;
  
  /**
   * This class represents an error that occurred during a Facebook request.
   * <p/>
   * In general, one would call {@link #getCategory()} to determine the type
   * of error that occurred, and act accordingly. The app can also call
   * {@link #getUserActionMessageId()} in order to get the resource id for a
   * string that can be displayed to the user. For more information on error
   * handling, see <a href="https://developers.facebook.com/docs/reference/api/errors/">
   * https://developers.facebook.com/docs/reference/api/errors/</a>
   */
  public final class FacebookRequestError {
  
      /** Represents an invalid or unknown error code from the server. */
      public static final int INVALID_ERROR_CODE = -1;
  
      /**
       * Indicates that there was no valid HTTP status code returned, indicating
       * that either the error occurred locally, before the request was sent, or
       * that something went wrong with the HTTP connection. Check the exception
       * from {@link #getException()};
       */
      public static final int INVALID_HTTP_STATUS_CODE = -1;
  
      private static final int INVALID_MESSAGE_ID = 0;
  
      private static final String CODE_KEY = "code";
      private static final String BODY_KEY = "body";
      private static final String ERROR_KEY = "error";
      private static final String ERROR_TYPE_FIELD_KEY = "type";
      private static final String ERROR_CODE_FIELD_KEY = "code";
      private static final String ERROR_MESSAGE_FIELD_KEY = "message";
      private static final String ERROR_CODE_KEY = "error_code";
      private static final String ERROR_SUB_CODE_KEY = "error_subcode";
      private static final String ERROR_MSG_KEY = "error_msg";
      private static final String ERROR_REASON_KEY = "error_reason";
  
      private static class Range {
          private final int start, end;
  
          private Range(int start, int end) {
              this.start = start;
              this.end = end;
          }
  
          boolean contains(int value) {
              return start <= value && value <= end;
          }
      }
  
      private static final int EC_UNKNOWN_ERROR = 1;
      private static final int EC_SERVICE_UNAVAILABLE = 2;
      private static final int EC_APP_TOO_MANY_CALLS = 4;
      private static final int EC_USER_TOO_MANY_CALLS = 17;
      private static final int EC_PERMISSION_DENIED = 10;
      private static final int EC_INVALID_SESSION = 102;
      private static final int EC_INVALID_TOKEN = 190;
      private static final Range EC_RANGE_PERMISSION = new Range(200, 299);
      private static final int EC_APP_NOT_INSTALLED = 458;
      private static final int EC_USER_CHECKPOINTED = 459;
      private static final int EC_PASSWORD_CHANGED = 460;
      private static final int EC_EXPIRED = 463;
      private static final int EC_UNCONFIRMED_USER = 464;
  
      private static final Range HTTP_RANGE_SUCCESS = new Range(200, 299);
      private static final Range HTTP_RANGE_CLIENT_ERROR = new Range(400, 499);
      private static final Range HTTP_RANGE_SERVER_ERROR = new Range(500, 599);
  
      private final int userActionMessageId;
      private final boolean shouldNotifyUser;
      private final Category category;
      private final int requestStatusCode;
      private final int errorCode;
      private final int subErrorCode;
      private final String errorType;
      private final String errorMessage;
      private final JSONObject requestResult;
      private final JSONObject requestResultBody;
      private final Object batchRequestResult;
      private final HttpURLConnection connection;
      private final FacebookException exception;
  
      private FacebookRequestError(int requestStatusCode, int errorCode,
              int subErrorCode, String errorType, String errorMessage, JSONObject requestResultBody,
              JSONObject requestResult, Object batchRequestResult, HttpURLConnection connection,
              FacebookException exception) {
          this.requestStatusCode = requestStatusCode;
          this.errorCode = errorCode;
          this.subErrorCode = subErrorCode;
          this.errorType = errorType;
          this.errorMessage = errorMessage;
          this.requestResultBody = requestResultBody;
          this.requestResult = requestResult;
          this.batchRequestResult = batchRequestResult;
          this.connection = connection;
  
          boolean isLocalException = false;
          if (exception != null) {
              this.exception = exception;
              isLocalException =  true;
          } else {
              this.exception = new FacebookServiceException(this, errorMessage);
          }
  
          // Initializes the error categories based on the documented error codes as outlined here
          // https://developers.facebook.com/docs/reference/api/errors/
          Category errorCategory = null;
          int messageId = INVALID_MESSAGE_ID;
          boolean shouldNotify = false;
          if (isLocalException) {
              errorCategory = Category.CLIENT;
              messageId = INVALID_MESSAGE_ID;
          } else {
              if (errorCode == EC_UNKNOWN_ERROR || errorCode == EC_SERVICE_UNAVAILABLE) {
                  errorCategory = Category.SERVER;
              } else if (errorCode == EC_APP_TOO_MANY_CALLS || errorCode == EC_USER_TOO_MANY_CALLS) {
                  errorCategory = Category.THROTTLING;
              } else if (errorCode == EC_PERMISSION_DENIED || EC_RANGE_PERMISSION.contains(errorCode)) {
                  errorCategory = Category.PERMISSION;
                  messageId = R.string.com_facebook_requesterror_permissions;
              } else if (errorCode == EC_INVALID_SESSION || errorCode == EC_INVALID_TOKEN) {
                  if (subErrorCode == EC_USER_CHECKPOINTED || subErrorCode == EC_UNCONFIRMED_USER) {
                      errorCategory = Category.AUTHENTICATION_RETRY;
                      messageId = R.string.com_facebook_requesterror_web_login;
                      shouldNotify = true;
                  } else {
                      errorCategory = Category.AUTHENTICATION_REOPEN_SESSION;
  
                      if ((subErrorCode == EC_APP_NOT_INSTALLED) || (subErrorCode == EC_EXPIRED)) {
                          messageId = R.string.com_facebook_requesterror_relogin;
                      } else if (subErrorCode == EC_PASSWORD_CHANGED) {
                          messageId = R.string.com_facebook_requesterror_password_changed;
                      } else {
                          messageId = R.string.com_facebook_requesterror_reconnect;
                          shouldNotify = true;
                      }
                  }
              }
  
              if (errorCategory == null) {
                  if (HTTP_RANGE_CLIENT_ERROR.contains(requestStatusCode)) {
                      errorCategory = Category.BAD_REQUEST;
                  } else if (HTTP_RANGE_SERVER_ERROR.contains(requestStatusCode)) {
                      errorCategory = Category.SERVER;
                  } else {
                      errorCategory = Category.OTHER;
                  }
              }
          }
  
          this.category = errorCategory;
          this.userActionMessageId = messageId;
          this.shouldNotifyUser = shouldNotify;
      }
  
      private FacebookRequestError(int requestStatusCode, int errorCode,
              int subErrorCode, String errorType, String errorMessage, JSONObject requestResultBody,
              JSONObject requestResult, Object batchRequestResult, HttpURLConnection connection) {
          this(requestStatusCode, errorCode, subErrorCode, errorType, errorMessage,
                  requestResultBody, requestResult, batchRequestResult, connection, null);
      }
  
      FacebookRequestError(HttpURLConnection connection, Exception exception) {
          this(INVALID_HTTP_STATUS_CODE, INVALID_ERROR_CODE, INVALID_ERROR_CODE,
                  null, null, null, null, null, connection,
                  (exception instanceof FacebookException) ?
                          (FacebookException) exception : new FacebookException(exception));
      }
  
      public FacebookRequestError(int errorCode, String errorType, String errorMessage) {
          this(INVALID_HTTP_STATUS_CODE, errorCode, INVALID_ERROR_CODE, errorType, errorMessage,
                  null, null, null, null, null);
      }
  
      /**
       * Returns the resource id for a user-friendly message for the application to
       * present to the user.
       *
       * @return a user-friendly message to present to the user
       */
      public int getUserActionMessageId() {
          return userActionMessageId;
      }
  
      /**
       * Returns whether direct user action is required to successfully continue with the Facebook
       * operation. If user action is required, apps can also call {@link #getUserActionMessageId()}
       * in order to get a resource id for a message to show the user.
       *
       * @return whether direct user action is required
       */
      public boolean shouldNotifyUser() {
          return shouldNotifyUser;
      }
  
      /**
       * Returns the category in which the error belongs. Applications can use the category
       * to determine how best to handle the errors (e.g. exponential backoff for retries if
       * being throttled).
       *
       * @return the category in which the error belong
       */
      public Category getCategory() {
          return category;
      }
  
      /**
       * Returns the HTTP status code for this particular request.
       *
       * @return the HTTP status code for the request
       */
      public int getRequestStatusCode() {
          return requestStatusCode;
      }
  
      /**
       * Returns the error code returned from Facebook.
       *
       * @return the error code returned from Facebook
       */
      public int getErrorCode() {
          return errorCode;
      }
  
      /**
       * Returns the sub-error code returned from Facebook.
       *
       * @return the sub-error code returned from Facebook
       */
      public int getSubErrorCode() {
          return subErrorCode;
      }
  
      /**
       * Returns the type of error as a raw string. This is generally less useful
       * than using the {@link #getCategory()} method, but can provide further details
       * on the error.
       *
       * @return the type of error as a raw string
       */
      public String getErrorType() {
          return errorType;
      }
  
      /**
       * Returns the error message returned from Facebook.
       *
       * @return the error message returned from Facebook
       */
      public String getErrorMessage() {
          if (errorMessage != null) {
              return errorMessage;
          } else {
              return exception.getLocalizedMessage();
          }
      }
  
      /**
       * Returns the body portion of the response corresponding to the request from Facebook.
       *
       * @return the body of the response for the request
       */
      public JSONObject getRequestResultBody() {
          return requestResultBody;
      }
  
      /**
       * Returns the full JSON response for the corresponding request. In a non-batch request,
       * this would be the raw response in the form of a JSON object. In a batch request, this
       * result will contain the body of the response as well as the HTTP headers that pertain
       * to the specific request (in the form of a "headers" JSONArray).
       *
       * @return the full JSON response for the request
       */
      public JSONObject getRequestResult() {
          return requestResult;
      }
  
      /**
       * Returns the full JSON response for the batch request. If the request was not a batch
       * request, then the result from this method is the same as {@link #getRequestResult()}.
       * In case of a batch request, the result will be a JSONArray where the elements
       * correspond to the requests in the batch. Callers should check the return type against
       * either JSONObject or JSONArray and cast accordingly.
       *
       * @return the full JSON response for the batch
       */
      public Object getBatchRequestResult() {
          return batchRequestResult;
      }
  
      /**
       * Returns the HTTP connection that was used to make the request.
       *
       * @return the HTTP connection used to make the request
       */
      public HttpURLConnection getConnection() {
          return connection;
      }
  
      /**
       * Returns the exception associated with this request, if any.
       *
       * @return the exception associated with this request
       */
      public FacebookException getException() {
          return exception;
      }
  
      @Override
      public String toString() {
          return new StringBuilder("{HttpStatus: ")
                  .append(requestStatusCode)
                  .append(", errorCode: ")
                  .append(errorCode)
                  .append(", errorType: ")
                  .append(errorType)
                  .append(", errorMessage: ")
                  .append(getErrorMessage())
                  .append("}")
                  .toString();
      }
  
      static FacebookRequestError checkResponseAndCreateError(JSONObject singleResult,
              Object batchResult, HttpURLConnection connection) {
          try {
              if (singleResult.has(CODE_KEY)) {
                  int responseCode = singleResult.getInt(CODE_KEY);
                  Object body = Utility.getStringPropertyAsJSON(singleResult, BODY_KEY,
                          Response.NON_JSON_RESPONSE_PROPERTY);
  
                  if (body != null && body instanceof JSONObject) {
                      JSONObject jsonBody = (JSONObject) body;
                      // Does this response represent an error from the service? We might get either an "error"
                      // with several sub-properties, or else one or more top-level fields containing error info.
                      String errorType = null;
                      String errorMessage = null;
                      int errorCode = INVALID_ERROR_CODE;
                      int errorSubCode = INVALID_ERROR_CODE;
  
                      boolean hasError = false;
                      if (jsonBody.has(ERROR_KEY)) {
                          // We assume the error object is correctly formatted.
                          JSONObject error = (JSONObject) Utility.getStringPropertyAsJSON(jsonBody, ERROR_KEY, null);
  
                          errorType = error.optString(ERROR_TYPE_FIELD_KEY, null);
                          errorMessage = error.optString(ERROR_MESSAGE_FIELD_KEY, null);
                          errorCode = error.optInt(ERROR_CODE_FIELD_KEY, INVALID_ERROR_CODE);
                          errorSubCode = error.optInt(ERROR_SUB_CODE_KEY, INVALID_ERROR_CODE);
                          hasError = true;
                      } else if (jsonBody.has(ERROR_CODE_KEY) || jsonBody.has(ERROR_MSG_KEY)
                              || jsonBody.has(ERROR_REASON_KEY)) {
                          errorType = jsonBody.optString(ERROR_REASON_KEY, null);
                          errorMessage = jsonBody.optString(ERROR_MSG_KEY, null);
                          errorCode = jsonBody.optInt(ERROR_CODE_KEY, INVALID_ERROR_CODE);
                          errorSubCode = jsonBody.optInt(ERROR_SUB_CODE_KEY, INVALID_ERROR_CODE);
                          hasError = true;
                      }
  
                      if (hasError) {
                          return new FacebookRequestError(responseCode, errorCode, errorSubCode,
                                  errorType, errorMessage, jsonBody, singleResult, batchResult, connection);
                      }
                  }
  
                  // If we didn't get error details, but we did get a failure response code, report it.
                  if (!HTTP_RANGE_SUCCESS.contains(responseCode)) {
                      return new FacebookRequestError(responseCode, INVALID_ERROR_CODE,
                              INVALID_ERROR_CODE, null, null,
                              singleResult.has(BODY_KEY) ?
                                      (JSONObject) Utility.getStringPropertyAsJSON(
                                              singleResult, BODY_KEY, Response.NON_JSON_RESPONSE_PROPERTY) : null,
                              singleResult, batchResult, connection);
                  }
              }
          } catch (JSONException e) {
              // defer the throwing of a JSONException to the graph object proxy
          }
          return null;
      }
  
      /**
       * An enum that represents the Facebook SDK classification for the error that occurred.
       */
      public enum Category {
          /**
           * Indicates that the error is authentication related, and that the app should retry
           * the request after some user action.
           */
          AUTHENTICATION_RETRY,
  
          /**
           * Indicates that the error is authentication related, and that the app should close
           * the session and reopen it.
           */
          AUTHENTICATION_REOPEN_SESSION,
  
          /** Indicates that the error is permission related. */
          PERMISSION,
  
          /**
           * Indicates that the error implies the server had an unexpected failure or may be
           * temporarily unavailable.
           */
          SERVER,
  
          /** Indicates that the error results from the server throttling the client. */
          THROTTLING,
  
          /**
           * Indicates that the error is Facebook-related but cannot be categorized at this time,
           * and is likely newer than the current version of the SDK.
           */
          OTHER,
  
          /**
           * Indicates that the error is an application error resulting in a bad or malformed
           * request to the server.
           */
          BAD_REQUEST,
  
          /**
           * Indicates that this is a client-side error. Examples of this can include, but are
           * not limited to, JSON parsing errors or {@link java.io.IOException}s.
           */
          CLIENT
      };
  
  }
  