use serde_json::Value;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Mark {
pub line: usize,
pub column: usize,
}
#[derive(Debug, Clone)]
pub struct Node {
pub tag: String,
pub value: Value,
pub start_mark: Option<Mark>,
pub end_mark: Option<Mark>,
}
impl Node {
pub fn new(
tag: impl Into<String>,
value: Value,
start_mark: Option<Mark>,
end_mark: Option<Mark>,
) -> Self {
Self {
tag: tag.into(),
value,
start_mark,
end_mark,
}
}
}
impl std::fmt::Display for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Node(tag={:?}, value={:?})", self.tag, self.value)
}
}
#[derive(Debug, Clone)]
pub struct ScalarNode {
pub node: Node,
pub style: Option<char>,
}
impl ScalarNode {
pub const ID: &'static str = "scalar";
pub fn new(
tag: impl Into<String>,
value: Value,
start_mark: Option<Mark>,
end_mark: Option<Mark>,
style: Option<char>,
) -> Self {
Self {
node: Node::new(tag, value, start_mark, end_mark),
style,
}
}
}
#[derive(Debug, Clone)]
pub struct CollectionNode {
pub node: Node,
pub flow_style: Option<bool>,
}
impl CollectionNode {
pub fn new(
tag: impl Into<String>,
value: Value,
start_mark: Option<Mark>,
end_mark: Option<Mark>,
flow_style: Option<bool>,
) -> Self {
Self {
node: Node::new(tag, value, start_mark, end_mark),
flow_style,
}
}
}
#[derive(Debug, Clone)]
pub struct SequenceNode {
pub collection: CollectionNode,
}
impl SequenceNode {
pub const ID: &'static str = "sequence";
pub fn new(
tag: impl Into<String>,
value: Value,
start_mark: Option<Mark>,
end_mark: Option<Mark>,
flow_style: Option<bool>,
) -> Self {
Self {
collection: CollectionNode::new(tag, value, start_mark, end_mark, flow_style),
}
}
}
#[derive(Debug, Clone)]
pub struct MappingNode {
pub collection: CollectionNode,
}
impl MappingNode {
pub const ID: &'static str = "mapping";
pub fn new(
tag: impl Into<String>,
value: Value,
start_mark: Option<Mark>,
end_mark: Option<Mark>,
flow_style: Option<bool>,
) -> Self {
Self {
collection: CollectionNode::new(tag, value, start_mark, end_mark, flow_style),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn node_carries_tag_value_and_marks() {
let start = Mark { line: 1, column: 0 };
let end = Mark { line: 1, column: 5 };
let n = Node::new(
"!str",
json!("hello"),
Some(start.clone()),
Some(end.clone()),
);
assert_eq!(n.tag, "!str");
assert_eq!(n.value, "hello");
assert_eq!(n.start_mark, Some(start));
assert_eq!(n.end_mark, Some(end));
}
#[test]
fn scalar_node_id_matches_upstream() {
assert_eq!(ScalarNode::ID, "scalar");
}
#[test]
fn scalar_node_carries_style() {
let s = ScalarNode::new("!str", json!("x"), None, None, Some('"'));
assert_eq!(s.style, Some('"'));
}
#[test]
fn sequence_node_id_matches_upstream() {
assert_eq!(SequenceNode::ID, "sequence");
}
#[test]
fn mapping_node_id_matches_upstream() {
assert_eq!(MappingNode::ID, "mapping");
}
#[test]
fn collection_node_carries_flow_style() {
let c = CollectionNode::new("!seq", json!([]), None, None, Some(true));
assert_eq!(c.flow_style, Some(true));
}
#[test]
fn node_display_matches_upstream_repr_shape() {
let n = Node::new("!str", json!("hello"), None, None);
let s = format!("{}", n);
assert!(s.starts_with("Node(tag="));
assert!(s.contains("value="));
}
}