use crate::ported::lint::markedjson::composer::ComposedNode;
use crate::ported::lint::markedjson::error::MarkedError;
use crate::ported::lint::markedjson::markedvalue::{gen_marked_value, MarkedAny};
use crate::ported::lint::markedjson::nodes::{MappingNode, Mark};
use serde_json::{Map, Value};
#[derive(Debug, Clone)]
pub struct ConstructorError(pub MarkedError);
impl std::fmt::Display for ConstructorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
impl std::error::Error for ConstructorError {}
pub fn marked(value: Value, mark: Mark) -> MarkedAny {
gen_marked_value(value, mark)
}
pub fn f<C>(construct: C, mark: Mark) -> MarkedAny
where
C: FnOnce() -> Value,
{
gen_marked_value(construct(), mark)
}
pub struct BaseConstructor {
pub yaml_constructors: std::collections::HashMap<String, ConstructorFn>,
pub constructed_objects: std::collections::HashMap<u64, MarkedAny>,
pub state_generators: Vec<()>,
pub deep_construct: bool,
}
pub type ConstructorFn = fn(&BaseConstructor, &ComposedNode) -> Result<MarkedAny, ConstructorError>;
impl Default for BaseConstructor {
fn default() -> Self {
Self::new()
}
}
impl BaseConstructor {
pub fn new() -> Self {
Self {
yaml_constructors: std::collections::HashMap::new(),
constructed_objects: std::collections::HashMap::new(),
state_generators: Vec::new(),
deep_construct: false,
}
}
pub fn check_data(has_node: bool) -> bool {
has_node
}
pub fn get_data(
&mut self,
node: Option<&ComposedNode>,
) -> Result<Option<MarkedAny>, ConstructorError> {
match node {
Some(n) => Ok(Some(self.construct_document(n)?)),
None => Ok(None),
}
}
pub fn get_single_data(
&mut self,
node: Option<&ComposedNode>,
) -> Result<Option<MarkedAny>, ConstructorError> {
match node {
Some(n) => Ok(Some(self.construct_document(n)?)),
None => Ok(None),
}
}
pub fn construct_document(
&mut self,
node: &ComposedNode,
) -> Result<MarkedAny, ConstructorError> {
let data = self.construct_object(node)?;
while !self.state_generators.is_empty() {
self.state_generators.clear();
}
self.constructed_objects.clear();
self.deep_construct = false;
Ok(data)
}
pub fn add_constructor(&mut self, tag: Option<&str>, constructor: ConstructorFn) {
let key = tag.unwrap_or("*").to_string();
self.yaml_constructors.insert(key, constructor);
}
pub fn construct_object(&self, node: &ComposedNode) -> Result<MarkedAny, ConstructorError> {
let tag = &node.node().tag;
if let Some(constructor) = self.yaml_constructors.get(tag) {
return constructor(self, node);
}
if let Some(constructor) = self.yaml_constructors.get("*") {
return constructor(self, node);
}
Err(ConstructorError(MarkedError::new(
None,
None,
Some(&format!("no constructor for tag {}", tag)),
None,
None,
)))
}
pub fn construct_scalar(node: &ComposedNode) -> Result<MarkedAny, ConstructorError> {
match node {
ComposedNode::Scalar(s) => {
let mark = s
.node
.start_mark
.clone()
.unwrap_or(Mark { line: 0, column: 0 });
Ok(marked(s.node.value.clone(), mark))
}
other => Err(ConstructorError(MarkedError::new(
None,
None,
Some(&format!(
"expected a scalar node, but found {}",
composed_id(other)
)),
None,
None,
))),
}
}
pub fn construct_sequence(
&self,
node: &ComposedNode,
) -> Result<Vec<MarkedAny>, ConstructorError> {
match node {
ComposedNode::Sequence(s) => {
let mut out = Vec::new();
if let Some(arr) = s.collection.node.value.as_array() {
for v in arr {
let mark = s
.collection
.node
.start_mark
.clone()
.unwrap_or(Mark { line: 0, column: 0 });
out.push(gen_marked_value(v.clone(), mark));
}
}
Ok(out)
}
other => Err(ConstructorError(MarkedError::new(
None,
None,
Some(&format!(
"expected a sequence node, but found {}",
composed_id(other)
)),
None,
None,
))),
}
}
pub fn construct_mapping(&self, node: &ComposedNode) -> Result<MarkedAny, ConstructorError> {
match node {
ComposedNode::Mapping(m) => {
let mut mapping = Map::new();
if let Some(obj) = m.collection.node.value.as_object() {
for (k, v) in obj {
if mapping.contains_key(k) {
continue;
}
mapping.insert(k.clone(), v.clone());
}
}
let mark = m
.collection
.node
.start_mark
.clone()
.unwrap_or(Mark { line: 0, column: 0 });
Ok(marked(Value::Object(mapping), mark))
}
other => Err(ConstructorError(MarkedError::new(
None,
None,
Some(&format!(
"expected a mapping node, but found {}",
composed_id(other)
)),
None,
None,
))),
}
}
}
pub struct Constructor {
pub base: BaseConstructor,
}
impl Default for Constructor {
fn default() -> Self {
Self::new()
}
}
impl Constructor {
pub fn new() -> Self {
let mut base = BaseConstructor::new();
base.add_constructor(Some("tag:yaml.org,2002:null"), construct_yaml_null);
base.add_constructor(Some("tag:yaml.org,2002:bool"), construct_yaml_bool);
base.add_constructor(Some("tag:yaml.org,2002:int"), construct_yaml_int);
base.add_constructor(Some("tag:yaml.org,2002:float"), construct_yaml_float);
base.add_constructor(Some("tag:yaml.org,2002:str"), construct_yaml_str);
base.add_constructor(Some("tag:yaml.org,2002:seq"), construct_yaml_seq);
base.add_constructor(Some("tag:yaml.org,2002:map"), construct_yaml_map);
base.add_constructor(None, construct_undefined);
Self { base }
}
pub fn flatten_mapping(&self, _node: &mut MappingNode) {
}
}
fn composed_id(node: &ComposedNode) -> &'static str {
match node {
ComposedNode::Scalar(_) => "scalar",
ComposedNode::Sequence(_) => "sequence",
ComposedNode::Mapping(_) => "mapping",
}
}
pub fn construct_yaml_null(
_c: &BaseConstructor,
node: &ComposedNode,
) -> Result<MarkedAny, ConstructorError> {
BaseConstructor::construct_scalar(node)?;
let mark = node.start_mark().unwrap_or(Mark { line: 0, column: 0 });
Ok(marked(Value::Null, mark))
}
pub fn construct_yaml_bool(
_c: &BaseConstructor,
node: &ComposedNode,
) -> Result<MarkedAny, ConstructorError> {
let mark = node.start_mark().unwrap_or(Mark { line: 0, column: 0 });
let raw = node.node().value.clone();
let b = match &raw {
Value::Bool(b) => *b,
Value::String(s) => !s.is_empty() && s != "false",
_ => false,
};
Ok(marked(Value::Bool(b), mark))
}
pub fn construct_yaml_int(
_c: &BaseConstructor,
node: &ComposedNode,
) -> Result<MarkedAny, ConstructorError> {
let mark = node.start_mark().unwrap_or(Mark { line: 0, column: 0 });
let raw = node.node().value.clone();
let parsed = match &raw {
Value::Number(n) => n.as_i64().unwrap_or(0),
Value::String(s) => {
let s = s.trim();
let (sign, body): (i64, &str) = if let Some(rest) = s.strip_prefix('-') {
(-1, rest)
} else if let Some(rest) = s.strip_prefix('+') {
(1, rest)
} else {
(1, s)
};
if body == "0" {
0
} else {
sign * body.parse::<i64>().unwrap_or(0)
}
}
_ => 0,
};
Ok(marked(Value::from(parsed), mark))
}
pub fn construct_yaml_float(
_c: &BaseConstructor,
node: &ComposedNode,
) -> Result<MarkedAny, ConstructorError> {
let mark = node.start_mark().unwrap_or(Mark { line: 0, column: 0 });
let raw = node.node().value.clone();
let parsed = match &raw {
Value::Number(n) => n.as_f64().unwrap_or(0.0),
Value::String(s) => {
let s = s.trim();
let (sign, body): (f64, &str) = if let Some(rest) = s.strip_prefix('-') {
(-1.0, rest)
} else if let Some(rest) = s.strip_prefix('+') {
(1.0, rest)
} else {
(1.0, s)
};
sign * body.parse::<f64>().unwrap_or(0.0)
}
_ => 0.0,
};
Ok(marked(
Value::Number(serde_json::Number::from_f64(parsed).unwrap_or_else(|| 0.into())),
mark,
))
}
pub fn construct_yaml_str(
_c: &BaseConstructor,
node: &ComposedNode,
) -> Result<MarkedAny, ConstructorError> {
BaseConstructor::construct_scalar(node)
}
pub fn construct_yaml_seq(
c: &BaseConstructor,
node: &ComposedNode,
) -> Result<MarkedAny, ConstructorError> {
let mark = node.start_mark().unwrap_or(Mark { line: 0, column: 0 });
let items = c.construct_sequence(node)?;
let arr: Vec<Value> = items.iter().map(marked_any_to_json).collect();
Ok(marked(Value::Array(arr), mark))
}
pub fn construct_yaml_map(
c: &BaseConstructor,
node: &ComposedNode,
) -> Result<MarkedAny, ConstructorError> {
c.construct_mapping(node)
}
pub fn construct_undefined(
_c: &BaseConstructor,
node: &ComposedNode,
) -> Result<MarkedAny, ConstructorError> {
Err(ConstructorError(MarkedError::new(
None,
None,
Some(&format!(
"could not determine a constructor for the tag {}",
node.node().tag
)),
None,
None,
)))
}
fn marked_any_to_json(m: &MarkedAny) -> Value {
match m {
MarkedAny::Unicode(u) => Value::String(u.value.clone()),
MarkedAny::Int(i) => Value::from(i.value),
MarkedAny::Float(f) => {
Value::Number(serde_json::Number::from_f64(f.value).unwrap_or_else(|| 0.into()))
}
MarkedAny::Dict(d) => Value::Object(d.value.clone()),
MarkedAny::List(l) => Value::Array(l.value.clone()),
MarkedAny::Other(v) => v.value.clone(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::lint::markedjson::nodes;
use crate::ported::lint::markedjson::nodes::{MappingNode, ScalarNode, SequenceNode};
use serde_json::json;
fn mk_scalar(tag: &str, value: Value) -> ComposedNode {
ComposedNode::Scalar(ScalarNode::new(
tag,
value,
Some(Mark { line: 0, column: 0 }),
None,
None,
))
}
fn mk_seq(tag: &str, arr: Value) -> ComposedNode {
ComposedNode::Sequence(SequenceNode::new(
tag,
arr,
Some(Mark { line: 0, column: 0 }),
None,
None,
))
}
fn mk_map(tag: &str, obj: Value) -> ComposedNode {
ComposedNode::Mapping(MappingNode::new(
tag,
obj,
Some(Mark { line: 0, column: 0 }),
None,
None,
))
}
#[test]
fn constructor_error_implements_error_traits() {
let me = MarkedError::new(Some("ctx"), None, Some("prob"), None, None);
let e = ConstructorError(me);
let _: &dyn std::error::Error = &e;
assert!(e.to_string().contains("ctx"));
}
#[test]
fn marked_wraps_value_with_mark() {
let mark = Mark { line: 3, column: 4 };
let m = marked(json!("hi"), mark);
assert!(matches!(m, MarkedAny::Unicode(_)));
}
#[test]
fn add_constructor_registers_tag() {
let mut b = BaseConstructor::new();
b.add_constructor(Some("custom"), construct_yaml_str);
assert!(b.yaml_constructors.contains_key("custom"));
}
#[test]
fn add_constructor_none_maps_to_catchall_key() {
let mut b = BaseConstructor::new();
b.add_constructor(None, construct_undefined);
assert!(b.yaml_constructors.contains_key("*"));
}
#[test]
fn construct_object_unknown_tag_returns_error() {
let b = BaseConstructor::new();
let n = mk_scalar("unknown-tag", json!("x"));
let r = b.construct_object(&n);
assert!(r.is_err());
assert!(r.unwrap_err().to_string().contains("no constructor"));
}
#[test]
fn construct_object_catchall_fires_for_unknown_tag() {
let mut b = BaseConstructor::new();
b.add_constructor(None, construct_undefined);
let n = mk_scalar("never-registered-tag", json!("x"));
let r = b.construct_object(&n);
assert!(r.is_err());
assert!(r
.unwrap_err()
.to_string()
.contains("could not determine a constructor"));
}
#[test]
fn construct_scalar_returns_marked_unicode() {
let n = mk_scalar("tag:yaml.org,2002:str", json!("hello"));
let r = BaseConstructor::construct_scalar(&n).unwrap();
match r {
MarkedAny::Unicode(u) => assert_eq!(u.value, "hello"),
_ => panic!("expected Unicode"),
}
}
#[test]
fn construct_scalar_rejects_sequence_node() {
let n = mk_seq("tag:yaml.org,2002:seq", json!([1]));
let r = BaseConstructor::construct_scalar(&n);
assert!(r.is_err());
assert!(r.unwrap_err().to_string().contains("scalar node"));
}
#[test]
fn construct_sequence_returns_vec_of_marked_values() {
let b = BaseConstructor::new();
let n = mk_seq("tag:yaml.org,2002:seq", json!([1, 2, 3]));
let r = b.construct_sequence(&n).unwrap();
assert_eq!(r.len(), 3);
}
#[test]
fn construct_sequence_rejects_scalar_node() {
let b = BaseConstructor::new();
let n = mk_scalar("tag:yaml.org,2002:str", json!("x"));
let r = b.construct_sequence(&n);
assert!(r.is_err());
}
#[test]
fn construct_mapping_returns_marked_dict() {
let b = BaseConstructor::new();
let n = mk_map("tag:yaml.org,2002:map", json!({"a": 1, "b": 2}));
let r = b.construct_mapping(&n).unwrap();
match r {
MarkedAny::Dict(d) => {
assert_eq!(d.value.get("a"), Some(&json!(1)));
assert_eq!(d.value.get("b"), Some(&json!(2)));
}
_ => panic!("expected Dict"),
}
}
#[test]
fn construct_yaml_null_returns_null() {
let c = Constructor::new();
let n = mk_scalar("tag:yaml.org,2002:null", json!("null"));
let r = construct_yaml_null(&c.base, &n).unwrap();
match r {
MarkedAny::Other(o) => assert_eq!(o.value, Value::Null),
_ => panic!("expected Null"),
}
}
#[test]
fn construct_yaml_bool_parses_true_string() {
let c = Constructor::new();
let n = mk_scalar("tag:yaml.org,2002:bool", json!("true"));
let r = construct_yaml_bool(&c.base, &n).unwrap();
match r {
MarkedAny::Other(o) => assert_eq!(o.value, Value::Bool(true)),
_ => panic!("expected Bool"),
}
}
#[test]
fn construct_yaml_bool_parses_false_string() {
let c = Constructor::new();
let n = mk_scalar("tag:yaml.org,2002:bool", json!("false"));
let r = construct_yaml_bool(&c.base, &n).unwrap();
match r {
MarkedAny::Other(o) => assert_eq!(o.value, Value::Bool(false)),
_ => panic!("expected Bool"),
}
}
#[test]
fn construct_yaml_int_positive() {
let c = Constructor::new();
let n = mk_scalar("tag:yaml.org,2002:int", json!("42"));
let r = construct_yaml_int(&c.base, &n).unwrap();
match r {
MarkedAny::Int(i) => assert_eq!(i.value, 42),
_ => panic!("expected Int"),
}
}
#[test]
fn construct_yaml_int_negative() {
let c = Constructor::new();
let n = mk_scalar("tag:yaml.org,2002:int", json!("-7"));
let r = construct_yaml_int(&c.base, &n).unwrap();
match r {
MarkedAny::Int(i) => assert_eq!(i.value, -7),
_ => panic!("expected Int"),
}
}
#[test]
fn construct_yaml_int_zero() {
let c = Constructor::new();
let n = mk_scalar("tag:yaml.org,2002:int", json!("0"));
let r = construct_yaml_int(&c.base, &n).unwrap();
match r {
MarkedAny::Int(i) => assert_eq!(i.value, 0),
_ => panic!("expected Int"),
}
}
#[test]
fn construct_yaml_float_parses_decimal() {
let c = Constructor::new();
let n = mk_scalar("tag:yaml.org,2002:float", json!("3.5"));
let r = construct_yaml_float(&c.base, &n).unwrap();
match r {
MarkedAny::Float(f) => assert!((f.value - 3.5).abs() < 1e-9),
_ => panic!("expected Float"),
}
}
#[test]
fn construct_yaml_str_returns_string() {
let c = Constructor::new();
let n = mk_scalar("tag:yaml.org,2002:str", json!("hello"));
let r = construct_yaml_str(&c.base, &n).unwrap();
match r {
MarkedAny::Unicode(u) => assert_eq!(u.value, "hello"),
_ => panic!("expected Unicode"),
}
}
#[test]
fn construct_yaml_seq_builds_marked_list() {
let c = Constructor::new();
let n = mk_seq("tag:yaml.org,2002:seq", json!([1, 2, 3]));
let r = construct_yaml_seq(&c.base, &n).unwrap();
match r {
MarkedAny::List(l) => {
assert_eq!(l.value.len(), 3);
assert_eq!(l.value[0], json!(1));
}
_ => panic!("expected List"),
}
}
#[test]
fn construct_yaml_map_builds_marked_dict() {
let c = Constructor::new();
let n = mk_map("tag:yaml.org,2002:map", json!({"k": "v"}));
let r = construct_yaml_map(&c.base, &n).unwrap();
match r {
MarkedAny::Dict(d) => assert_eq!(d.value.get("k"), Some(&json!("v"))),
_ => panic!("expected Dict"),
}
}
#[test]
fn construct_undefined_always_errors() {
let c = Constructor::new();
let n = mk_scalar("custom:tag", json!("x"));
let r = construct_undefined(&c.base, &n);
assert!(r.is_err());
assert!(r
.unwrap_err()
.to_string()
.contains("could not determine a constructor"));
}
#[test]
fn constructor_new_registers_all_eight_tags() {
let c = Constructor::new();
for tag in [
"tag:yaml.org,2002:null",
"tag:yaml.org,2002:bool",
"tag:yaml.org,2002:int",
"tag:yaml.org,2002:float",
"tag:yaml.org,2002:str",
"tag:yaml.org,2002:seq",
"tag:yaml.org,2002:map",
"*", ] {
assert!(
c.base.yaml_constructors.contains_key(tag),
"missing tag: {}",
tag
);
}
}
#[test]
fn construct_object_dispatches_int_constructor() {
let c = Constructor::new();
let n = mk_scalar("tag:yaml.org,2002:int", json!("42"));
let r = c.base.construct_object(&n).unwrap();
match r {
MarkedAny::Int(i) => assert_eq!(i.value, 42),
_ => panic!("expected Int"),
}
}
#[test]
fn base_constructor_new_initialises_empty_state() {
let c = BaseConstructor::new();
assert!(c.constructed_objects.is_empty());
assert!(c.state_generators.is_empty());
assert!(!c.deep_construct);
}
#[test]
fn check_data_returns_input() {
assert!(BaseConstructor::check_data(true));
assert!(!BaseConstructor::check_data(false));
}
#[test]
fn get_data_returns_none_when_no_node() {
let mut c = Constructor::new();
let r = c.base.get_data(None).unwrap();
assert!(r.is_none());
}
#[test]
fn get_single_data_returns_none_when_no_node() {
let mut c = Constructor::new();
let r = c.base.get_single_data(None).unwrap();
assert!(r.is_none());
}
#[test]
fn construct_document_resets_constructed_objects_after_construction() {
use crate::ported::lint::markedjson::markedvalue::MarkedInt;
let mut c = Constructor::new();
c.base.constructed_objects.insert(
42,
MarkedAny::Int(MarkedInt {
value: 0,
mark: Mark { line: 0, column: 0 },
}),
);
let scalar = nodes::ScalarNode {
node: nodes::Node {
tag: "tag:yaml.org,2002:int".to_string(),
value: Value::from(7),
start_mark: Some(Mark { line: 1, column: 0 }),
end_mark: None,
},
style: None,
};
let composed = ComposedNode::Scalar(scalar);
let r = c.base.construct_document(&composed).unwrap();
match r {
MarkedAny::Int(i) => assert_eq!(i.value, 7),
other => panic!("expected Int, got {:?}", other),
}
assert!(c.base.constructed_objects.is_empty());
}
#[test]
fn construct_document_resets_deep_construct_after_construction() {
let mut c = Constructor::new();
c.base.deep_construct = true;
let scalar = nodes::ScalarNode {
node: nodes::Node {
tag: "tag:yaml.org,2002:bool".to_string(),
value: Value::from(true),
start_mark: Some(Mark { line: 1, column: 0 }),
end_mark: None,
},
style: None,
};
let composed = ComposedNode::Scalar(scalar);
let _ = c.base.construct_document(&composed).unwrap();
assert!(!c.base.deep_construct);
}
#[test]
fn get_data_with_node_returns_constructed_value() {
let mut c = Constructor::new();
let scalar = nodes::ScalarNode {
node: nodes::Node {
tag: "tag:yaml.org,2002:int".to_string(),
value: Value::from(123),
start_mark: Some(Mark { line: 1, column: 0 }),
end_mark: None,
},
style: None,
};
let composed = ComposedNode::Scalar(scalar);
let r = c.base.get_data(Some(&composed)).unwrap().unwrap();
match r {
MarkedAny::Int(i) => assert_eq!(i.value, 123),
_ => panic!("expected Int"),
}
}
#[test]
fn get_single_data_with_node_returns_constructed_value() {
let mut c = Constructor::new();
let scalar = nodes::ScalarNode {
node: nodes::Node {
tag: "tag:yaml.org,2002:str".to_string(),
value: Value::from("hello"),
start_mark: Some(Mark { line: 1, column: 0 }),
end_mark: None,
},
style: None,
};
let composed = ComposedNode::Scalar(scalar);
let r = c.base.get_single_data(Some(&composed)).unwrap().unwrap();
match r {
MarkedAny::Unicode(u) => assert_eq!(u.value, "hello"),
other => panic!("expected Unicode, got {:?}", other),
}
}
#[test]
fn f_closure_wraps_construct_result_with_mark() {
let m = Mark { line: 5, column: 7 };
let result = f(|| Value::String("hello".to_string()), m);
match result {
MarkedAny::Unicode(u) => {
assert_eq!(u.value, "hello");
assert_eq!(u.mark.line, 5);
assert_eq!(u.mark.column, 7);
}
other => panic!("expected Unicode, got {:?}", other),
}
}
}