Betradar UOF Schemas

Copy Markdown View Source

CI Coverage Status Package Version hexdocs.pm

Ecto embedded schemas and a generic XML decoder for Betradar's Unified Odds Feed (UOF), generated from the official .NET SDK 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:

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:

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:

{: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):

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:

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:

make clean && mix uof.schemas.gen && mix format

The upstream repo and pinned tag live in codegen/xsd/sources.ex.