code: // Licensed to the Software Freedom Conservancy (SFC) under one
  // or more contributor license agreements.  See the NOTICE file
  // distributed with this work for additional information
  // regarding copyright ownership.  The SFC licenses this file
  // to you 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 org.openqa.selenium.remote;
  
  import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
  
  import com.google.common.collect.ImmutableSet;
  import com.google.common.collect.Iterables;
  import java.util.Comparator;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Optional;
  import java.util.Set;
  import java.util.logging.Logger;
  import java.util.stream.Collectors;
  import org.openqa.selenium.ElementClickInterceptedException;
  import org.openqa.selenium.ElementNotInteractableException;
  import org.openqa.selenium.InvalidArgumentException;
  import org.openqa.selenium.InvalidCookieDomainException;
  import org.openqa.selenium.InvalidElementStateException;
  import org.openqa.selenium.InvalidSelectorException;
  import org.openqa.selenium.JavascriptException;
  import org.openqa.selenium.NoAlertPresentException;
  import org.openqa.selenium.NoSuchCookieException;
  import org.openqa.selenium.NoSuchElementException;
  import org.openqa.selenium.NoSuchFrameException;
  import org.openqa.selenium.NoSuchSessionException;
  import org.openqa.selenium.NoSuchShadowRootException;
  import org.openqa.selenium.NoSuchWindowException;
  import org.openqa.selenium.ScriptTimeoutException;
  import org.openqa.selenium.SessionNotCreatedException;
  import org.openqa.selenium.StaleElementReferenceException;
  import org.openqa.selenium.TimeoutException;
  import org.openqa.selenium.UnableToSetCookieException;
  import org.openqa.selenium.UnhandledAlertException;
  import org.openqa.selenium.UnsupportedCommandException;
  import org.openqa.selenium.WebDriverException;
  import org.openqa.selenium.interactions.MoveTargetOutOfBoundsException;
  
  /** Defines common error codes for the wire protocol. */
  public class ErrorCodes {
  
    public static final String SUCCESS_STRING = "success";
    public static final int SUCCESS = 0;
    public static final int NO_SUCH_SESSION = 6;
    public static final int NO_SUCH_ELEMENT = 7;
    public static final int NO_SUCH_FRAME = 8;
    public static final int UNKNOWN_COMMAND = 9;
    public static final int STALE_ELEMENT_REFERENCE = 10;
    public static final int INVALID_ELEMENT_STATE = 12;
    public static final int UNHANDLED_ERROR = 13;
    public static final int JAVASCRIPT_ERROR = 17;
    public static final int XPATH_LOOKUP_ERROR = 19;
    public static final int TIMEOUT = 21;
    public static final int NO_SUCH_WINDOW = 23;
    public static final int INVALID_COOKIE_DOMAIN = 24;
    public static final int UNABLE_TO_SET_COOKIE = 25;
    public static final int UNEXPECTED_ALERT_PRESENT = 26;
    public static final int NO_ALERT_PRESENT = 27;
    public static final int ASYNC_SCRIPT_TIMEOUT = 28;
    public static final int INVALID_SELECTOR_ERROR = 32;
    public static final int SESSION_NOT_CREATED = 33;
    public static final int MOVE_TARGET_OUT_OF_BOUNDS = 34;
    public static final int INVALID_XPATH_SELECTOR = 51;
    public static final int INVALID_XPATH_SELECTOR_RETURN_TYPER = 52;
  
    // json wire protocol doesn't have analogous status codes for
    // these new W3C status response 'codes', so making some up!
    public static final int ELEMENT_NOT_INTERACTABLE = 60;
    public static final int INVALID_ARGUMENT = 61;
    public static final int NO_SUCH_COOKIE = 62;
    public static final int UNABLE_TO_CAPTURE_SCREEN = 63;
    public static final int ELEMENT_CLICK_INTERCEPTED = 64;
    public static final int NO_SUCH_SHADOW_ROOT = 65;
  
    // The following error codes are derived straight from HTTP return codes.
    public static final int METHOD_NOT_ALLOWED = 405;
  
    private static final Logger LOG = Logger.getLogger(ErrorCodes.class.getName());
    // Every row on this table should be self-explanatory, except for the two booleans at the end.
    // The first of these is "isCanonicalJsonCodeForException". This means that when doing the mapping
    // for a JSON Wire Protocol status code, this KnownError provides the exception that should be
    // thrown. The second boolean is "isCanonicalForW3C". This means that when mapping a state or
    // exception to a W3C state, this KnownError provides the default exception and Json Wire Protocol
    // status to send.
    private static final ImmutableSet<KnownError> KNOWN_ERRORS =
        ImmutableSet.<KnownError>builder()
            .add(
                new KnownError(
                    ASYNC_SCRIPT_TIMEOUT,
                    "script timeout",
                    500,
                    ScriptTimeoutException.class,
                    true,
                    true))
            .add(
                new KnownError(
                    ELEMENT_CLICK_INTERCEPTED,
                    "element click intercepted",
                    400,
                    ElementClickInterceptedException.class,
                    true,
                    true))
            .add(
                new KnownError(
                    ELEMENT_NOT_INTERACTABLE,
                    "element not interactable",
                    400,
                    ElementNotInteractableException.class,
                    true,
                    true))
            .add(
                new KnownError(
                    INVALID_ARGUMENT,
                    "invalid argument",
                    400,
                    InvalidArgumentException.class,
                    true,
                    true))
            .add(
                new KnownError(
                    INVALID_COOKIE_DOMAIN,
                    "invalid cookie domain",
                    400,
                    InvalidCookieDomainException.class,
                    true,
                    true))
            .add(
                new KnownError(
                    INVALID_ELEMENT_STATE,
                    "invalid element state",
                    400,
                    InvalidElementStateException.class,
                    true,
                    true))
            .add(
                new KnownError(
                    INVALID_SELECTOR_ERROR,
                    "invalid selector",
                    400,
                    InvalidSelectorException.class,
                    true,
                    true))
            .add(
                new KnownError(
                    INVALID_XPATH_SELECTOR,
                    "invalid selector",
                    400,
                    InvalidSelectorException.class,
                    false,
                    false))
            .add(
                new KnownError(
                    INVALID_XPATH_SELECTOR_RETURN_TYPER,
                    "invalid selector",
                    400,
                    InvalidSelectorException.class,
                    false,
                    true))
            .add(
                new KnownError(
                    JAVASCRIPT_ERROR, "javascript error", 500, JavascriptException.class, true, true))
            .add(
                new KnownError(
                    METHOD_NOT_ALLOWED,
                    "unknown method",
                    405,
                    UnsupportedCommandException.class,
                    false,
                    true))
            .add(
                new KnownError(
                    METHOD_NOT_ALLOWED,
                    "unsupported operation",
                    500,
                    UnsupportedCommandException.class,
                    false,
                    true))
            .add(
                new KnownError(
                    MOVE_TARGET_OUT_OF_BOUNDS,
                    "move target out of bounds",
                    500,
                    MoveTargetOutOfBoundsException.class,
                    true,
                    true))
            .add(
                new KnownError(
                    NO_ALERT_PRESENT,
                    "no such alert",
                    404,
                    NoAlertPresentException.class,
                    true,
                    true))
            .add(
                new KnownError(
                    NO_SUCH_COOKIE, "no such cookie", 404, NoSuchCookieException.class, true, true))
            .add(
                new KnownError(
                    NO_SUCH_ELEMENT,
                    "no such element",
                    404,
                    NoSuchElementException.class,
                    true,
                    true))
            .add(
                new KnownError(
                    NO_SUCH_FRAME, "no such frame", 404, NoSuchFrameException.class, true, true))
            .add(
                new KnownError(
                    NO_SUCH_SESSION,
                    "invalid session id",
                    404,
                    NoSuchSessionException.class,
                    true,
                    true))
            .add(
                new KnownError(
                    NO_SUCH_SHADOW_ROOT,
                    "no such shadow root",
                    404,
                    NoSuchShadowRootException.class,
                    true,
                    true))
            .add(
                new KnownError(
                    NO_SUCH_WINDOW, "no such window", 404, NoSuchWindowException.class, true, true))
            .add(
                new KnownError(
                    SESSION_NOT_CREATED,
                    "session not created",
                    500,
                    SessionNotCreatedException.class,
                    true,
                    true))
            .add(
                new KnownError(
                    STALE_ELEMENT_REFERENCE,
                    "stale element reference",
                    404,
                    StaleElementReferenceException.class,
                    true,
                    true))
            .add(new KnownError(TIMEOUT, "timeout", 500, TimeoutException.class, true, true))
            .add(
                new KnownError(
                    XPATH_LOOKUP_ERROR,
                    "invalid selector",
                    400,
                    InvalidSelectorException.class,
                    false,
                    false))
            .add(
                new KnownError(
                    UNABLE_TO_CAPTURE_SCREEN,
                    "unable to capture screen",
                    500,
                    ScreenshotException.class,
                    true,
                    true))
            .add(
                new KnownError(
                    UNABLE_TO_SET_COOKIE,
                    "unable to set cookie",
                    500,
                    UnableToSetCookieException.class,
                    true,
                    true))
            .add(
                new KnownError(
                    UNEXPECTED_ALERT_PRESENT,
                    "unexpected alert open",
                    500,
                    UnhandledAlertException.class,
                    true,
                    true))
            .add(
                new KnownError(
                    UNHANDLED_ERROR, "unknown error", 500, WebDriverException.class, true, true))
            .add(
                new KnownError(
                    UNKNOWN_COMMAND,
                    "unknown command",
                    404,
                    UnsupportedCommandException.class,
                    true,
                    true))
            .build();
  
    static {
      {
        // Validate uniqueness constraints.
        //
        // There should be only one canonical JSON Wire protocol code per exception
        Map<Object, Set<KnownError>> matched = new HashMap<>();
        for (KnownError knownError : KNOWN_ERRORS) {
          matched.computeIfAbsent(knownError, key -> new HashSet<>()).add(knownError);
        }
        for (Set<KnownError> errors : matched.values()) {
          if (errors.size() != 1) {
            throw new RuntimeException("Duplicate canonical exceptions: " + errors);
          }
        }
  
        // There should only be one canonical W3C code to JSON Wire Protocol code
        //    matched = new HashMap<>();
        //    for (KnownError error : KNOWN_ERRORS) {
        //      matched.computeIfAbsent(error.getW3cCode(), key -> new HashSet<>()).add(error);
        //    }
        //    for (Set<KnownError> errors : matched.values()) {
        //      if (errors.size() != 1) {
        //        throw new RuntimeException("Duplicate canonical w3c state codes: " + errors);
        //      }
        //    }
      }
    }
  
    public String toState(Integer status) {
      if (status == null) {
        return toState(UNHANDLED_ERROR);
      }
  
      if (SUCCESS == status) {
        return SUCCESS_STRING;
      }
  
      Set<String> possibleMatches =
          KNOWN_ERRORS.stream()
              .filter(knownError -> knownError.getJsonCode() == status)
              .filter(KnownError::isCanonicalForW3C)
              .map(KnownError::getW3cCode)
              .collect(Collectors.toSet());
  
      return Iterables.getOnlyElement(possibleMatches, "unhandled error");
    }
  
    public int toStatus(String webdriverState, Optional<Integer> httpStatus) {
      if (SUCCESS_STRING.equals(webdriverState)) {
        return 0;
      }
  
      List<KnownError> possibleMatches =
          KNOWN_ERRORS.stream()
              .filter(knownError -> knownError.getW3cCode().equals(webdriverState))
              .filter(KnownError::isCanonicalForW3C)
              .sorted(Comparator.comparingInt(KnownError::getJsonCode))
              .collect(Collectors.toList());
  
      if (possibleMatches.isEmpty()) {
        return UNHANDLED_ERROR;
      }
      KnownError error = possibleMatches.get(0);
      if (httpStatus.isPresent() && httpStatus.get() != error.getW3cHttpStatus()) {
        LOG.info(
            String.format(
                "HTTP Status: '%d' -> incorrect JSON status mapping for '%s' (%d expected)",
                httpStatus.get(), webdriverState, error.getW3cHttpStatus()));
      }
      return error.getJsonCode();
    }
  
    public int getHttpStatusCode(Throwable throwable) {
      return KNOWN_ERRORS.stream()
          .filter(error -> error.getException().isAssignableFrom(throwable.getClass()))
          .filter(KnownError::isCanonicalForW3C)
          .map(KnownError::getW3cHttpStatus)
          .findAny()
          .orElse(HTTP_INTERNAL_ERROR);
    }
  
    /**
     * Returns the exception type that corresponds to the given {@code statusCode}. All unrecognized
     * status codes will be mapped to {@link WebDriverException WebDriverException.class}.
     *
     * @param statusCode The status code to convert.
     * @return The exception type that corresponds to the provided status code or {@code null} if
     *     {@code statusCode == 0}.
     */
    public Class<? extends WebDriverException> getExceptionType(int statusCode) {
      if (SUCCESS == statusCode) {
        return null;
      }
  
      // We know that the tuple of (status code, exception) is distinct.
      Set<Class<? extends WebDriverException>> allPossibleExceptions =
          KNOWN_ERRORS.stream()
              .filter(knownError -> knownError.getJsonCode() == statusCode)
              .map(KnownError::getException)
              .collect(Collectors.toSet());
  
      return Iterables.getOnlyElement(allPossibleExceptions, WebDriverException.class);
    }
  
    public Class<? extends WebDriverException> getExceptionType(String webdriverState) {
      Set<Class<? extends WebDriverException>> possibleMatches =
          KNOWN_ERRORS.stream()
              .filter(knownError -> knownError.getW3cCode().equals(webdriverState))
              .filter(KnownError::isCanonicalForW3C)
              .map(KnownError::getException)
              .collect(Collectors.toSet());
  
      return Iterables.getOnlyElement(possibleMatches, WebDriverException.class);
    }
  
    public int toStatusCode(Throwable e) {
      if (e == null) {
        return SUCCESS;
      }
  
      Set<Integer> possibleMatches =
          KNOWN_ERRORS.stream()
              .filter(knownError -> knownError.getException().equals(e.getClass()))
              .filter(knownError -> knownError.isCanonicalJsonCodeForException)
              .map(KnownError::getJsonCode)
              .collect(Collectors.toSet());
  
      return Iterables.getOnlyElement(possibleMatches, UNHANDLED_ERROR);
    }
  
    public boolean isMappableError(Throwable rootCause) {
      if (rootCause == null) {
        return false;
      }
      Set<KnownError> possibleMatches =
          KNOWN_ERRORS.stream()
              .filter(knownError -> knownError.getException().equals(rootCause.getClass()))
              .collect(Collectors.toSet());
  
      return !possibleMatches.isEmpty();
    }
  
    private static class KnownError {
      private final int jsonCode;
      private final String w3cCode;
      private final int w3cHttpStatus;
      private final Class<? extends WebDriverException> exception;
      private final boolean isCanonicalJsonCodeForException;
      private final boolean isCanonicalForW3C;
  
      public KnownError(
          int jsonCode,
          String w3cCode,
          int w3cHttpStatus,
          Class<? extends WebDriverException> exception,
          boolean isCanonicalJsonCodeForException,
          boolean isCanonicalForW3C) {
        this.jsonCode = jsonCode;
        this.w3cCode = w3cCode;
        this.w3cHttpStatus = w3cHttpStatus;
        this.exception = exception;
        this.isCanonicalJsonCodeForException = isCanonicalJsonCodeForException;
        this.isCanonicalForW3C = isCanonicalForW3C;
      }
  
      public int getJsonCode() {
        return jsonCode;
      }
  
      public String getW3cCode() {
        return w3cCode;
      }
  
      public int getW3cHttpStatus() {
        return w3cHttpStatus;
      }
  
      public Class<? extends WebDriverException> getException() {
        return exception;
      }
  
      public boolean isCanonicalForW3C() {
        return isCanonicalForW3C;
      }
    }
  }
  