# Encoding

The `Encoding` module (from the core `effect` package) does pure, synchronous
conversion between bytes and text in three formats: **Base64** (RFC 4648),
**Base64Url** (the URL-safe, unpadded variant), and **Hex** (lowercase
hexadecimal).

There is a deliberate asymmetry between the two directions:

- **Encoders are total.** `encodeBase64`, `encodeBase64Url`, and `encodeHex`
  always return a `string` — any `Uint8Array` or `string` can be encoded, so
  they never fail.
- **Decoders are safe, not throwing.** `decodeBase64`, `decodeHex`, and friends
  return `Result.Result<…, EncodingError>` because the input might be malformed.
  Instead of throwing, they hand you a `Result` you branch on.

The mental model for inputs and outputs:

- A `string` input is first converted to UTF-8 bytes with `TextEncoder`, then
  encoded. A `Uint8Array` input is encoded directly.
- Byte decoders (`decodeBase64`, `decodeBase64Url`, `decodeHex`) return a raw
  `Uint8Array`.
- The `*String` decoders (`decodeBase64String`, `decodeBase64UrlString`,
  `decodeHexString`) decode those bytes back into UTF-8 text with
  `TextDecoder`.

## Round-trip example

A full encode/decode round trip. Encoding produces a plain string; decoding
produces a `Result` that you resolve with `Result.match`.

```ts
import { Encoding, Result } from "effect"

// Some binary payload
const bytes = new Uint8Array([72, 101, 108, 108, 111]) // "Hello"

// Encode (total — always a string)
const encoded = Encoding.encodeBase64(bytes)
// => "SGVsbG8="

// Decode (safe — a Result, because input could be malformed)
const result = Encoding.decodeBase64(encoded)

const message = Result.match(result, {
  onFailure: (error) => `decode failed: ${error.message}`,
  onSuccess: (decoded) => `got ${decoded.length} bytes back`
})
// => "got 5 bytes back"
```
**String vs bytes:** Every encoder accepts both `Uint8Array` and `string`. Pass a `string` when you
  just want to encode UTF-8 text; pass a `Uint8Array` when you already hold raw
  bytes (a hash, a binary file, a crypto key, etc.).

## Base64

Standard, padded RFC 4648 Base64. This is the default choice for embedding
binary data in JSON, data URLs, or anywhere a Base64 string is expected.

```ts
import { Encoding, Result } from "effect"

// Encode text
Encoding.encodeBase64("hello")
// => "aGVsbG8="

// Encode bytes
Encoding.encodeBase64(new Uint8Array([72, 101, 108, 108, 111]))
// => "SGVsbG8="

// Decode back to bytes
const bytes = Encoding.decodeBase64("SGVsbG8=")
if (Result.isSuccess(bytes)) {
  Array.from(bytes.success)
  // => [72, 101, 108, 108, 111]
}

// Decode straight to UTF-8 text
const text = Encoding.decodeBase64String("aGVsbG8=")
if (Result.isSuccess(text)) {
  text.success
  // => "hello"
}
```

Use `decodeBase64` when you want the raw `Uint8Array` (binary payloads), and
`decodeBase64String` when the encoded bytes are UTF-8 text and you want the
string directly.
**Whitespace:** Base64 and Base64Url decoders strip carriage returns and line feeds (`\r`,
  `\n`) before validating, so wrapped/MIME-style input decodes cleanly. Other
  characters are still rejected.

## Base64Url

The URL-safe variant: it replaces `+` with `-` and `/` with `_`, and emits
**unpadded** output (no trailing `=`). This is the form used for JWT segments,
URL query parameters, and filenames.

```ts
import { Encoding, Result } from "effect"

// Encode text — note the trailing "_" instead of "/" and no padding
Encoding.encodeBase64Url("hello?")
// => "aGVsbG8_"

// Encode bytes
Encoding.encodeBase64Url(new Uint8Array([72, 101, 108, 108, 111, 63]))
// => "SGVsbG8_"

// Decode back to bytes (accepts padded AND unpadded input)
const bytes = Encoding.decodeBase64Url("SGVsbG8_")
if (Result.isSuccess(bytes)) {
  Array.from(bytes.success)
  // => [72, 101, 108, 108, 111, 63]
}

// Decode straight to UTF-8 text
const text = Encoding.decodeBase64UrlString("aGVsbG8_")
if (Result.isSuccess(text)) {
  text.success
  // => "hello?"
}
```
**Padding asymmetry:** `encodeBase64Url` always emits **unpadded** output, but `decodeBase64Url`
  accepts both padded and unpadded URL-safe input. So a token you produce will
  have no `=`, while a token you receive from elsewhere can include padding and
  still decode.

A common use is splitting and decoding a JWT, whose three segments are
Base64Url-encoded:

```ts
import { Encoding, Result } from "effect"

const decodeJwtPayload = (jwt: string) => {
  const segment = jwt.split(".")[1] ?? ""
  return Result.map(Encoding.decodeBase64UrlString(segment), JSON.parse)
}
```

## Hex

Lowercase hexadecimal. Encoding always emits lowercase letters; decoding
requires an even number of hex characters.

```ts
import { Encoding, Result } from "effect"

// Encode text
Encoding.encodeHex("hello")
// => "68656c6c6f"

// Encode bytes
Encoding.encodeHex(new Uint8Array([72, 101, 108, 108, 111]))
// => "48656c6c6f"

// Decode back to bytes
const bytes = Encoding.decodeHex("48656c6c6f")
if (Result.isSuccess(bytes)) {
  Array.from(bytes.success)
  // => [72, 101, 108, 108, 111]
}

// Decode straight to UTF-8 text
const text = Encoding.decodeHexString("68656c6c6f")
if (Result.isSuccess(text)) {
  text.success
  // => "hello"
}
```
**Case:** `decodeHex` accepts both uppercase and lowercase hex digits, but `encodeHex`
  always produces lowercase. If you need a canonical lowercase representation,
  round-tripping through `decodeHex` then `encodeHex` normalizes it.

## Handling decode failures

Decoders never throw. A malformed input becomes `Result.fail` carrying an
`EncodingError`, which records what went wrong.

```ts
import { Encoding, Result } from "effect"

// "!" is not a valid Base64 character
const result = Encoding.decodeBase64("not-base64!")

Result.match(result, {
  onFailure: (error) => {
    error._tag // => "EncodingError"
    error.kind // => "Decode"
    error.module // => "Base64"
    error.message // human-readable reason
  },
  onSuccess: (bytes) => bytes
})
```

### Narrowing with `isEncodingError`

When a value's type is wider than `EncodingError` (e.g. caught from a mixed
error channel), use the `isEncodingError` guard to narrow it.

```ts
import { Encoding } from "effect"

const handle = (u: unknown) => {
  if (Encoding.isEncodingError(u)) {
    // u is now typed as EncodingError
    return `${u.module} ${u.kind} failed: ${u.message}`
  }
  return "unknown error"
}
```

### Pulling a `Result` into an Effect

Inside `Effect.gen`, lift the decode `Result` into the Effect error channel with
`Effect.fromResult`. A `Result.fail` becomes a typed `EncodingError` failure you
can handle with `Effect.catchTag`.

```ts
import { Effect, Encoding } from "effect"

const program = Effect.gen(function* () {
  // Fails with EncodingError if the input is not valid Base64
  const bytes = yield* Effect.fromResult(Encoding.decodeBase64("SGVsbG8="))
  return bytes.length
}).pipe(
  // EncodingError is a tagged error, so catchTag narrows it precisely
  Effect.catchTag("EncodingError", (error) =>
    Effect.succeed(`bad input: ${error.message}`)
  )
)
```
**Streaming and wire formats:** This module is for one-shot, in-memory conversions. For byte streams, see
  [Stream encoding](https://effect.plants.sh/streaming/encoding/) (UTF-8, Base64, Ndjson, Msgpack
  pipelines). For structured wire formats such as Ndjson and Msgpack as standalone
  codecs, see [Encoding formats](https://effect.plants.sh/platform/encoding-formats/).

## API reference

Every public export of the `Encoding` module. Encoders return `string`; decoders
return `Result.Result<…, EncodingError>`.

### encodeBase64

Encodes a `string` (as UTF-8 bytes) or a `Uint8Array` into a standard padded
RFC 4648 Base64 string.

```ts
import { Encoding } from "effect"

Encoding.encodeBase64("hello")
// => "aGVsbG8="
Encoding.encodeBase64(new Uint8Array([255, 0, 255]))
// => "/wD/"
```

### decodeBase64

Decodes a standard Base64 string into a `Uint8Array`, returning a `Result`.
Fails if the length is not a multiple of 4 or padding is misplaced.

```ts
import { Encoding, Result } from "effect"

Encoding.decodeBase64("aGVsbG8=")
// => Result.succeed(Uint8Array [104, 101, 108, 108, 111])
Result.isFailure(Encoding.decodeBase64("abc"))
// => true (length is not a multiple of 4)
```

### decodeBase64String

Decodes a standard Base64 string into UTF-8 text, returning a `Result`. Equivalent
to `decodeBase64` followed by `TextDecoder.decode`.

```ts
import { Encoding } from "effect"

Encoding.decodeBase64String("aGVsbG8=")
// => Result.succeed("hello")
```

### encodeBase64Url

Encodes a `string` or `Uint8Array` into URL-safe, **unpadded** Base64 (`-`/`_`
instead of `+`/`/`, no trailing `=`).

```ts
import { Encoding } from "effect"

Encoding.encodeBase64Url("hello?")
// => "aGVsbG8_"
Encoding.encodeBase64Url(new Uint8Array([255, 255, 255]))
// => "____"
```

### decodeBase64Url

Decodes URL-safe Base64 (padded or unpadded) into a `Uint8Array`, returning a
`Result`.

```ts
import { Encoding, Result } from "effect"

Encoding.decodeBase64Url("aGVsbG8_")
// => Result.succeed(Uint8Array [104, 101, 108, 108, 111, 63])
// Padded input is accepted too:
Result.isSuccess(Encoding.decodeBase64Url("aGVsbG8="))
// => true
```

### decodeBase64UrlString

Decodes URL-safe Base64 into UTF-8 text, returning a `Result`.

```ts
import { Encoding } from "effect"

Encoding.decodeBase64UrlString("aGVsbG8_")
// => Result.succeed("hello?")
```

### encodeHex

Encodes a `string` or `Uint8Array` into a lowercase hexadecimal string.

```ts
import { Encoding } from "effect"

Encoding.encodeHex("hello")
// => "68656c6c6f"
Encoding.encodeHex(new Uint8Array([0, 15, 255]))
// => "000fff"
```

### decodeHex

Decodes a hexadecimal string into a `Uint8Array`, returning a `Result`. Fails on
an odd number of characters or non-hex characters.

```ts
import { Encoding, Result } from "effect"

Encoding.decodeHex("000fff")
// => Result.succeed(Uint8Array [0, 15, 255])
Result.isFailure(Encoding.decodeHex("abc"))
// => true (odd length)
```

### decodeHexString

Decodes a hexadecimal string into UTF-8 text, returning a `Result`.

```ts
import { Encoding } from "effect"

Encoding.decodeHexString("68656c6c6f")
// => Result.succeed("hello")
```

### EncodingError

The structured error returned by decode failures. It is a
`Data.TaggedError("EncodingError")` carrying `kind` (`"Decode" | "Encode"`),
`module` (e.g. `"Base64"`, `"Base64Url"`, `"Hex"`), the original `input`, and a
human-readable `message`. Because it is a tagged error, it works with
`Effect.catchTag("EncodingError", …)`.

```ts
import { Encoding, Result } from "effect"

const result = Encoding.decodeHex("zz")
if (Result.isFailure(result)) {
  result.failure._tag // => "EncodingError"
  result.failure.kind // => "Decode"
  result.failure.module // => "Hex"
  result.failure.message // human-readable reason
}
```

### isEncodingError

Type guard that narrows an `unknown` value to `EncodingError` by checking its
runtime marker.

```ts
import { Encoding } from "effect"

Encoding.isEncodingError(new Error("boom"))
// => false
const err = Encoding.decodeHex("zz")
// inside a Result.failure branch, err.failure is an EncodingError:
// Encoding.isEncodingError(err.failure) // => true
```

### EncodingErrorTypeId

The string marker stored on `EncodingError` values (`"~effect/encoding/EncodingError"`)
that `isEncodingError` checks. You rarely reference it directly — prefer the
guard — but it is exported for advanced narrowing, along with its literal type
`EncodingErrorTypeId`.

```ts
import { Encoding } from "effect"

Encoding.EncodingErrorTypeId
// => "~effect/encoding/EncodingError"
```