use crate::{AdapterManifest, RegisteredAdapter, lookup_manifest};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RouteError {
SchemaVersionMismatch { expected: String, found: String },
EmptySentinel { field: &'static str },
UnknownEventName { received: String },
UnknownEnumName {
field: &'static str,
received: String,
},
InvalidFrameContext { detail: String },
InvalidPayloadRef { index: usize, detail: String },
InvalidEventEnvelope { detail: String },
AdapterIdNotFound { adapter_id: String },
AdapterVersionMismatch {
adapter_id: String,
requested: String,
registered: String,
},
}
impl std::fmt::Display for RouteError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::SchemaVersionMismatch { expected, found } => write!(
f,
"schema_version mismatch: expected `{expected}`, found `{found}`"
),
Self::EmptySentinel { field } => {
write!(f, "empty sentinel string in field `{field}`")
}
Self::UnknownEventName { received } => {
write!(f, "unknown lifecycle event wire name `{received}`")
}
Self::UnknownEnumName { field, received } => {
write!(f, "unknown enum wire name `{received}` for field `{field}`")
}
Self::InvalidFrameContext { detail } => {
write!(f, "invalid frame_context: {detail}")
}
Self::InvalidPayloadRef { index, detail } => {
write!(f, "invalid payload_refs[{index}]: {detail}")
}
Self::InvalidEventEnvelope { detail } => {
write!(f, "invalid event envelope: {detail}")
}
Self::AdapterIdNotFound { adapter_id } => {
write!(f, "no registered adapter with id `{adapter_id}`")
}
Self::AdapterVersionMismatch {
adapter_id,
requested,
registered,
} => write!(
f,
"adapter `{adapter_id}` is registered at version `{registered}`, \
request asked for `{requested}`"
),
}
}
}
impl std::error::Error for RouteError {}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AdapterResolution {
Found(RegisteredAdapter),
UnknownId,
VersionMismatch { registered_version: String },
}
pub trait AdapterRegistry {
fn resolve(&self, adapter_id: &str, adapter_version: &str) -> AdapterResolution;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct BuiltinAdapterRegistry;
impl AdapterRegistry for BuiltinAdapterRegistry {
fn resolve(&self, adapter_id: &str, adapter_version: &str) -> AdapterResolution {
match lookup_manifest(adapter_id) {
None => AdapterResolution::UnknownId,
Some(entry) => {
if entry.manifest.adapter_version == adapter_version {
AdapterResolution::Found(entry)
} else {
AdapterResolution::VersionMismatch {
registered_version: entry.manifest.adapter_version.clone(),
}
}
}
}
}
}
pub(crate) fn manifest_of(resolution: &AdapterResolution) -> Option<&AdapterManifest> {
match resolution {
AdapterResolution::Found(entry) => Some(&entry.manifest),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builtin_registry_resolves_codex_at_known_version() {
let reg = BuiltinAdapterRegistry;
let r = reg.resolve("codex", "0.1.0");
assert!(matches!(r, AdapterResolution::Found(_)));
}
#[test]
fn builtin_registry_distinguishes_unknown_id_from_version_mismatch() {
let reg = BuiltinAdapterRegistry;
assert_eq!(
reg.resolve("nonexistent", "0.1.0"),
AdapterResolution::UnknownId
);
match reg.resolve("codex", "9.9.9") {
AdapterResolution::VersionMismatch { registered_version } => {
assert_eq!(registered_version, "0.1.0");
}
other => panic!("expected VersionMismatch, got {other:?}"),
}
}
}