# Betradar UOF Schemas
[](https://github.com/efcasado/betradar_uof_schemas/actions/workflows/ci.yml)
[](https://coveralls.io/github/efcasado/betradar_uof_schemas?branch=main)
[](https://hex.pm/packages/uof_schemas)
[](https://hexdocs.pm/uof_schemas/)
Ecto embedded schemas and a generic XML decoder for Betradar's Unified Odds Feed (UOF), generated from the official [.NET SDK](https://github.com/sportradar/UnifiedOddsSdkNetCore) XSDs (pinned to `v3.11.0`). Schemas are namespaced under `UOF.Schemas.API.*` (HTTP API responses) and `UOF.Schemas.Feed.*` (AMQP feed messages).
## Installation
Add `:uof_schemas` to your dependencies in `mix.exs`:
```elixir
def deps do
[
{:uof_schemas, "~> 0.2.0"} # <!-- x-release-please-version -->
]
end
```
## Usage
`UOF.Schemas.XML.decode/1` is the everyday entry point. It dispatches on the document's root element against the full generated registry — every feed message and API response — and returns `{:ok, struct}`, `{:error, Ecto.Changeset.t()}`, or `{:error, {:unknown_message, name}}` for an unrecognised root. It walks the nested embeds and casts scalars (integers, decimals, datetimes, …) as it goes.
For the cases where you want to override that dispatch, `decode/2` takes a **schema module** to decode a known type (static).
### `decode/1` — dispatch on the root element
You don't need to know which message you're holding. Hand the raw XML to `decode/1` and it selects the matching schema — here an `odds_change` from the feed:
```elixir
xml = """
<odds_change product="1" event_id="sr:match:11830662" timestamp="1620902980000">
<sport_event_status status="1" match_status="7" home_score="1" away_score="2"/>
<odds>
<market favourite="1" status="1" id="1">
<outcome id="1" odds="1.85" active="1" probabilities="0.524431"/>
<outcome id="2" odds="3.30" active="1" probabilities="0.286887"/>
<outcome id="3" odds="4.65" active="1" probabilities="0.188682"/>
</market>
</odds>
</odds_change>
"""
{:ok, odds_change} = UOF.Schemas.XML.decode(xml)
# => %UOF.Schemas.Feed.OddsChange{
# product: 1,
# event_id: "sr:match:11830662",
# timestamp: 1620902980000,
# sport_event_status: %UOF.Schemas.Feed.SportEventStatus{
# status: 1, match_status: 7,
# home_score: Decimal.new("1"), away_score: Decimal.new("2"), ...
# },
# odds: %UOF.Schemas.Feed.OddsChangeOdds{
# market: [
# %UOF.Schemas.Feed.OddsChangeMarket{
# id: 1, status: 1, favourite: 1,
# outcome: [
# %UOF.Schemas.Feed.OddsChangeMarketOutcome{id: "1", odds: Decimal.new("1.85"), probabilities: 0.524431, active: 1},
# %UOF.Schemas.Feed.OddsChangeMarketOutcome{id: "2", odds: Decimal.new("3.30"), probabilities: 0.286887, active: 1},
# %UOF.Schemas.Feed.OddsChangeMarketOutcome{id: "3", odds: Decimal.new("4.65"), probabilities: 0.188682, active: 1}
# ]
# }
# ]
# }, ...
# }
```
Any HTTP API response decodes the same way. Every endpoint can also return the shared `<response>` envelope (an error or a write acknowledgement), which `decode/1` routes to `UOF.Schemas.Common.Response`:
```elixir
{:ok, response} =
UOF.Schemas.XML.decode(~s(<response response_code="NOT_FOUND"><message>no such event</message></response>))
# => %UOF.Schemas.Common.Response{response_code: "NOT_FOUND", message: "no such event", ...}
```
### `decode/2` — override the dispatch
When the endpoint already fixes the type, pass the schema module directly to assert it regardless of the root element. For example, the market descriptions endpoint (`GET /v1/descriptions/{language}/markets.xml`):
```elixir
xml = """
<market_descriptions response_code="OK">
<market id="1" name="1x2" groups="all|score|regular_play">
<outcomes>
<outcome id="1" name="{$competitor1}"/>
<outcome id="2" name="draw"/>
<outcome id="3" name="{$competitor2}"/>
</outcomes>
</market>
</market_descriptions>
"""
{:ok, descriptions} =
UOF.Schemas.XML.decode(xml, UOF.Schemas.API.Descriptions.MarketDescriptions)
# => %UOF.Schemas.API.Descriptions.MarketDescriptions{
# response_code: "OK",
# market: [
# %UOF.Schemas.API.Descriptions.DescMarket{
# id: 1, name: "1x2", groups: "all|score|regular_play",
# outcomes: %UOF.Schemas.API.Descriptions.DescOutcomes{
# outcome: [
# %UOF.Schemas.API.Descriptions.DescOutcomesOutcome{id: "1", name: "{$competitor1}"},
# %UOF.Schemas.API.Descriptions.DescOutcomesOutcome{id: "2", name: "draw"},
# %UOF.Schemas.API.Descriptions.DescOutcomesOutcome{id: "3", name: "{$competitor2}"}
# ]
# }, ...
# }
# ]
# }
```
## Regenerating schemas
The modules under `lib/uof/schemas/` are generated and committed, so consumers never run codegen. To refresh them against the pinned SDK tag:
```sh
mix uof.schemas.gen # fetch XSDs (if missing) + generate
mix format # apply Styler to the generated modules
```
`mix uof.schemas.gen` is additive and only fetches when the cache is empty. To force a clean refresh (re-download XSDs and drop removed/renamed modules), run `make clean` first — it deletes `priv/xsd/` and the generated schema directories — then regenerate:
```sh
make clean && mix uof.schemas.gen && mix format
```
The upstream repo and pinned tag live in `codegen/xsd/sources.ex`.