use serde_json::Value;
use crate::draft::Draft;
use super::resource::ResourceRef;
#[derive(Debug, Clone, Copy)]
pub enum Anchor<'a> {
Default {
name: &'a str,
resource: ResourceRef<'a>,
},
Dynamic {
name: &'a str,
resource: ResourceRef<'a>,
},
}
impl<'a> Anchor<'a> {
#[must_use]
pub fn name(&self) -> &'a str {
match self {
Anchor::Default { name, .. } | Anchor::Dynamic { name, .. } => name,
}
}
#[must_use]
pub fn resource(&self) -> ResourceRef<'a> {
match self {
Anchor::Default { resource, .. } | Anchor::Dynamic { resource, .. } => *resource,
}
}
}
pub enum AnchorIter<'a> {
Empty,
One(Anchor<'a>),
Two(Anchor<'a>, Anchor<'a>),
}
impl<'a> Iterator for AnchorIter<'a> {
type Item = Anchor<'a>;
fn next(&mut self) -> Option<Self::Item> {
match core::mem::replace(self, AnchorIter::Empty) {
AnchorIter::Empty => None,
AnchorIter::One(anchor) => Some(anchor),
AnchorIter::Two(first, second) => {
*self = AnchorIter::One(second);
Some(first)
}
}
}
}
pub fn anchors_202012(draft: Draft, contents: &Value) -> AnchorIter<'_> {
let Some(schema) = contents.as_object() else {
return AnchorIter::Empty;
};
let default_anchor =
schema
.get("$anchor")
.and_then(Value::as_str)
.map(|name| Anchor::Default {
name,
resource: ResourceRef::new(contents, draft),
});
let dynamic_anchor = schema
.get("$dynamicAnchor")
.and_then(Value::as_str)
.map(|name| Anchor::Dynamic {
name,
resource: ResourceRef::new(contents, draft),
});
match (default_anchor, dynamic_anchor) {
(Some(d), Some(dyn_a)) => AnchorIter::Two(d, dyn_a),
(Some(d), None) => AnchorIter::One(d),
(None, Some(dyn_a)) => AnchorIter::One(dyn_a),
(None, None) => AnchorIter::Empty,
}
}
pub fn anchors_201909(draft: Draft, contents: &Value) -> AnchorIter<'_> {
match contents
.as_object()
.and_then(|schema| schema.get("$anchor"))
.and_then(Value::as_str)
{
Some(name) => AnchorIter::One(Anchor::Default {
name,
resource: ResourceRef::new(contents, draft),
}),
None => AnchorIter::Empty,
}
}
pub fn legacy_anchor_in_dollar_id(draft: Draft, contents: &Value) -> AnchorIter<'_> {
match contents
.as_object()
.and_then(|schema| schema.get("$id"))
.and_then(Value::as_str)
.and_then(|id| id.strip_prefix('#'))
{
Some(id) => AnchorIter::One(Anchor::Default {
name: id,
resource: ResourceRef::new(contents, draft),
}),
None => AnchorIter::Empty,
}
}
pub fn legacy_anchor_in_id(draft: Draft, contents: &Value) -> AnchorIter<'_> {
match contents
.as_object()
.and_then(|schema| schema.get("id"))
.and_then(Value::as_str)
.and_then(|id| id.strip_prefix('#'))
{
Some(id) => AnchorIter::One(Anchor::Default {
name: id,
resource: ResourceRef::new(contents, draft),
}),
None => AnchorIter::Empty,
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn anchor_202012_both() {
let schema = json!({"$anchor": "foo", "$dynamicAnchor": "bar"});
let mut iter = anchors_202012(Draft::Draft202012, &schema);
let first = iter.next().unwrap();
assert_eq!(first.name(), "foo");
assert!(matches!(first, Anchor::Default { .. }));
let second = iter.next().unwrap();
assert_eq!(second.name(), "bar");
assert!(matches!(second, Anchor::Dynamic { .. }));
assert!(iter.next().is_none());
}
#[test]
fn anchor_202012_only_dynamic() {
let schema = json!({"$dynamicAnchor": "dyn"});
let mut iter = anchors_202012(Draft::Draft202012, &schema);
let a = iter.next().unwrap();
assert_eq!(a.name(), "dyn");
assert!(matches!(a, Anchor::Dynamic { .. }));
assert!(iter.next().is_none());
}
#[test]
fn anchor_202012_none() {
let schema = json!({"type": "string"});
let mut iter = anchors_202012(Draft::Draft202012, &schema);
assert!(iter.next().is_none());
}
#[test]
fn anchor_201909() {
let schema = json!({"$anchor": "myAnchor"});
let mut iter = anchors_201909(Draft::Draft201909, &schema);
let a = iter.next().unwrap();
assert_eq!(a.name(), "myAnchor");
assert!(iter.next().is_none());
}
#[test]
fn legacy_anchor_dollar_id() {
let schema = json!({"$id": "#legacyAnchor"});
let mut iter = legacy_anchor_in_dollar_id(Draft::Draft7, &schema);
let a = iter.next().unwrap();
assert_eq!(a.name(), "legacyAnchor");
assert!(iter.next().is_none());
}
#[test]
fn legacy_anchor_id() {
let schema = json!({"id": "#d4anchor"});
let mut iter = legacy_anchor_in_id(Draft::Draft4, &schema);
let a = iter.next().unwrap();
assert_eq!(a.name(), "d4anchor");
assert!(iter.next().is_none());
}
#[test]
fn non_object_returns_empty() {
let schema = json!(true);
assert!(anchors_202012(Draft::Draft202012, &schema).next().is_none());
}
}