Packages

Elixir client library for cryptocurrency exchanges — generated from CCXT specs via compile-time macros.

Current section

Files

Jump to
ccxt_client lib ccxt spec merger.ex
Raw

lib/ccxt/spec/merger.ex

defmodule CCXT.Spec.Merger do
@moduledoc """
Deep-merge logic for curated spec overlays.
When a curated spec contains `_curated_overrides: true`, the extracted spec
loads as a base and curated fields are deep-merged on top. This avoids
duplicating the entire extracted spec when only a few fields need overriding.
## Merge Rules
- **Scalars**: curated replaces extracted
- **Maps**: deep-merge (curated keys override, extracted keys preserved)
- **`endpoints` list**: merge by `:name` (matching endpoint replaced wholesale, new endpoints appended)
- **Other lists**: curated replaces entirely
- **Without `_curated_overrides`**: existing winner-take-all behavior preserved
"""
@sentinel_key :_curated_overrides
@doc """
Returns true if the raw spec map contains the `_curated_overrides: true` sentinel.
"""
@spec overlay?(map()) :: boolean()
def overlay?(map) when is_map(map) do
Map.get(map, @sentinel_key) == true
end
@doc """
Deep-merges curated overrides on top of extracted base spec.
Strips the `_curated_overrides` sentinel before merging.
"""
@spec merge(map(), map()) :: map()
def merge(base, overrides) when is_map(base) and is_map(overrides) do
overrides = Map.delete(overrides, @sentinel_key)
deep_merge(base, overrides)
end
@doc false
# Deep-merge two maps, with special handling for :endpoints lists.
@spec deep_merge(map(), map()) :: map()
defp deep_merge(base, overrides) do
Map.merge(base, overrides, fn key, base_val, override_val ->
resolve_conflict(key, base_val, override_val)
end)
end
@doc false
defp resolve_conflict(:endpoints, base_list, override_list) when is_list(base_list) and is_list(override_list) do
merge_endpoints(base_list, override_list)
end
defp resolve_conflict(_key, base_val, override_val) when is_map(base_val) and is_map(override_val) do
deep_merge(base_val, override_val)
end
defp resolve_conflict(_key, _base_val, override_val) do
override_val
end
@doc false
# Merges endpoint lists by :name — matching endpoints are replaced wholesale,
# new endpoints are appended.
@spec merge_endpoints(list(), list()) :: list()
defp merge_endpoints(base_endpoints, override_endpoints) do
override_names = MapSet.new(override_endpoints, &Map.get(&1, :name))
# Keep base endpoints that aren't overridden, then append all overrides
kept = Enum.reject(base_endpoints, &(Map.get(&1, :name) in override_names))
kept ++ override_endpoints
end
end