# `CCXT.Spec`
[🔗](https://github.com/ZenHive/ccxt_client/blob/main/lib/ccxt/spec.ex#L1)

Compile-time JSON spec loader for exchange specifications.

Reads exchange specs from `priv/specs/json/output/` and decodes them for use
by the generator macro (`CCXT.Exchange`). Specs are loaded at compile time
inside the generator — this module provides the loading functions but does
not store specs as module attributes.

## Usage

    # In generator macro (compile time):
    spec = CCXT.Spec.load!("bybit")

    # List available exchanges:
    CCXT.Spec.exchanges()
    #=> ["aftermath", "alpaca", "apex", ...]

## Schema Version

`load!/1` and `load_manifest!/0` enforce `schema_version` major `3`
(ccxt_extract Schema 3.0.0+). Per-exchange specs must also carry a
non-empty top-level `_provenance` map (RFC 6901 pointer keys →
`"raw"` / `"derived"` / `"override"`); the manifest does not.

A mismatch raises with a specific message naming the spec and the
offending field, so a stale `priv/specs/json/output/` surfaces as a
clear compile-time failure instead of a downstream `MatchError`.

## Dead Weight Stripping

`load!/1` removes extraction-only data (`structure.interface_signatures`
and `structure.methods`) that is only useful for static analysis, not
runtime code generation. This reduces memory for `Macro.escape/1`.

# `exchanges`

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

Returns the list of available exchange IDs from the manifest.

## Examples

    CCXT.Spec.exchanges()
    #=> ["aftermath", "alpaca", "apex", "arkham", ...]

# `load!`

```elixir
@spec load!(String.t()) :: map()
```

Loads and decodes an exchange spec by ID.

Reads the JSON file, decodes it, and strips extraction-only data from
`structure`. Returns a map with string keys.

Raises `File.Error` if the spec file doesn't exist, or `Jason.DecodeError`
on invalid JSON.

## Examples

    spec = CCXT.Spec.load!("bybit")
    spec["exchange"]["id"]
    #=> "bybit"

# `load_manifest!`

```elixir
@spec load_manifest!() :: map()
```

Loads and decodes the manifest file.

Returns the decoded `_manifest.json` containing exchange list,
CCXT version, and extraction metadata.

## Examples

    manifest = CCXT.Spec.load_manifest!()
    manifest["ccxt_version"]
    #=> "4.5.46"

    manifest["exchange_count"]
    #=> 23

# `manifest_path`

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

Returns the absolute path to the manifest file.

## Examples

    CCXT.Spec.manifest_path()
    #=> "/path/to/priv/specs/json/output/_manifest.json"

# `spec_path`

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

Returns the absolute path to a spec file for the given exchange ID.

## Examples

    CCXT.Spec.spec_path("bybit")
    #=> "/path/to/priv/specs/json/output/bybit.json"

# `validate_manifest_schema!`

```elixir
@spec validate_manifest_schema!(map()) :: map()
```

Validates a decoded manifest map against the Schema 3.0.0+ contract.

Only the `schema_version` major is enforced — manifests have no `_provenance`
by design (different document type from per-exchange specs). Raises on
mismatch; returns the manifest unchanged on success. Exposed for test
coverage of the guard branches.

# `validate_schema!`

```elixir
@spec validate_schema!(map(), String.t()) :: map()
```

Validates a decoded per-exchange spec map against the Schema 3.0.0+ contract:
`schema_version` must be a string whose numeric major equals `3`,
and `_provenance` must be a non-empty top-level map. On mismatch, raises with
a message naming the exchange and the offending field.

Returns the spec unchanged on success. Called internally from `load!/1`;
exposed publicly so test suites can exercise every branch against in-memory
maps without writing fixture files into the live spec directory.

---

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