#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum RefOutcome {
Component(String),
External,
UnsupportedLocal,
Dangling(String),
}
#[derive(Debug, Default, Clone)]
pub(crate) struct RefIndex {
schemas: indexmap::IndexSet<String>,
}
impl RefIndex {
pub fn register(&mut self, id: impl Into<String>) {
self.schemas.insert(id.into());
}
pub fn contains(&self, id: &str) -> bool {
self.schemas.contains(id)
}
}
pub(crate) fn resolve(index: &RefIndex, raw: &str) -> RefOutcome {
let Some(fragment) = raw.strip_prefix('#') else {
return RefOutcome::External;
};
let path = fragment.strip_prefix('/').unwrap_or(fragment);
if let Some(name) = path.strip_prefix("components/schemas/") {
let decoded = decode_pointer_token(name);
let id = crate::sanitize::ident(&decoded);
return if index.contains(&id) {
RefOutcome::Component(id)
} else {
RefOutcome::Dangling(id)
};
}
if !path.is_empty() && !path.contains('/') {
let decoded = decode_pointer_token(path);
let id = crate::sanitize::ident(&decoded);
if index.contains(&id) {
return RefOutcome::Component(id);
}
}
RefOutcome::UnsupportedLocal
}
fn decode_pointer_token(s: &str) -> String {
s.replace("~1", "/").replace("~0", "~")
}
#[cfg(test)]
mod tests {
use super::*;
fn idx_with(names: &[&str]) -> RefIndex {
let mut i = RefIndex::default();
for n in names {
i.register(*n);
}
i
}
#[test]
fn component_ref() {
let idx = idx_with(&["Pet"]);
assert_eq!(
resolve(&idx, "#/components/schemas/Pet"),
RefOutcome::Component("Pet".into())
);
}
#[test]
fn dangling_ref() {
let idx = idx_with(&["Pet"]);
assert_eq!(
resolve(&idx, "#/components/schemas/Missing"),
RefOutcome::Dangling("Missing".into())
);
}
#[test]
fn external_ref() {
let idx = RefIndex::default();
assert_eq!(
resolve(&idx, "other.json#/components/schemas/Pet"),
RefOutcome::External
);
}
#[test]
fn unsupported_local_ref() {
let idx = RefIndex::default();
assert_eq!(
resolve(&idx, "#/components/parameters/Foo"),
RefOutcome::UnsupportedLocal
);
}
#[test]
fn pointer_decoding() {
let idx = idx_with(&["a_b_c"]); assert_eq!(
resolve(&idx, "#/components/schemas/a~1b~0c"),
RefOutcome::Component("a_b_c".into())
);
}
}