use std::collections::{BTreeMap, BTreeSet};
use std::time::{Duration, SystemTime};
use async_trait::async_trait;
use serde::Deserialize;
use serde_json::value::RawValue;
use super::catalog::{
CatalogContent, CatalogContentError, CatalogError, CatalogModelEntry, CatalogProvider,
CatalogRefresh, CatalogSnapshot, CatalogSource, ETag, HttpDate, InvalidCatalogId, JsonPointer,
Modality, ModelCapability, ModelFacts, ModelField, ModelId, ModelLifecycle, ModelLimits,
ObservedPrice, ObservedRate, PriceRates, PriceTier, PriceTierThreshold, ProviderEndpoint,
ProviderOffering, RawPayload, Refusable, Refusal, RefusalReason, SchemaVersion,
SourceValidators, excerpt, excerpt_list, excerpt_located, source_snapshot,
};
use super::{Capabilities, Capability};
use crate::desired_state::canonical::{CanonicalError, CanonicalValue};
pub const MODELS_DEV_CATALOG_URL: &str = "https://models.dev/catalog.json";
const SUPPORTED_PATH: &str = "/catalog.json";
const BACKEND: &str = "models.dev";
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ModelsDevError {
#[error(
"`{}` is not a supported models.dev document; only `{SUPPORTED_PATH}` is \
(`api.json` and `models.json` have different shapes)",
excerpt_located(url)
)]
UnsupportedEndpoint { url: String },
#[error("the payload is not JSON: {}", excerpt_located(message))]
NotJson { message: String },
#[error(
"the payload is not a models.dev catalogue document{}: {}",
pointer.as_ref().map_or_else(String::new, |pointer| format!(" at `{pointer}`")),
excerpt_located(message)
)]
Schema {
pointer: Option<JsonPointer>,
message: String,
},
#[error(
"`{pointer}` is keyed `{}` but its `id` is `{}`",
excerpt(key),
excerpt(id)
)]
IdMismatch {
pointer: JsonPointer,
key: String,
id: String,
},
#[error("`{pointer}` has an unusable identifier: {source}")]
Identifier {
pointer: JsonPointer,
#[source]
source: InvalidCatalogId,
},
#[error("`{pointer}` has an unrecognized status `{}`", excerpt(status))]
UnknownStatus {
pointer: JsonPointer,
status: String,
},
#[error("`{pointer}` has an unrecognized modality `{}`", excerpt(modality))]
UnknownModality {
pointer: JsonPointer,
modality: String,
},
#[error("`{pointer}` states a price the gateway cannot represent: {reason}")]
Price {
pointer: JsonPointer,
reason: PriceRejection,
},
#[error("`{pointer}` has an unrecognized price tier type `{}`", excerpt(kind))]
UnknownTierType { pointer: JsonPointer, kind: String },
#[error("`{pointer}` states two prices for the same tier threshold")]
DuplicateTier { pointer: JsonPointer },
#[error("`{pointer}` publishes a price on a provider-neutral record")]
NeutralPrice { pointer: JsonPointer },
#[error("`{pointer}` cannot be held in normalized content: {source}")]
UncanonicalizableText {
pointer: JsonPointer,
#[source]
source: CanonicalError,
},
#[error(
"`{}` offers `{}`, which could be any of {}",
pointer,
excerpt(key),
excerpt_list(candidates)
)]
AmbiguousModelKey {
pointer: JsonPointer,
key: String,
candidates: Vec<String>,
},
#[error("the payload's catalogue is not usable: {source}")]
Content {
#[source]
source: CatalogContentError,
},
}
impl ModelsDevError {
fn schema_at(pointer: JsonPointer, message: impl Into<String>) -> Self {
Self::Schema {
pointer: Some(pointer),
message: message.into(),
}
}
fn identifier(pointer: &JsonPointer, source: InvalidCatalogId) -> Self {
Self::Identifier {
pointer: pointer.clone(),
source,
}
}
}
impl Refusable for ModelsDevError {
fn refusal(&self) -> Refusal {
match self {
Self::UnsupportedEndpoint { .. } => Refusal::new(RefusalReason::UnsupportedEndpoint),
Self::NotJson { .. } => Refusal::new(RefusalReason::NotJson),
Self::Schema { pointer, .. } => pointer.clone().map_or_else(
|| Refusal::new(RefusalReason::Schema),
|pointer| Refusal::at(RefusalReason::Schema, pointer),
),
Self::IdMismatch { pointer, .. } => {
Refusal::at(RefusalReason::IdMismatch, pointer.clone())
}
Self::Identifier { pointer, .. } => {
Refusal::at(RefusalReason::Identifier, pointer.clone())
}
Self::UnknownStatus { pointer, .. } => {
Refusal::at(RefusalReason::UnknownStatus, pointer.clone())
}
Self::UnknownModality { pointer, .. } => {
Refusal::at(RefusalReason::UnknownModality, pointer.clone())
}
Self::Price { pointer, .. } => Refusal::at(RefusalReason::Price, pointer.clone()),
Self::UnknownTierType { pointer, .. } => {
Refusal::at(RefusalReason::UnknownTierType, pointer.clone())
}
Self::DuplicateTier { pointer } => {
Refusal::at(RefusalReason::DuplicateTier, pointer.clone())
}
Self::NeutralPrice { pointer } => {
Refusal::at(RefusalReason::NeutralPrice, pointer.clone())
}
Self::UncanonicalizableText { pointer, .. } => {
Refusal::at(RefusalReason::UncanonicalizableText, pointer.clone())
}
Self::AmbiguousModelKey { pointer, .. } => {
Refusal::at(RefusalReason::AmbiguousModelKey, pointer.clone())
}
Self::Content { .. } => Refusal::new(RefusalReason::Content),
}
}
}
impl From<ModelsDevError> for CatalogError {
fn from(error: ModelsDevError) -> Self {
Self::Invalid {
backend: BACKEND,
refusal: error.refusal(),
message: error.to_string(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum PriceRejection {
#[error("`{}` is not a JSON number", excerpt(value))]
NotANumber { value: String },
#[error("`{}` is negative", excerpt(value))]
Negative { value: String },
#[error(
"`{}` is finer than one nano-dollar per million tokens",
excerpt(value)
)]
ExcessPrecision { value: String },
#[error("`{}` is larger than an observed rate can hold", excerpt(value))]
Overflow { value: String },
#[error("a price states `{stated}` without `{missing}`")]
Partial {
stated: &'static str,
missing: &'static str,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ModelsDevAdapter {
source_url: String,
}
impl Default for ModelsDevAdapter {
fn default() -> Self {
Self {
source_url: MODELS_DEV_CATALOG_URL.to_owned(),
}
}
}
impl ModelsDevAdapter {
pub fn new(source_url: impl Into<String>) -> Result<Self, ModelsDevError> {
let source_url = source_url.into();
let path = source_url
.split_once("://")
.map_or(source_url.as_str(), |(_, rest)| rest);
let path = path.split(['?', '#']).next().unwrap_or(path);
if !path.ends_with(SUPPORTED_PATH) {
return Err(ModelsDevError::UnsupportedEndpoint { url: source_url });
}
Ok(Self { source_url })
}
pub fn source_url(&self) -> &str {
&self.source_url
}
pub fn parse(
&self,
payload: &[u8],
validators: SourceValidators,
fetched_at: SystemTime,
) -> Result<CatalogSnapshot, ModelsDevError> {
let text = std::str::from_utf8(payload).map_err(|error| ModelsDevError::NotJson {
message: error.to_string(),
})?;
let mut deserializer = serde_json::Deserializer::from_str(text);
let document: WireCatalog =
serde_path_to_error::deserialize(&mut deserializer).map_err(|error| {
let pointer = json_pointer(error.path());
let inner = error.into_inner();
if inner.is_syntax() || inner.is_eof() {
ModelsDevError::NotJson {
message: inner.to_string(),
}
} else {
ModelsDevError::Schema {
pointer,
message: inner.to_string(),
}
}
})?;
deserializer
.end()
.map_err(|error| ModelsDevError::NotJson {
message: error.to_string(),
})?;
let content = normalize(&document)?;
let source = source_snapshot(
self.source_url.clone(),
SchemaVersion::MODELS_DEV_CATALOG_V1,
payload,
&content,
validators,
fetched_at,
);
Ok(CatalogSnapshot { source, content })
}
}
#[derive(Debug, Deserialize)]
struct WireCatalog {
models: BTreeMap<String, WireModel>,
providers: BTreeMap<String, WireProvider>,
}
#[derive(Debug, Deserialize)]
struct WireProvider {
id: String,
name: String,
#[serde(default)]
doc: Option<String>,
#[serde(default)]
api: Option<String>,
#[serde(default)]
npm: Option<String>,
#[serde(default)]
env: Vec<String>,
models: BTreeMap<String, WireModel>,
}
#[derive(Debug, Deserialize)]
struct WireModel {
id: String,
name: String,
#[serde(default)]
family: Option<String>,
#[serde(default)]
attachment: Option<bool>,
#[serde(default)]
reasoning: Option<bool>,
#[serde(default)]
tool_call: Option<bool>,
#[serde(default)]
temperature: Option<bool>,
#[serde(default)]
structured_output: Option<bool>,
#[serde(default)]
interleaved: Option<WireFlag>,
#[serde(default)]
open_weights: Option<bool>,
#[serde(default)]
experimental: Option<WireFlag>,
#[serde(default)]
status: Option<String>,
#[serde(default)]
knowledge: Option<String>,
#[serde(default)]
release_date: Option<String>,
#[serde(default)]
last_updated: Option<String>,
#[serde(default)]
modalities: WireModalities,
#[serde(default)]
limit: WireLimit,
#[serde(default)]
cost: Option<WireCost>,
#[serde(default)]
provider: Option<WireModelProvider>,
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum WireFlag {
Stated(bool),
Configured(BTreeMap<String, serde_json::Value>),
}
impl WireFlag {
const fn configurable(&self) -> bool {
match self {
Self::Stated(stated) => *stated,
Self::Configured(_) => true,
}
}
const fn asserted(&self) -> bool {
match self {
Self::Stated(stated) => *stated,
Self::Configured(_) => false,
}
}
}
#[derive(Debug, Default, Deserialize)]
struct WireModalities {
#[serde(default)]
input: Vec<String>,
#[serde(default)]
output: Vec<String>,
}
#[derive(Debug, Default, Deserialize)]
struct WireLimit {
#[serde(default)]
context: Option<u64>,
#[serde(default)]
input: Option<u64>,
#[serde(default)]
output: Option<u64>,
}
#[derive(Debug, Deserialize)]
struct WireModelProvider {
#[serde(default)]
api: Option<String>,
#[serde(default)]
npm: Option<String>,
#[serde(default)]
shape: Option<String>,
}
#[derive(Debug, Deserialize)]
struct WireCost {
#[serde(default)]
input: Option<Box<RawValue>>,
#[serde(default)]
output: Option<Box<RawValue>>,
#[serde(default)]
cache_read: Option<Box<RawValue>>,
#[serde(default)]
cache_write: Option<Box<RawValue>>,
#[serde(default)]
reasoning: Option<Box<RawValue>>,
#[serde(default)]
input_audio: Option<Box<RawValue>>,
#[serde(default)]
output_audio: Option<Box<RawValue>>,
#[serde(default)]
tiers: Vec<WireTier>,
#[serde(default)]
context_over_200k: Option<WireTierRates>,
}
#[derive(Debug, Deserialize)]
struct WireTier {
tier: WireTierKey,
#[serde(default)]
input: Option<Box<RawValue>>,
#[serde(default)]
output: Option<Box<RawValue>>,
#[serde(default)]
cache_read: Option<Box<RawValue>>,
#[serde(default)]
cache_write: Option<Box<RawValue>>,
#[serde(default)]
reasoning: Option<Box<RawValue>>,
#[serde(default)]
input_audio: Option<Box<RawValue>>,
#[serde(default)]
output_audio: Option<Box<RawValue>>,
}
#[derive(Debug, Deserialize)]
struct WireTierKey {
#[serde(rename = "type")]
kind: String,
#[serde(default)]
size: Option<u64>,
}
#[derive(Debug, Deserialize)]
struct WireTierRates {
#[serde(default)]
input: Option<Box<RawValue>>,
#[serde(default)]
output: Option<Box<RawValue>>,
#[serde(default)]
cache_read: Option<Box<RawValue>>,
#[serde(default)]
cache_write: Option<Box<RawValue>>,
#[serde(default)]
reasoning: Option<Box<RawValue>>,
#[serde(default)]
input_audio: Option<Box<RawValue>>,
#[serde(default)]
output_audio: Option<Box<RawValue>>,
}
struct WireRates<'a> {
input: Option<&'a RawValue>,
output: Option<&'a RawValue>,
cache_read: Option<&'a RawValue>,
cache_write: Option<&'a RawValue>,
reasoning: Option<&'a RawValue>,
input_audio: Option<&'a RawValue>,
output_audio: Option<&'a RawValue>,
}
impl WireCost {
fn states_only_base_rates(&self) -> bool {
self.cache_read.is_none()
&& self.cache_write.is_none()
&& self.reasoning.is_none()
&& self.input_audio.is_none()
&& self.output_audio.is_none()
&& self.tiers.is_empty()
&& self.context_over_200k.is_none()
}
fn rates(&self) -> WireRates<'_> {
WireRates {
input: self.input.as_deref(),
output: self.output.as_deref(),
cache_read: self.cache_read.as_deref(),
cache_write: self.cache_write.as_deref(),
reasoning: self.reasoning.as_deref(),
input_audio: self.input_audio.as_deref(),
output_audio: self.output_audio.as_deref(),
}
}
}
impl WireTier {
fn rates(&self) -> WireRates<'_> {
WireRates {
input: self.input.as_deref(),
output: self.output.as_deref(),
cache_read: self.cache_read.as_deref(),
cache_write: self.cache_write.as_deref(),
reasoning: self.reasoning.as_deref(),
input_audio: self.input_audio.as_deref(),
output_audio: self.output_audio.as_deref(),
}
}
}
impl WireTierRates {
fn rates(&self) -> WireRates<'_> {
WireRates {
input: self.input.as_deref(),
output: self.output.as_deref(),
cache_read: self.cache_read.as_deref(),
cache_write: self.cache_write.as_deref(),
reasoning: self.reasoning.as_deref(),
input_audio: self.input_audio.as_deref(),
output_audio: self.output_audio.as_deref(),
}
}
}
type NeutralRecords = BTreeMap<ModelId, (ModelFacts, JsonPointer)>;
fn resolve_provider_models<'a>(
published: &BTreeMap<&'a str, ModelId>,
neutral: &NeutralRecords,
pointers: &BTreeMap<&'a str, JsonPointer>,
) -> Result<BTreeMap<&'a str, ModelId>, ModelsDevError> {
let mut resolved = BTreeMap::new();
for (key, id) in published {
let pointer = &pointers[key];
resolved.insert(*key, canonical_model_id(id, neutral, pointer)?);
}
Ok(resolved)
}
fn canonical_model_id(
published: &ModelId,
neutral: &NeutralRecords,
pointer: &JsonPointer,
) -> Result<ModelId, ModelsDevError> {
if neutral.contains_key(published) {
return Ok(published.clone());
}
let tail = format!("/{published}");
let candidates: Vec<&ModelId> = neutral
.keys()
.filter(|id| id.as_str().ends_with(&tail))
.collect();
match candidates.as_slice() {
[] => Ok(published.clone()),
[only] => Ok((*only).clone()),
many => Err(ModelsDevError::AmbiguousModelKey {
pointer: pointer.clone(),
key: published.to_string(),
candidates: many.iter().map(ToString::to_string).collect(),
}),
}
}
fn normalize(document: &WireCatalog) -> Result<CatalogContent, ModelsDevError> {
let root = JsonPointer::new("");
let providers_pointer = root.child("providers");
let models_pointer = root.child("models");
let mut neutral: NeutralRecords = BTreeMap::new();
for (key, model) in &document.models {
let pointer = models_pointer.child(key);
let id = identifier(key, &pointer)?;
expect_key(key, &model.id, &pointer)?;
if model.cost.is_some() {
return Err(ModelsDevError::NeutralPrice { pointer });
}
neutral.insert(id, (facts(model, &pointer)?, pointer));
}
let mut providers = Vec::with_capacity(document.providers.len());
let mut offerings: BTreeMap<ModelId, Vec<ProviderOffering>> = BTreeMap::new();
for (key, provider) in &document.providers {
let pointer = providers_pointer.child(key);
let id = identifier(key, &pointer)?;
expect_key(key, &provider.id, &pointer)?;
providers.push(CatalogProvider {
id: id.clone(),
display_name: text(Some(&provider.name), &pointer.child("name"))?,
doc_url: text(provider.doc.as_deref(), &pointer.child("doc"))?,
endpoint: ProviderEndpoint {
api_base: text(provider.api.as_deref(), &pointer.child("api"))?,
client_package: text(provider.npm.as_deref(), &pointer.child("npm"))?,
wire_shape: None,
},
env_vars: {
let env_pointer = pointer.child("env");
let mut names = Vec::with_capacity(provider.env.len());
for (index, env) in provider.env.iter().enumerate() {
if let Some(name) = text(Some(env), &env_pointer.child(&index.to_string()))? {
names.push(name);
}
}
names
},
pointer: pointer.clone(),
});
let offered_pointer = pointer.child("models");
let mut published_ids = BTreeMap::new();
let mut pointers = BTreeMap::new();
for (model_key, model) in &provider.models {
let model_pointer = offered_pointer.child(model_key);
let published = identifier(model_key, &model_pointer)?;
expect_key(model_key, &model.id, &model_pointer)?;
published_ids.insert(model_key.as_str(), published);
pointers.insert(model_key.as_str(), model_pointer);
}
let resolved = resolve_provider_models(&published_ids, &neutral, &pointers)?;
for (model_key, model) in &provider.models {
let model_pointer = pointers[model_key.as_str()].clone();
let model_id = resolved[model_key.as_str()].clone();
let endpoint = match model.provider.as_ref() {
None => ProviderEndpoint::default(),
Some(endpoint) => {
let pointer = model_pointer.child("provider");
ProviderEndpoint {
api_base: text(endpoint.api.as_deref(), &pointer.child("api"))?,
client_package: text(endpoint.npm.as_deref(), &pointer.child("npm"))?,
wire_shape: text(endpoint.shape.as_deref(), &pointer.child("shape"))?,
}
}
};
offerings
.entry(model_id.clone())
.or_default()
.push(ProviderOffering {
provider: id.clone(),
model: model_id,
published_model_id: model.id.clone(),
facts: facts(model, &model_pointer)?,
overrides: Vec::new(),
price: price(model.cost.as_ref(), &model_pointer)?,
endpoint,
pointer: model_pointer,
});
}
}
let ids: BTreeSet<ModelId> = neutral.keys().chain(offerings.keys()).cloned().collect();
let models = ids
.into_iter()
.map(|id| {
let neutral_facts = neutral.get(&id).map(|(facts, _)| facts.clone());
let mut model_offerings = offerings.remove(&id).unwrap_or_default();
if let Some(neutral_facts) = &neutral_facts {
for offering in &mut model_offerings {
offering.overrides = offering
.facts
.differences(neutral_facts)
.into_iter()
.map(|field| (field, field_pointer(&offering.pointer, field)))
.collect();
}
}
CatalogModelEntry {
id,
neutral: neutral_facts,
offerings: model_offerings,
}
})
.collect();
CatalogContent::new(providers, models).map_err(|source| ModelsDevError::Content { source })
}
fn json_pointer(path: &serde_path_to_error::Path) -> Option<JsonPointer> {
let mut pointer = JsonPointer::new("");
let mut named = false;
for segment in path.iter() {
match segment {
serde_path_to_error::Segment::Seq { index } => {
pointer = pointer.child(&index.to_string());
named = true;
}
serde_path_to_error::Segment::Map { key } => {
pointer = pointer.child(key);
named = true;
}
serde_path_to_error::Segment::Enum { variant } => {
pointer = pointer.child(variant);
named = true;
}
serde_path_to_error::Segment::Unknown => {}
}
}
named.then_some(pointer)
}
fn field_pointer(offering: &JsonPointer, field: ModelField) -> JsonPointer {
match field {
ModelField::DisplayName => offering.child("name"),
ModelField::Family => offering.child("family"),
ModelField::Capabilities => offering.clone(),
ModelField::InputModalities => offering.child("modalities").child("input"),
ModelField::OutputModalities => offering.child("modalities").child("output"),
ModelField::ContextTokens => offering.child("limit").child("context"),
ModelField::InputTokens => offering.child("limit").child("input"),
ModelField::OutputTokens => offering.child("limit").child("output"),
ModelField::Lifecycle => offering.child("status"),
ModelField::KnowledgeCutoff => offering.child("knowledge"),
ModelField::ReleaseDate => offering.child("release_date"),
ModelField::LastUpdated => offering.child("last_updated"),
ModelField::Endpoint => offering.child("provider"),
ModelField::PublishedModelId => offering.child("id"),
}
}
fn text(value: Option<&str>, pointer: &JsonPointer) -> Result<Option<String>, ModelsDevError> {
let Some(value) = value else {
return Ok(None);
};
let collapsed = value.split_whitespace().collect::<Vec<_>>().join(" ");
if collapsed.is_empty() {
return Ok(None);
}
CanonicalValue::string(&collapsed)
.to_canonical_bytes()
.map_err(|source| ModelsDevError::UncanonicalizableText {
pointer: pointer.clone(),
source,
})?;
Ok(Some(collapsed))
}
fn identifier(key: &str, pointer: &JsonPointer) -> Result<ModelId, ModelsDevError> {
ModelId::parse(key).map_err(|source| ModelsDevError::identifier(pointer, source))
}
fn expect_key(key: &str, id: &str, pointer: &JsonPointer) -> Result<(), ModelsDevError> {
if key == id {
return Ok(());
}
Err(ModelsDevError::IdMismatch {
pointer: pointer.clone(),
key: key.to_owned(),
id: id.to_owned(),
})
}
fn facts(model: &WireModel, pointer: &JsonPointer) -> Result<ModelFacts, ModelsDevError> {
let mut capabilities = BTreeSet::new();
for (stated, capability) in [
(model.attachment, ModelCapability::Attachment),
(model.reasoning, ModelCapability::Reasoning),
(model.tool_call, ModelCapability::ToolCall),
(model.temperature, ModelCapability::Temperature),
(model.structured_output, ModelCapability::StructuredOutput),
(
model.interleaved.as_ref().map(WireFlag::configurable),
ModelCapability::Interleaved,
),
(model.open_weights, ModelCapability::OpenWeights),
(
model.experimental.as_ref().map(WireFlag::asserted),
ModelCapability::Experimental,
),
] {
if stated == Some(true) {
capabilities.insert(capability);
}
}
Ok(ModelFacts {
display_name: text(Some(&model.name), &pointer.child("name"))?,
family: text(model.family.as_deref(), &pointer.child("family"))?,
capabilities,
input_modalities: modalities(
&model.modalities.input,
&pointer.child("modalities").child("input"),
)?,
output_modalities: modalities(
&model.modalities.output,
&pointer.child("modalities").child("output"),
)?,
limits: ModelLimits {
context_tokens: model.limit.context,
input_tokens: model.limit.input,
output_tokens: model.limit.output,
},
lifecycle: lifecycle(model.status.as_deref(), &pointer.child("status"))?,
knowledge_cutoff: text(model.knowledge.as_deref(), &pointer.child("knowledge"))?,
release_date: text(
model.release_date.as_deref(),
&pointer.child("release_date"),
)?,
last_updated: text(
model.last_updated.as_deref(),
&pointer.child("last_updated"),
)?,
})
}
fn modalities(
stated: &[String],
pointer: &JsonPointer,
) -> Result<BTreeSet<Modality>, ModelsDevError> {
stated
.iter()
.map(|modality| {
Modality::parse(modality).ok_or_else(|| ModelsDevError::UnknownModality {
pointer: pointer.clone(),
modality: modality.clone(),
})
})
.collect()
}
fn lifecycle(
status: Option<&str>,
pointer: &JsonPointer,
) -> Result<ModelLifecycle, ModelsDevError> {
let Some(status) = status else {
return Ok(ModelLifecycle::Available);
};
ModelLifecycle::ALL
.iter()
.copied()
.find(|lifecycle| lifecycle.as_str() == status)
.ok_or_else(|| ModelsDevError::UnknownStatus {
pointer: pointer.clone(),
status: status.to_owned(),
})
}
fn price(
cost: Option<&WireCost>,
pointer: &JsonPointer,
) -> Result<Option<ObservedPrice>, ModelsDevError> {
let Some(cost) = cost else {
return Ok(None);
};
let pointer = pointer.child("cost");
let stated = cost.rates();
if stated.input.is_none() && stated.output.is_none() {
if cost.states_only_base_rates() {
return Ok(None);
}
return Err(ModelsDevError::Price {
pointer,
reason: PriceRejection::Partial {
stated: "tiered or optional rates",
missing: "input and output",
},
});
}
let base = rates(&stated, &pointer)?;
let mut tiers = Vec::new();
for (index, tier) in cost.tiers.iter().enumerate() {
let tier_pointer = pointer.child("tiers").child(&index.to_string());
let threshold = match tier.tier.kind.as_str() {
"context" => PriceTierThreshold::ContextOver {
tokens: tier.tier.size.ok_or_else(|| {
ModelsDevError::schema_at(
tier_pointer.child("tier"),
"a `context` tier states no `size`",
)
})?,
},
kind => {
return Err(ModelsDevError::UnknownTierType {
pointer: tier_pointer,
kind: kind.to_owned(),
});
}
};
tiers.push(PriceTier {
threshold,
rates: rates(&tier.rates(), &tier_pointer)?,
});
}
if let Some(legacy) = &cost.context_over_200k {
let tier_pointer = pointer.child("context_over_200k");
let threshold = PriceTierThreshold::ContextOver {
tokens: LEGACY_LONG_CONTEXT_TOKENS,
};
let legacy = PriceTier {
threshold,
rates: rates(&legacy.rates(), &tier_pointer)?,
};
match tiers.iter().find(|tier| tier.threshold == threshold) {
Some(stated) if *stated == legacy => {}
Some(_) => return Err(ModelsDevError::DuplicateTier { pointer }),
None => match tiers
.iter()
.enumerate()
.filter(|(_, tier)| tier.threshold > threshold)
.min_by_key(|(_, tier)| tier.threshold)
{
Some((index, lowest)) if lowest.rates == legacy.rates => {
tiers[index].threshold = threshold;
}
_ => tiers.push(legacy),
},
}
}
tiers.sort_by_key(|tier| tier.threshold);
if tiers
.windows(2)
.any(|pair| pair[0].threshold == pair[1].threshold)
{
return Err(ModelsDevError::DuplicateTier { pointer });
}
Ok(Some(ObservedPrice { base, tiers }))
}
const LEGACY_LONG_CONTEXT_TOKENS: u64 = 200_000;
fn rates(stated: &WireRates<'_>, pointer: &JsonPointer) -> Result<PriceRates, ModelsDevError> {
let (Some(input), Some(output)) = (stated.input, stated.output) else {
let (present, missing) = match (stated.input.is_some(), stated.output.is_some()) {
(true, _) => ("input", "output"),
(_, true) => ("output", "input"),
_ => ("only optional rates", "input and output"),
};
return Err(ModelsDevError::Price {
pointer: pointer.clone(),
reason: PriceRejection::Partial {
stated: present,
missing,
},
});
};
Ok(PriceRates {
input: rate(input, &pointer.child("input"))?,
output: rate(output, &pointer.child("output"))?,
cache_read: optional_rate(stated.cache_read, pointer, "cache_read")?,
cache_write: optional_rate(stated.cache_write, pointer, "cache_write")?,
reasoning: optional_rate(stated.reasoning, pointer, "reasoning")?,
input_audio: optional_rate(stated.input_audio, pointer, "input_audio")?,
output_audio: optional_rate(stated.output_audio, pointer, "output_audio")?,
})
}
fn optional_rate(
raw: Option<&RawValue>,
pointer: &JsonPointer,
field: &str,
) -> Result<Option<ObservedRate>, ModelsDevError> {
raw.map(|raw| rate(raw, &pointer.child(field))).transpose()
}
fn rate(raw: &RawValue, pointer: &JsonPointer) -> Result<ObservedRate, ModelsDevError> {
nano_dollars_per_million(raw.get()).map_err(|reason| ModelsDevError::Price {
pointer: pointer.clone(),
reason,
})
}
struct Decimal {
digits: u128,
exponent: i32,
}
fn nano_dollars_per_million(text: &str) -> Result<ObservedRate, PriceRejection> {
const NANO_DOLLARS_PER_DOLLAR: i32 = 9;
let decimal = parse_decimal(text)?;
let shift = decimal
.exponent
.checked_add(NANO_DOLLARS_PER_DOLLAR)
.ok_or_else(|| PriceRejection::Overflow {
value: text.to_owned(),
})?;
let nanos = if shift >= 0 {
let factor = 10u128
.checked_pow(u32::try_from(shift).map_err(|_| PriceRejection::Overflow {
value: text.to_owned(),
})?)
.ok_or_else(|| PriceRejection::Overflow {
value: text.to_owned(),
})?;
decimal
.digits
.checked_mul(factor)
.ok_or_else(|| PriceRejection::Overflow {
value: text.to_owned(),
})?
} else {
let divisor = 10u128.checked_pow(shift.unsigned_abs()).ok_or_else(|| {
PriceRejection::ExcessPrecision {
value: text.to_owned(),
}
})?;
let remainder = decimal.digits % divisor;
let quotient = decimal.digits / divisor;
match remainder {
0 => quotient,
_ if is_binary_artifact(decimal.digits, divisor, remainder) => {
if remainder * 2 >= divisor {
quotient + 1
} else {
quotient
}
}
_ => {
return Err(PriceRejection::ExcessPrecision {
value: text.to_owned(),
});
}
}
};
u64::try_from(nanos)
.map(ObservedRate::from_nanos)
.map_err(|_| PriceRejection::Overflow {
value: text.to_owned(),
})
}
fn parse_decimal(text: &str) -> Result<Decimal, PriceRejection> {
let not_a_number = || PriceRejection::NotANumber {
value: text.to_owned(),
};
if text.is_empty() {
return Err(not_a_number());
}
if let Some(rest) = text.strip_prefix('-') {
return parse_decimal(rest).and(Err(PriceRejection::Negative {
value: text.to_owned(),
}));
}
let (mantissa, exponent) = match text.split_once(['e', 'E']) {
Some((mantissa, exponent)) => {
let stated = exponent.strip_prefix('+').unwrap_or(exponent);
let exponent = stated.parse::<i32>().map_err(|_| {
match stated.strip_prefix('-') {
Some(magnitude) if all_digits(magnitude) && !magnitude.is_empty() => {
PriceRejection::ExcessPrecision {
value: text.to_owned(),
}
}
Some(_) => not_a_number(),
None if all_digits(stated) && !stated.is_empty() => PriceRejection::Overflow {
value: text.to_owned(),
},
None => not_a_number(),
}
})?;
(mantissa, exponent)
}
None => (text, 0),
};
let (integer, fraction) = match mantissa.split_once('.') {
Some((integer, fraction)) => (integer, fraction),
None => (mantissa, ""),
};
if integer.is_empty() || !all_digits(integer) || (mantissa.contains('.') && fraction.is_empty())
{
return Err(not_a_number());
}
if !all_digits(fraction) {
return Err(not_a_number());
}
let mut digits = String::with_capacity(integer.len() + fraction.len());
digits.push_str(integer);
digits.push_str(fraction);
let digits = digits
.parse::<u128>()
.map_err(|_| PriceRejection::Overflow {
value: text.to_owned(),
})?;
let fraction_length = i32::try_from(fraction.len()).map_err(|_| PriceRejection::Overflow {
value: text.to_owned(),
})?;
Ok(Decimal {
digits,
exponent: exponent.checked_sub(fraction_length).ok_or_else(|| {
PriceRejection::ExcessPrecision {
value: text.to_owned(),
}
})?,
})
}
fn is_binary_artifact(digits: u128, divisor: u128, remainder: u128) -> bool {
const TOLERANCE: u128 = 1_000_000_000_000;
let distance = remainder.min(divisor - remainder);
distance
.checked_mul(TOLERANCE)
.is_some_and(|scaled| scaled <= digits)
}
fn all_digits(text: &str) -> bool {
text.bytes().all(|byte| byte.is_ascii_digit())
}
pub const SEED_PAYLOAD: &str = include_str!("fixtures/models_dev/catalog.seed.json");
pub fn seed_fetched_at() -> SystemTime {
SystemTime::UNIX_EPOCH + Duration::from_secs(1_786_566_474)
}
fn seed_validators(content: &CatalogContent) -> SourceValidators {
SourceValidators {
etag: Some(ETag(format!("W/\"seed-{}\"", content.content_id()))),
last_modified: None,
}
}
pub fn seed_snapshot() -> CatalogSnapshot {
let mut snapshot = ModelsDevAdapter::default()
.parse(
SEED_PAYLOAD.as_bytes(),
SourceValidators::default(),
seed_fetched_at(),
)
.expect("the bundled models.dev seed parses");
snapshot.source.validators = seed_validators(&snapshot.content);
snapshot
}
#[derive(Debug, Default, Clone, Copy)]
pub struct SeedCatalogSource;
#[async_trait]
impl CatalogSource for SeedCatalogSource {
fn name(&self) -> &'static str {
"models.dev-seed"
}
fn capabilities(&self) -> Capabilities {
Capabilities::new(&[Capability::IncrementalRefresh, Capability::PriceMetadata])
}
async fn refresh(
&self,
since: Option<&SourceValidators>,
) -> Result<CatalogRefresh, CatalogError> {
let snapshot = seed_snapshot();
if since == Some(&snapshot.source.validators) {
return Ok(CatalogRefresh::Unchanged {
validators: snapshot.source.validators,
});
}
Ok(CatalogRefresh::Updated {
snapshot: Box::new(snapshot),
payload: RawPayload::new(SEED_PAYLOAD.as_bytes()),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FetchResponse {
NotModified { validators: SourceValidators },
Payload {
bytes: Vec<u8>,
validators: SourceValidators,
},
}
pub const MAX_PAYLOAD_BYTES: usize = 64 * 1024 * 1024;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum FetchError {
#[error("{message}")]
Transport { message: String },
#[error("upstream answered HTTP {status}")]
Status { status: u16 },
#[error("payload exceeds the {limit}-byte ceiling")]
TooLarge { limit: usize },
}
const fn misconfigured(status: u16) -> bool {
matches!(status, 300..=499) && !matches!(status, 408 | 429)
}
impl Refusable for FetchError {
fn refusal(&self) -> Refusal {
Refusal::new(match self {
Self::Transport { .. } => RefusalReason::Unreachable,
Self::Status { status } if *status == 401 || *status == 403 => RefusalReason::Denied,
Self::Status { status } if misconfigured(*status) => RefusalReason::UnsupportedEndpoint,
Self::Status { .. } => RefusalReason::Unreachable,
Self::TooLarge { .. } => RefusalReason::Oversized,
})
}
}
impl From<FetchError> for CatalogError {
fn from(error: FetchError) -> Self {
let refusal = error.refusal();
match error {
FetchError::Status { status } if status == 401 || status == 403 => Self::Denied {
backend: BACKEND,
refusal,
message: error.to_string(),
},
FetchError::Status { status } if misconfigured(status) => Self::Misconfigured {
backend: BACKEND,
refusal,
message: error.to_string(),
},
FetchError::TooLarge { .. } => Self::Invalid {
backend: BACKEND,
refusal,
message: error.to_string(),
},
error => Self::Unavailable {
backend: BACKEND,
refusal,
message: error.to_string(),
},
}
}
}
pub const DECLARED_RESERVE_BYTES: usize = 1024 * 1024;
fn declared_reserve(declared: Option<u64>, limit: usize) -> usize {
declared
.and_then(|declared| usize::try_from(declared).ok())
.unwrap_or_default()
.min(limit)
.min(DECLARED_RESERVE_BYTES)
}
pub async fn bounded_body(
mut response: reqwest::Response,
limit: usize,
) -> Result<Vec<u8>, FetchError> {
let declared = response.content_length();
if declared.is_some_and(|declared| declared > limit as u64) {
return Err(FetchError::TooLarge { limit });
}
let mut body = Vec::with_capacity(declared_reserve(declared, limit));
while let Some(chunk) = response
.chunk()
.await
.map_err(|error| FetchError::Transport {
message: error.to_string(),
})?
{
if body.len() + chunk.len() > limit {
return Err(FetchError::TooLarge { limit });
}
body.extend_from_slice(&chunk);
}
Ok(body)
}
#[async_trait]
pub trait CatalogFetch: Send + Sync {
async fn get(
&self,
url: &str,
validators: Option<&SourceValidators>,
) -> Result<FetchResponse, FetchError>;
}
pub struct HttpCatalogFetch {
client: reqwest::Client,
limit: usize,
}
impl std::fmt::Debug for HttpCatalogFetch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HttpCatalogFetch")
.field("limit", &self.limit)
.finish_non_exhaustive()
}
}
impl HttpCatalogFetch {
pub fn new(timeout: Duration) -> Result<Self, FetchError> {
let client = reqwest::Client::builder()
.timeout(timeout)
.redirect(reqwest::redirect::Policy::none())
.build()
.map_err(|error| FetchError::Transport {
message: error.to_string(),
})?;
Ok(Self {
client,
limit: MAX_PAYLOAD_BYTES,
})
}
#[must_use]
pub const fn holding_at_most(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
}
#[async_trait]
impl CatalogFetch for HttpCatalogFetch {
async fn get(
&self,
url: &str,
validators: Option<&SourceValidators>,
) -> Result<FetchResponse, FetchError> {
let mut request = self.client.get(url);
if let Some(ETag(etag)) = validators.and_then(|validators| validators.etag.as_ref()) {
request = request.header(reqwest::header::IF_NONE_MATCH, etag);
}
if let Some(HttpDate(date)) =
validators.and_then(|validators| validators.last_modified.as_ref())
{
request = request.header(reqwest::header::IF_MODIFIED_SINCE, date);
}
let response = request
.send()
.await
.map_err(|error| FetchError::Transport {
message: error.to_string(),
})?;
let validators = SourceValidators {
etag: response
.headers()
.get(reqwest::header::ETAG)
.and_then(|value| value.to_str().ok())
.map(|value| ETag(value.to_owned())),
last_modified: response
.headers()
.get(reqwest::header::LAST_MODIFIED)
.and_then(|value| value.to_str().ok())
.map(|value| HttpDate(value.to_owned())),
};
match response.status().as_u16() {
304 => Ok(FetchResponse::NotModified { validators }),
200 => Ok(FetchResponse::Payload {
bytes: bounded_body(response, self.limit).await?,
validators,
}),
status => Err(FetchError::Status { status }),
}
}
}
#[derive(Debug)]
pub struct ModelsDevSource<F> {
adapter: ModelsDevAdapter,
fetch: F,
payload_limit: usize,
}
impl<F: CatalogFetch> ModelsDevSource<F> {
pub const fn new(adapter: ModelsDevAdapter, fetch: F) -> Self {
Self {
adapter,
fetch,
payload_limit: MAX_PAYLOAD_BYTES,
}
}
#[must_use]
pub const fn with_payload_limit(mut self, limit: usize) -> Self {
self.payload_limit = limit;
self
}
}
#[async_trait]
impl<F: CatalogFetch> CatalogSource for ModelsDevSource<F> {
fn name(&self) -> &'static str {
BACKEND
}
fn capabilities(&self) -> Capabilities {
Capabilities::new(&[Capability::IncrementalRefresh, Capability::PriceMetadata])
}
async fn refresh(
&self,
since: Option<&SourceValidators>,
) -> Result<CatalogRefresh, CatalogError> {
match self.fetch.get(self.adapter.source_url(), since).await? {
FetchResponse::NotModified { validators } => {
Ok(CatalogRefresh::Unchanged { validators })
}
FetchResponse::Payload { bytes, validators } => {
if bytes.len() > self.payload_limit {
return Err(FetchError::TooLarge {
limit: self.payload_limit,
}
.into());
}
let snapshot = self.adapter.parse(&bytes, validators, SystemTime::now())?;
Ok(CatalogRefresh::Updated {
snapshot: Box::new(snapshot),
payload: RawPayload::new(bytes),
})
}
}
}
}
#[cfg(test)]
mod tests {
use std::net::SocketAddr;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use axum::extract::State;
use axum::http::{HeaderMap, StatusCode, header};
use axum::response::{IntoResponse, Response};
use axum::routing::get;
use super::super::catalog::{
Admission, CatalogChange, LastKnownGoodCatalog, ModelCapability, ProviderId,
};
use super::super::{BackendFailure, FailureCategory};
use super::*;
const IDENTITY: &str = include_str!("fixtures/models_dev/catalog.identity.json");
const ALIASES: &str = include_str!("fixtures/models_dev/catalog.aliases.json");
const IDENTITY_REORDERED: &str =
include_str!("fixtures/models_dev/catalog.identity-reordered.json");
const IDENTITY_CONTENT_ID: &str =
"sha256:07f8cd2d43cdbbe172a71954a7994db79c2b38c3d8a034e327b9fed617a89dae";
fn drift(name: &str) -> &'static str {
match name {
"limit-type" => include_str!("fixtures/models_dev/drift.limit-type.json"),
"unknown-status" => include_str!("fixtures/models_dev/drift.unknown-status.json"),
"unknown-modality" => include_str!("fixtures/models_dev/drift.unknown-modality.json"),
"price-precision" => include_str!("fixtures/models_dev/drift.price-precision.json"),
"price-negative" => include_str!("fixtures/models_dev/drift.price-negative.json"),
"price-type" => include_str!("fixtures/models_dev/drift.price-type.json"),
"price-partial" => include_str!("fixtures/models_dev/drift.price-partial.json"),
"tier-type" => include_str!("fixtures/models_dev/drift.tier-type.json"),
"tier-without-size" => {
include_str!("fixtures/models_dev/drift.tier-without-size.json")
}
"tier-duplicate" => include_str!("fixtures/models_dev/drift.tier-duplicate.json"),
"model-id-mismatch" => include_str!("fixtures/models_dev/drift.model-id-mismatch.json"),
"model-id-case" => include_str!("fixtures/models_dev/drift.model-id-case.json"),
"provider-id-mismatch" => {
include_str!("fixtures/models_dev/drift.provider-id-mismatch.json")
}
"neutral-price" => include_str!("fixtures/models_dev/drift.neutral-price.json"),
"missing-providers" => include_str!("fixtures/models_dev/drift.missing-providers.json"),
"providers-empty" => include_str!("fixtures/models_dev/drift.providers-empty.json"),
"missing-model-name" => {
include_str!("fixtures/models_dev/drift.missing-model-name.json")
}
"top-level-array" => include_str!("fixtures/models_dev/drift.top-level-array.json"),
"empty" => include_str!("fixtures/models_dev/drift.empty.json"),
"not-json" => include_str!("fixtures/models_dev/drift.not-json.json"),
"control-character" => {
include_str!("fixtures/models_dev/drift.control-character.json")
}
"price-tiers-without-base" => {
include_str!("fixtures/models_dev/drift.price-tiers-without-base.json")
}
"tier-without-base" => {
include_str!("fixtures/models_dev/drift.tier-without-base.json")
}
"model-key-ambiguous" => {
include_str!("fixtures/models_dev/drift.model-key-ambiguous.json")
}
other => panic!("no drift fixture named `{other}`"),
}
}
fn parse(payload: &str) -> Result<CatalogSnapshot, ModelsDevError> {
ModelsDevAdapter::default().parse(
payload.as_bytes(),
SourceValidators::etag("\"fixture\""),
SystemTime::UNIX_EPOCH,
)
}
#[test]
fn only_the_catalog_document_is_an_accepted_source() {
assert_eq!(
ModelsDevAdapter::new(MODELS_DEV_CATALOG_URL)
.expect("the supported endpoint")
.source_url(),
MODELS_DEV_CATALOG_URL
);
assert!(ModelsDevAdapter::new("https://mirror.example/models.dev/catalog.json").is_ok());
for rejected in [
"https://models.dev/api.json",
"https://models.dev/models.json",
"https://models.dev/",
] {
assert_eq!(
ModelsDevAdapter::new(rejected),
Err(ModelsDevError::UnsupportedEndpoint {
url: rejected.to_owned()
}),
"`{rejected}` is a different document shape"
);
}
}
#[test]
fn an_unsupported_long_source_url_keeps_its_rejected_suffix_visible_and_bounded() {
const REJECTED_SUFFIX: &str = "not-the-catalogue.json";
let rejected = format!(
"https://mirror.example/{}/{}",
"snapshot/".repeat(1024),
REJECTED_SUFFIX
);
let error = ModelsDevAdapter::new(&rejected)
.expect_err("the unique rejected suffix is not the catalogue");
let rendered = error.to_string();
assert!(
rendered.contains(REJECTED_SUFFIX),
"the diagnostic must preserve the unique suffix that made the URL invalid: {rendered}"
);
assert!(
rendered.contains(&format!("… ({} bytes) …", rejected.len())) && rendered.len() < 512,
"the rejected URL must remain bounded while retaining its tail: {rendered}"
);
}
#[test]
fn a_schema_refusal_stays_bounded_and_still_says_where() {
let hostile = format!(
r#"{{"models":{{"gpt-5.5":{{"id":"gpt-5.5","name":"gpt-5.5","limit":{{"context":"{}"}}}}}}}}"#,
"n".repeat(4 * 1024 * 1024)
);
let refusal = parse(&hostile)
.expect_err("a context limit that is a string is schema drift")
.to_string();
assert!(
refusal.len() < 512,
"a refusal an upstream can size is a log amplifier: {} bytes",
refusal.len()
);
assert!(
refusal.contains("invalid type: string")
&& refusal.contains("… (")
&& refusal.contains("bytes) …")
&& refusal.contains("at line ")
&& refusal.contains(" column "),
"the bounded head and tail preserve both the rejected value and its locator: {refusal}"
);
}
#[test]
fn normalization_is_independent_of_key_order_formatting_and_unknown_fields() {
let ordered = parse(IDENTITY).expect("fixture parses");
let reordered = parse(IDENTITY_REORDERED).expect("reordered fixture parses");
assert_eq!(ordered.content, reordered.content);
assert_eq!(ordered.source.content_id, reordered.source.content_id);
assert_ne!(
ordered.source.raw, reordered.source.raw,
"the raw payloads differ, and the raw digest says so"
);
}
#[test]
fn the_content_identity_is_stable_across_releases() {
let snapshot = parse(IDENTITY).expect("fixture parses");
assert_eq!(snapshot.source.content_id.to_string(), IDENTITY_CONTENT_ID);
}
#[test]
fn provider_offerings_keep_their_overrides_with_provenance() {
let snapshot = parse(IDENTITY).expect("fixture parses");
let id = ModelId::parse("openai/gpt-5.5").expect("id");
let entry = snapshot.content.model(&id).expect("the fixture's model");
let neutral = entry.neutral.as_ref().expect("a neutral record");
assert_eq!(neutral.limits.context_tokens, Some(1_050_000));
let openai = entry
.offering(&ProviderId::parse("openai").expect("id"))
.expect("the first-party offering");
assert!(
!openai.has_overrides(),
"an offering that agrees with the neutral record overrides nothing"
);
let aggregator = entry
.offering(&ProviderId::parse("hpc-ai").expect("id"))
.expect("the aggregator's offering");
let overrides: Vec<(&str, &str)> = aggregator
.overrides
.iter()
.map(|(field, pointer)| (field.as_str(), pointer.as_str()))
.collect();
assert_eq!(
overrides,
vec![
("capabilities", "/providers/hpc-ai/models/openai~1gpt-5.5"),
(
"input_modalities",
"/providers/hpc-ai/models/openai~1gpt-5.5/modalities/input"
),
(
"context_tokens",
"/providers/hpc-ai/models/openai~1gpt-5.5/limit/context"
),
(
"input_tokens",
"/providers/hpc-ai/models/openai~1gpt-5.5/limit/input"
),
(
"lifecycle",
"/providers/hpc-ai/models/openai~1gpt-5.5/status"
),
]
);
assert_eq!(aggregator.facts.limits.context_tokens, Some(272_000));
assert_eq!(aggregator.facts.lifecycle, ModelLifecycle::Deprecated);
assert!(!aggregator.facts.input_modalities.contains(&Modality::Pdf));
assert!(
!aggregator
.facts
.capabilities
.contains(&ModelCapability::StructuredOutput)
);
assert_eq!(
aggregator.endpoint.api_base.as_deref(),
Some("https://api.hpc-ai.com/v1")
);
}
#[test]
fn the_two_spellings_of_a_long_context_tier_are_one_tier_when_the_rates_agree() {
fn tiers(cost: &str) -> Vec<(u64, ObservedRate)> {
let cost: WireCost = serde_json::from_str(cost).expect("a cost object");
price(Some(&cost), &JsonPointer::new(""))
.expect("a representable price")
.expect("a published price")
.tiers
.iter()
.map(|tier| match tier.threshold {
PriceTierThreshold::ContextOver { tokens } => (tokens, tier.rates.input),
})
.collect()
}
assert_eq!(
tiers(
r#"{"input": 5, "output": 30,
"tiers": [{"input": 10, "output": 45,
"tier": {"type": "context", "size": 272000}}],
"context_over_200k": {"input": 10, "output": 45}}"#
),
vec![(200_000, ObservedRate::from_nanos(10_000_000_000))],
"one schedule stated twice is one tier, from the threshold it already applied at"
);
assert_eq!(
tiers(
r#"{"input": 5, "output": 30,
"tiers": [{"input": 20, "output": 60,
"tier": {"type": "context", "size": 272000}}],
"context_over_200k": {"input": 10, "output": 45}}"#
),
vec![
(200_000, ObservedRate::from_nanos(10_000_000_000)),
(272_000, ObservedRate::from_nanos(20_000_000_000)),
],
"two thresholds charging differently are two tiers"
);
assert_eq!(
tiers(
r#"{"input": 5, "output": 30,
"tiers": [{"input": 20, "output": 60,
"tier": {"type": "context", "size": 250000}},
{"input": 10, "output": 45,
"tier": {"type": "context", "size": 272000}}],
"context_over_200k": {"input": 10, "output": 45}}"#
),
vec![
(200_000, ObservedRate::from_nanos(10_000_000_000)),
(250_000, ObservedRate::from_nanos(20_000_000_000)),
(272_000, ObservedRate::from_nanos(10_000_000_000)),
],
"a differently-priced tier in between means the matching one is a \
boundary of its own: lowering it to 200k would charge 20 above \
272k, where the payload says 10"
);
}
#[test]
fn published_decimals_become_exact_integer_rates() {
let snapshot = parse(IDENTITY).expect("fixture parses");
let id = ModelId::parse("openai/gpt-5.5").expect("id");
let entry = snapshot.content.model(&id).expect("model");
let price = entry
.offering(&ProviderId::parse("openai").expect("id"))
.expect("offering")
.price
.as_ref()
.expect("a published price");
assert_eq!(price.base.input, ObservedRate::from_nanos(5_000_000_000));
assert_eq!(price.base.output, ObservedRate::from_nanos(30_000_000_000));
assert_eq!(
price.base.cache_read,
Some(ObservedRate::from_nanos(500_000_000))
);
assert!(price.tiers.is_empty());
let tiered = entry
.offering(&ProviderId::parse("hpc-ai").expect("id"))
.expect("offering")
.price
.as_ref()
.expect("a published price");
assert_eq!(
tiered.tiers,
vec![PriceTier {
threshold: PriceTierThreshold::ContextOver { tokens: 272_000 },
rates: PriceRates {
input: ObservedRate::from_nanos(12_500_000_000),
output: ObservedRate::from_nanos(50_000_000_000),
..PriceRates::new(ObservedRate::ZERO, ObservedRate::ZERO)
},
}]
);
}
#[test]
fn decimal_conversion_is_exact_and_refuses_what_it_cannot_state() {
for (text, nanos) in [
("0", 0),
("10", 10_000_000_000),
("2.5", 2_500_000_000),
("0.075", 75_000_000),
("0.1", 100_000_000),
("0.26666667", 266_666_670),
("0.000000001", 1),
("1e-9", 1),
("1.5e1", 15_000_000_000),
("1E+2", 100_000_000_000),
] {
assert_eq!(
nano_dollars_per_million(text),
Ok(ObservedRate::from_nanos(nanos)),
"`{text}` converts exactly"
);
}
for (published, nanos) in [
("0.049999999999999996", 50_000_000),
("0.09999999999999999", 100_000_000),
("2.9000000000000004", 2_900_000_000),
("0.12500000000000003", 125_000_000),
] {
assert_eq!(
nano_dollars_per_million(published),
Ok(ObservedRate::from_nanos(nanos)),
"`{published}` is a float artifact of a representable rate"
);
}
for finer in ["0.0000000001", "0.0000000015", "0.1234567891"] {
assert_eq!(
nano_dollars_per_million(finer),
Err(PriceRejection::ExcessPrecision {
value: finer.to_owned()
}),
"`{finer}` is a rate finer than the gateway represents"
);
}
assert_eq!(
nano_dollars_per_million("-1"),
Err(PriceRejection::Negative {
value: "-1".to_owned()
})
);
assert_eq!(
nano_dollars_per_million("1e30"),
Err(PriceRejection::Overflow {
value: "1e30".to_owned()
})
);
for enormous in ["1e2147483647", "1e2147483648", "1E999999999999999999"] {
assert_eq!(
nano_dollars_per_million(enormous),
Err(PriceRejection::Overflow {
value: enormous.to_owned()
}),
"`{enormous}` states more dollars than a rate holds"
);
}
for minuscule in ["1.5e-2147483648", "1e-2147483649", "1e-999999999999999999"] {
assert_eq!(
nano_dollars_per_million(minuscule),
Err(PriceRejection::ExcessPrecision {
value: minuscule.to_owned()
}),
"`{minuscule}` states a rate below any nano-dollar"
);
}
for malformed in [
"", "\"10\"", "+1", "1.", ".5", "1.2.3", "abc", "null", "1e", "1e-",
] {
assert!(
matches!(
nano_dollars_per_million(malformed),
Err(PriceRejection::NotANumber { .. })
),
"`{malformed}` is not a JSON number"
);
}
}
#[test]
fn a_record_stating_no_modalities_or_limits_is_a_record_stating_none() {
let payload = r#"{
"models": {},
"providers": {
"openai": {
"id": "openai", "name": "OpenAI",
"models": { "gpt-4o": { "id": "gpt-4o", "name": "GPT-4o" } }
}
}
}"#;
let snapshot = parse(payload).expect("an unstated field is not changed meaning");
let offering = &snapshot.content.models()[0].offerings[0];
assert_eq!(offering.facts.limits, ModelLimits::default());
assert!(offering.facts.input_modalities.is_empty());
assert!(offering.facts.output_modalities.is_empty());
}
#[test]
fn every_drifted_payload_is_refused_with_a_pointer() {
type Expectation = (&'static str, fn(&ModelsDevError) -> bool);
let expectations: &[Expectation] = &[
("not-json", |error| {
matches!(error, ModelsDevError::NotJson { .. })
}),
("top-level-array", |error| {
matches!(error, ModelsDevError::Schema { .. })
}),
("missing-providers", |error| {
matches!(error, ModelsDevError::Schema { pointer: None, .. })
}),
("missing-model-name", |error| {
matches!(
error,
ModelsDevError::Schema { pointer: Some(pointer), .. }
if pointer.as_str() == "/providers/hpc-ai/models/openai~1gpt-5.5"
)
}),
("limit-type", |error| {
matches!(
error,
ModelsDevError::Schema { pointer: Some(pointer), .. }
if pointer.as_str()
== "/providers/hpc-ai/models/openai~1gpt-5.5/limit/context"
)
}),
("tier-without-size", |error| {
matches!(
error,
ModelsDevError::Schema { pointer: Some(pointer), .. }
if pointer.as_str()
== "/providers/hpc-ai/models/openai~1gpt-5.5/cost/tiers/0/tier"
)
}),
(
"unknown-status",
|error| matches!(error, ModelsDevError::UnknownStatus { status, .. } if status == "sunset"),
),
("unknown-modality", |error| {
matches!(
error,
ModelsDevError::UnknownModality { modality, .. } if modality == "telepathy"
)
}),
("price-precision", |error| {
matches!(
error,
ModelsDevError::Price {
reason: PriceRejection::ExcessPrecision { .. },
..
}
)
}),
("price-negative", |error| {
matches!(
error,
ModelsDevError::Price {
reason: PriceRejection::Negative { .. },
..
}
)
}),
("price-type", |error| {
matches!(
error,
ModelsDevError::Price {
reason: PriceRejection::NotANumber { .. },
..
}
)
}),
("price-tiers-without-base", |error| {
matches!(
error,
ModelsDevError::Price {
reason: PriceRejection::Partial {
stated: "tiered or optional rates",
..
},
..
}
)
}),
("price-partial", |error| {
matches!(
error,
ModelsDevError::Price {
reason: PriceRejection::Partial { .. },
..
}
)
}),
("tier-without-base", |error| {
matches!(
error,
ModelsDevError::Price {
reason: PriceRejection::Partial {
stated: "only optional rates",
missing: "input and output",
},
..
}
)
}),
(
"tier-type",
|error| matches!(error, ModelsDevError::UnknownTierType { kind, .. } if kind == "requests"),
),
("tier-duplicate", |error| {
matches!(error, ModelsDevError::DuplicateTier { .. })
}),
("model-id-mismatch", |error| {
matches!(error, ModelsDevError::IdMismatch { .. })
}),
("model-id-case", |error| {
matches!(error, ModelsDevError::IdMismatch { .. })
}),
("provider-id-mismatch", |error| {
matches!(error, ModelsDevError::IdMismatch { .. })
}),
("model-key-ambiguous", |error| {
matches!(
error,
ModelsDevError::AmbiguousModelKey { key, candidates, .. }
if key == "m-1" && candidates == &["alpha/m-1", "beta/m-1"]
)
}),
("neutral-price", |error| {
matches!(error, ModelsDevError::NeutralPrice { .. })
}),
("empty", |error| {
matches!(
error,
ModelsDevError::Content {
source: CatalogContentError::Empty
}
)
}),
("providers-empty", |error| {
matches!(
error,
ModelsDevError::Content {
source: CatalogContentError::Unoffered { .. }
}
)
}),
("control-character", |error| {
matches!(
error,
ModelsDevError::UncanonicalizableText { pointer, source }
if pointer.as_str() == "/providers/openai/models/openai~1gpt-5.5/name"
&& *source == CanonicalError::ControlCharacter { codepoint: 0x7 }
)
}),
];
for (name, expected) in expectations {
let error = parse(drift(name)).expect_err("a drifted payload is refused");
assert!(
expected(&error),
"`{name}` produced the wrong error: {error}"
);
if let ModelsDevError::Schema {
pointer: Some(pointer),
..
} = &error
{
assert_eq!(
error.refusal().pointer(),
Some(pointer),
"`{name}`'s refusal must name where it was decided"
);
let message = error.to_string();
assert!(
message.contains(pointer.as_str()),
"`{name}`'s message must read where it was decided: {message}"
);
assert!(
CatalogError::from(error.clone())
.to_string()
.contains(pointer.as_str()),
"`{name}` must keep that location on the way out of the module"
);
}
}
}
#[test]
fn a_payload_that_does_not_end_where_its_document_does_is_refused() {
let spliced = format!("{IDENTITY}{IDENTITY}");
assert!(
matches!(
parse(&spliced).expect_err("two documents are not one document"),
ModelsDevError::NotJson { .. }
),
"trailing content is malformed JSON, not a schema change"
);
assert!(
parse(&format!("{IDENTITY} \n")).is_ok(),
"trailing whitespace is not content"
);
}
#[test]
fn a_refused_payload_cannot_replace_last_known_good_state() {
let mut catalogue = LastKnownGoodCatalog::new();
let good = parse(IDENTITY).expect("fixture parses");
let content_id = good.source.content_id;
assert_eq!(catalogue.admit(good), Admission::Initial { content_id });
for name in [
"not-json",
"unknown-status",
"price-precision",
"empty",
"missing-providers",
"providers-empty",
"control-character",
"model-key-ambiguous",
] {
let (error, active) = catalogue
.admit_result(parse(drift(name)))
.expect_err("a drifted payload is refused");
assert!(!error.to_string().is_empty());
let active = active.expect("the refusal hands back what stayed active");
assert_eq!(
active.source.content_id, content_id,
"`{name}` must not disturb the active catalogue"
);
assert!(
active.source.fetched_at <= SystemTime::now(),
"`{name}`'s refusal must expose how old the catalogue it kept is, \
so a scheduler cannot report a refusal without its staleness"
);
}
assert_eq!(
catalogue
.active()
.map(|snapshot| snapshot.source.content_id),
Some(content_id)
);
}
#[test]
fn the_offline_seed_parses_deterministically() {
let first = seed_snapshot();
let second = seed_snapshot();
assert_eq!(first, second);
assert_eq!(first.source.content_id, second.source.content_id);
assert_eq!(first.source.fetched_at, seed_fetched_at());
assert_eq!(first.source.source_url, MODELS_DEV_CATALOG_URL);
assert_eq!(
first.source.schema_version,
SchemaVersion::MODELS_DEV_CATALOG_V1
);
assert_eq!(first.source.raw.size_bytes as usize, SEED_PAYLOAD.len());
let content = &first.content;
assert_eq!(content.providers().len(), 4);
assert!(content.offering_count() >= 5);
let deprecated = content
.offering(
&ModelId::parse("gpt-4o").expect("id"),
&ProviderId::parse("azure").expect("id"),
)
.expect("azure offers gpt-4o");
assert_eq!(deprecated.facts.lifecycle, ModelLifecycle::Deprecated);
let tiered = content
.offering(
&ModelId::parse("openai/gpt-5.5").expect("id"),
&ProviderId::parse("hpc-ai").expect("id"),
)
.expect("hpc-ai offers gpt-5.5");
assert_eq!(
tiered
.price
.as_ref()
.expect("a published price")
.tiers
.iter()
.map(|tier| tier.threshold)
.collect::<Vec<_>>(),
vec![PriceTierThreshold::ContextOver { tokens: 200_000 }]
);
}
#[test]
fn a_provider_local_key_files_under_the_model_it_offers() {
let content = seed_snapshot().content;
let id = ModelId::parse("openai/gpt-5.5").expect("id");
assert!(
content
.model(&ModelId::parse("gpt-5.5").expect("id"))
.is_none(),
"a provider-local key is not a model of its own"
);
let entry = content.model(&id).expect("the authored record");
assert!(entry.neutral.is_some());
let offering = content
.offering(&id, &ProviderId::parse("openai").expect("id"))
.expect("its author offers it");
assert_eq!(
offering.published_model_id, "gpt-5.5",
"a request to OpenAI must still use OpenAI's own id"
);
assert!(
offering.overrides.is_empty(),
"and it is compared against the neutral record it agrees with, \
rather than having none to compare against"
);
assert!(
entry
.offerings
.iter()
.any(|offering| offering.published_model_id == "openai/gpt-5.5"),
"an aggregator republishing the authored id joins the same entry"
);
}
#[test]
fn two_published_aliases_of_one_model_are_two_offerings_of_one_model() {
let content = parse(ALIASES).expect("fixture parses").content;
let authored = ModelId::parse("xiaomi/mimo-v2-flash").expect("id");
let provider = ProviderId::parse("qiniu-ai").expect("id");
assert!(
content
.model(&ModelId::parse("mimo-v2-flash").expect("id"))
.is_none(),
"an alias of a model is not a second model"
);
let entry = content.model(&authored).expect("the authored record");
assert!(entry.neutral.is_some());
assert_eq!(
entry
.offerings_by(&provider)
.map(|offering| offering.published_model_id.as_str())
.collect::<Vec<_>>(),
vec!["mimo-v2-flash", "xiaomi/mimo-v2-flash"],
"a request may use either published id, so neither is dropped"
);
assert_eq!(
content
.offering(&authored, &provider)
.map(|offering| offering.model.clone()),
Some(authored.clone()),
"and every one of them is an offering of the model it names"
);
assert_eq!(content.offering_count(), 2);
}
#[test]
fn an_object_valued_flag_states_the_capability_only_where_it_configures_it() {
let content = seed_snapshot().content;
let configured = content
.offering(
&ModelId::parse("openai/gpt-5.5").expect("id"),
&ProviderId::parse("hpc-ai").expect("id"),
)
.expect("hpc-ai offers gpt-5.5");
assert!(
configured
.facts
.capabilities
.contains(&ModelCapability::Interleaved)
);
let modes = content
.offering(
&ModelId::parse("openai/gpt-5.5").expect("id"),
&ProviderId::parse("openai").expect("id"),
)
.expect("openai offers gpt-5.5");
assert!(
!modes
.facts
.capabilities
.contains(&ModelCapability::Experimental),
"`experimental: {{ modes: … }}` describes modes, not the offering's status"
);
assert!(
!modes.overrides_field(ModelField::Capabilities),
"and so it is not an override of the neutral record either"
);
}
#[test]
fn the_seed_never_claims_the_upstream_document_it_was_cut_from() {
let snapshot = seed_snapshot();
let etag = snapshot
.source
.validators
.etag
.as_ref()
.expect("the seed identifies its own content");
assert_eq!(
etag.0,
format!("W/\"seed-{}\"", snapshot.content.content_id()),
"the tag is over the excerpt, so no upstream can match it"
);
assert!(
!etag.0.contains("38a27321531a976c916911889525f559"),
"and never the tag the fixture README records for the full document \
this excerpt was trimmed from"
);
assert_eq!(
snapshot.source.validators.last_modified, None,
"a date from the whole document would match conditionally too"
);
}
#[tokio::test]
async fn the_seed_source_serves_the_catalogue_without_a_network() {
let source = SeedCatalogSource;
let CatalogRefresh::Updated { snapshot, payload } =
source.refresh(None).await.expect("refresh")
else {
panic!("a first refresh transfers the seed");
};
assert_eq!(
payload.as_bytes(),
SEED_PAYLOAD.as_bytes(),
"the seed hands over the bytes it was parsed from, so a store retains the import"
);
assert_eq!(
source.refresh(Some(&snapshot.source.validators)).await,
Ok(CatalogRefresh::Unchanged {
validators: snapshot.source.validators.clone()
})
);
}
#[test]
fn a_price_only_upstream_edit_is_a_price_diff_and_nothing_else() {
let before = parse(IDENTITY).expect("fixture parses");
let repriced = IDENTITY.replace("\"input\": 5,", "\"input\": 4.25,");
let after = parse(&repriced).expect("the repriced fixture parses");
assert_ne!(after.source.content_id, before.source.content_id);
let diff = after.content.diff(&before.content);
assert!(diff.has_price_changes());
let counts = diff.counts();
assert_eq!(counts.prices_changed, 1);
assert_eq!(counts.metadata_changed, 0);
assert_eq!(counts.capabilities_changed, 0);
assert_eq!(counts.lifecycle_changed, 0);
assert!(matches!(
diff.changes(),
[CatalogChange::PriceChanged { to, .. }]
if to.as_ref().map(|price| price.base.input)
== Some(ObservedRate::from_nanos(4_250_000_000))
));
}
#[derive(Clone)]
struct Upstream {
etag: String,
payload: &'static str,
transfers: Arc<AtomicUsize>,
}
async fn serve(State(upstream): State<Upstream>, headers: HeaderMap) -> Response {
let matched = headers
.get(header::IF_NONE_MATCH)
.and_then(|value| value.to_str().ok())
== Some(upstream.etag.as_str());
if matched {
return (
StatusCode::NOT_MODIFIED,
[(header::ETAG, upstream.etag.clone())],
)
.into_response();
}
upstream.transfers.fetch_add(1, Ordering::Relaxed);
(
StatusCode::OK,
[
(header::ETAG, upstream.etag.clone()),
(
header::LAST_MODIFIED,
"Wed, 12 Aug 2026 20:27:54 GMT".to_owned(),
),
],
upstream.payload,
)
.into_response()
}
fn fetch() -> HttpCatalogFetch {
HttpCatalogFetch::new(Duration::from_secs(30)).expect("a client builds")
}
async fn upstream(payload: &'static str) -> (ModelsDevAdapter, Arc<AtomicUsize>) {
let transfers = Arc::new(AtomicUsize::new(0));
let router = axum::Router::new()
.route("/catalog.json", get(serve))
.with_state(Upstream {
etag: "\"identity-1\"".to_owned(),
payload,
transfers: Arc::clone(&transfers),
});
let listener = tokio::net::TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0)))
.await
.expect("a free port");
let address = listener.local_addr().expect("a bound address");
tokio::spawn(async move {
let _ = axum::serve(listener, router).await;
});
let adapter = ModelsDevAdapter::new(format!("http://{address}/catalog.json"))
.expect("a catalog.json URL");
(adapter, transfers)
}
#[tokio::test]
async fn a_conditional_refresh_transfers_nothing_when_the_upstream_is_unchanged() {
let (adapter, transfers) = upstream(IDENTITY).await;
let source = ModelsDevSource::new(adapter, fetch());
let mut catalogue = LastKnownGoodCatalog::new();
let CatalogRefresh::Updated { snapshot, .. } =
source.refresh(None).await.expect("first refresh")
else {
panic!("a first refresh has nothing to be conditional on");
};
assert_eq!(
snapshot.source.validators.etag,
Some(ETag("\"identity-1\"".to_owned()))
);
assert!(snapshot.source.validators.last_modified.is_some());
let content_id = snapshot.source.content_id;
catalogue.admit(*snapshot);
assert_eq!(transfers.load(Ordering::Relaxed), 1);
let refreshed = source
.refresh(catalogue.validators())
.await
.expect("second refresh");
assert!(matches!(refreshed, CatalogRefresh::Unchanged { .. }));
assert_eq!(
transfers.load(Ordering::Relaxed),
1,
"a 304 transfers no payload"
);
assert_eq!(
catalogue
.active()
.map(|snapshot| snapshot.source.content_id),
Some(content_id)
);
}
#[tokio::test]
async fn a_redirected_source_is_refused_rather_than_followed() {
let transfers = Arc::new(AtomicUsize::new(0));
let router = axum::Router::new()
.route(
"/moved/catalog.json",
get(|| async {
(StatusCode::FOUND, [(header::LOCATION, "/catalog.json")], "").into_response()
}),
)
.route("/catalog.json", get(serve))
.with_state(Upstream {
etag: "\"identity-1\"".to_owned(),
payload: IDENTITY,
transfers: Arc::clone(&transfers),
});
let listener = tokio::net::TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0)))
.await
.expect("a free port");
let address = listener.local_addr().expect("a bound address");
tokio::spawn(async move {
let _ = axum::serve(listener, router).await;
});
let adapter = ModelsDevAdapter::new(format!("http://{address}/moved/catalog.json"))
.expect("a catalog.json URL");
let source = ModelsDevSource::new(adapter, fetch());
let error = source.refresh(None).await.expect_err("a redirect");
assert_eq!(
transfers.load(Ordering::Relaxed),
0,
"the redirect target was never read"
);
let message = error.to_string();
assert!(
message.contains("302"),
"the refusal names the status it got, said: {message}"
);
assert!(
!error.retryable(),
"asking the same URL again is answered the same way"
);
}
#[tokio::test]
async fn an_upstream_outage_is_retryable_and_leaves_the_catalogue_alone() {
struct Offline;
#[async_trait]
impl CatalogFetch for Offline {
async fn get(
&self,
_url: &str,
_validators: Option<&SourceValidators>,
) -> Result<FetchResponse, FetchError> {
Err(FetchError::Transport {
message: "connection refused".to_owned(),
})
}
}
let source = ModelsDevSource::new(ModelsDevAdapter::default(), Offline);
let error = source.refresh(None).await.expect_err("an outage");
assert!(matches!(error, CatalogError::Unavailable { .. }));
assert_eq!(
CatalogError::from(FetchError::Status { status: 403 }),
CatalogError::Denied {
backend: BACKEND,
refusal: Refusal::new(RefusalReason::Denied),
message: "upstream answered HTTP 403".to_owned(),
}
);
assert_eq!(
error.refused_by().reason(),
RefusalReason::Unreachable,
"an outage is a transport refusal, not a denial"
);
}
#[test]
fn a_url_that_cannot_serve_a_catalogue_is_not_reported_as_an_outage() {
for status in [301, 302, 307, 308, 400, 404, 405, 410, 414, 451] {
let error = CatalogError::from(FetchError::Status { status });
assert_eq!(
error,
CatalogError::Misconfigured {
backend: BACKEND,
refusal: Refusal::new(RefusalReason::UnsupportedEndpoint),
message: format!("upstream answered HTTP {status}"),
},
"HTTP {status} says the configured URL is wrong"
);
assert!(!error.retryable(), "HTTP {status} cannot be retried away");
assert_eq!(error.category(), FailureCategory::NotFound);
assert_eq!(
error.refused_by().reason(),
RefusalReason::UnsupportedEndpoint,
"HTTP {status} is counted apart from an upstream that is down"
);
}
for status in [408, 429, 500, 502, 503, 504] {
let error = CatalogError::from(FetchError::Status { status });
assert!(
error.retryable(),
"HTTP {status} is the same request again, later"
);
}
for status in [401, 403] {
assert!(matches!(
CatalogError::from(FetchError::Status { status }),
CatalogError::Denied { .. }
));
}
let oversized = CatalogError::from(FetchError::TooLarge {
limit: MAX_PAYLOAD_BYTES,
});
assert!(matches!(oversized, CatalogError::Invalid { .. }));
assert!(!oversized.retryable());
assert_eq!(
oversized.refused_by().reason(),
RefusalReason::Oversized,
"a ceiling breach is counted apart from a malformed document"
);
}
#[test]
fn a_declared_length_reserves_no_more_than_a_declaration_is_worth() {
assert_eq!(declared_reserve(Some(4096), MAX_PAYLOAD_BYTES), 4096);
assert_eq!(declared_reserve(None, MAX_PAYLOAD_BYTES), 0);
assert_eq!(
declared_reserve(Some(MAX_PAYLOAD_BYTES as u64), MAX_PAYLOAD_BYTES),
DECLARED_RESERVE_BYTES,
"an honest ceiling-sized declaration still grows into its body"
);
assert_eq!(
declared_reserve(Some(u64::MAX), 512),
512,
"and a declaration past the ceiling cannot reserve past it either"
);
}
#[tokio::test]
async fn an_oversized_payload_is_refused_rather_than_held() {
let ceiling = IDENTITY.len() - 1;
let (adapter, transfers) = upstream(IDENTITY).await;
let source = ModelsDevSource::new(adapter, fetch().holding_at_most(ceiling));
let error = source
.refresh(None)
.await
.expect_err("an oversized payload");
assert_eq!(
error,
CatalogError::Invalid {
backend: BACKEND,
refusal: Refusal::new(RefusalReason::Oversized),
message: format!("payload exceeds the {ceiling}-byte ceiling"),
},
"a document that never fits is the configured source's shape, not an outage"
);
assert_eq!(
transfers.load(Ordering::Relaxed),
1,
"the body was served; the point is that it was not kept"
);
struct Unbounded;
#[async_trait]
impl CatalogFetch for Unbounded {
async fn get(
&self,
_url: &str,
_validators: Option<&SourceValidators>,
) -> Result<FetchResponse, FetchError> {
Ok(FetchResponse::Payload {
bytes: IDENTITY.as_bytes().to_vec(),
validators: SourceValidators::default(),
})
}
}
let source = ModelsDevSource::new(ModelsDevAdapter::default(), Unbounded)
.with_payload_limit(ceiling);
assert_eq!(
source.refresh(None).await.expect_err("too large to parse"),
CatalogError::Invalid {
backend: BACKEND,
refusal: Refusal::new(RefusalReason::Oversized),
message: format!("payload exceeds the {ceiling}-byte ceiling"),
}
);
}
}