# `Unicom360.Codes`
[🔗](https://github.com/TechRode-Solution-Limited/unicom360-elixir/blob/v0.1.0/lib/unicom360/codes.ex#L1)

The normalised error codes, and the function that derives them from the gateway's messages.

The gateway answers a refusal with a message that is sometimes a code
(`"INSUFFICIENT_BUNDLE"`) and sometimes a sentence (`"Invalid From Sender ID"`).
`normalize/1` turns both into one code, so `%Unicom360.Error{code: code}` compares the same way
whichever form the gateway used:

    iex> Unicom360.Codes.normalize("Invalid From Sender ID")
    "INVALID_SENDER_ID"
    iex> Unicom360.Codes.normalize("DUPLICATE_MSG_REF")
    "DUPLICATE_REFERENCE"
    iex> Unicom360.Codes.normalize("INSUFFICIENT_BUNDLE: no international SMS bundle")
    "INSUFFICIENT_BUNDLE"
    iex> Unicom360.Codes.normalize("SOME_FUTURE_CODE")
    "SOME_FUTURE_CODE"
    iex> Unicom360.Codes.normalize("Missing required keys: [\"message\"]")
    "UNKNOWN"

Codes are plain strings, so a code this version does not know passes through unchanged. Each
known code also has a function returning it (`Unicom360.Codes.insufficient_bundle/0` returns
`"INSUFFICIENT_BUNDLE"`), and `known/0` lists them all. In a pattern, match the string itself:

    case Unicom360.SMS.send(client, params) do
      {:ok, result} -> result
      {:error, %Unicom360.Error{code: "INSUFFICIENT_BUNDLE"}} -> top_up()
    end

# `authentication_failed`

```elixir
@spec authentication_failed() :: String.t()
```

Returns `"AUTHENTICATION_FAILED"`.

# `client_disabled`

```elixir
@spec client_disabled() :: String.t()
```

Returns `"CLIENT_DISABLED"`.

# `client_not_found`

```elixir
@spec client_not_found() :: String.t()
```

Returns `"CLIENT_NOT_FOUND"`.

# `credential_disabled`

```elixir
@spec credential_disabled() :: String.t()
```

Returns `"CREDENTIAL_DISABLED"`.

# `credential_expired`

```elixir
@spec credential_expired() :: String.t()
```

Returns `"CREDENTIAL_EXPIRED"`.

# `duplicate_reference`

```elixir
@spec duplicate_reference() :: String.t()
```

Returns `"DUPLICATE_REFERENCE"`.

# `insufficient_bundle`

```elixir
@spec insufficient_bundle() :: String.t()
```

Returns `"INSUFFICIENT_BUNDLE"`.

# `invalid_authorization`

```elixir
@spec invalid_authorization() :: String.t()
```

Returns `"INVALID_AUTHORIZATION"`.

# `invalid_number`

```elixir
@spec invalid_number() :: String.t()
```

Returns `"INVALID_NUMBER"`.

# `invalid_parameters`

```elixir
@spec invalid_parameters() :: String.t()
```

Returns `"INVALID_PARAMETERS"`.

# `invalid_recipient_limit`

```elixir
@spec invalid_recipient_limit() :: String.t()
```

Returns `"INVALID_RECIPIENT_LIMIT"`.

# `invalid_sender_id`

```elixir
@spec invalid_sender_id() :: String.t()
```

Returns `"INVALID_SENDER_ID"`.

# `invalid_user_reference_id`

```elixir
@spec invalid_user_reference_id() :: String.t()
```

Returns `"INVALID_USER_REFERENCE_ID"`.

# `ip_not_allowed`

```elixir
@spec ip_not_allowed() :: String.t()
```

Returns `"IP_NOT_ALLOWED"`.

# `known`

```elixir
@spec known() :: [String.t()]
```

Every code this version documents, as strings.

# `language_required`

```elixir
@spec language_required() :: String.t()
```

Returns `"LANGUAGE_REQUIRED"`.

# `media_url_invalid`

```elixir
@spec media_url_invalid() :: String.t()
```

Returns `"MEDIA_URL_INVALID"`.

# `missing_api_key`

```elixir
@spec missing_api_key() :: String.t()
```

Returns `"MISSING_API_KEY"`.

# `no_valid_recipients`

```elixir
@spec no_valid_recipients() :: String.t()
```

Returns `"NO_VALID_RECIPIENTS"`.

# `normalize`

```elixir
@spec normalize(term()) :: String.t()
```

Normalises a gateway message to an error code.

The first matching rule wins: an exact match in the alias table; a message that is itself a
code (`^[A-Z][A-Z0-9_]*$`, aliased when the table has it); a message that starts with a code
followed by a colon and white space (the code, aliased when the table has it); otherwise
`"UNKNOWN"`. Matching is exact and case-sensitive. Anything that is not a string is
`"UNKNOWN"`.

# `rate_limit_exceeded`

```elixir
@spec rate_limit_exceeded() :: String.t()
```

Returns `"RATE_LIMIT_EXCEEDED"`.

# `reaction_target_not_found`

```elixir
@spec reaction_target_not_found() :: String.t()
```

Returns `"REACTION_TARGET_NOT_FOUND"`.

# `reply_to_not_found`

```elixir
@spec reply_to_not_found() :: String.t()
```

Returns `"REPLY_TO_NOT_FOUND"`.

# `send_failed`

```elixir
@spec send_failed() :: String.t()
```

Returns `"SEND_FAILED"`.

# `sender_id_not_allowed`

```elixir
@spec sender_id_not_allowed() :: String.t()
```

Returns `"SENDER_ID_NOT_ALLOWED"`.

# `sender_required`

```elixir
@spec sender_required() :: String.t()
```

Returns `"SENDER_REQUIRED"`.

# `session_window_closed`

```elixir
@spec session_window_closed() :: String.t()
```

Returns `"SESSION_WINDOW_CLOSED"`.

# `template_not_approved`

```elixir
@spec template_not_approved() :: String.t()
```

Returns `"TEMPLATE_NOT_APPROVED"`.

# `template_not_found`

```elixir
@spec template_not_found() :: String.t()
```

Returns `"TEMPLATE_NOT_FOUND"`.

# `template_sender_mismatch`

```elixir
@spec template_sender_mismatch() :: String.t()
```

Returns `"TEMPLATE_SENDER_MISMATCH"`.

# `unknown`

```elixir
@spec unknown() :: String.t()
```

Returns `"UNKNOWN"`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
