#[cfg(feature = "base64")]
mod binary;
mod decimal;
mod quoting;
mod render;
mod resolve;
pub(crate) use decimal::{digits_to_decimal, places_to_decimal, signed_digits_to_decimal};
use crate::as_yaml::FlowQuoting;
use std::fmt;
#[cfg(feature = "base64")]
use binary::base64_decode;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScalarStyle {
Plain,
SingleQuoted,
DoubleQuoted,
Literal,
Folded,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CoreScalarType {
Null,
Boolean,
Integer,
Float,
String,
}
impl From<CoreScalarType> for ScalarType {
fn from(t: CoreScalarType) -> Self {
match t {
CoreScalarType::Null => ScalarType::Null,
CoreScalarType::Boolean => ScalarType::Boolean,
CoreScalarType::Integer => ScalarType::Integer,
CoreScalarType::Float => ScalarType::Float,
CoreScalarType::String => ScalarType::String,
}
}
}
pub(crate) const INDICATORS: &[char] = &[
'-', '?', ':', ',', '[', ']', '{', '}', '#', '&', '*', '!', '|', '>', '\'', '"', '%', '@', '`',
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ScalarType {
String,
Integer,
Float,
Boolean,
Null,
#[cfg(feature = "base64")]
Binary,
Timestamp,
Regex,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScalarValue {
value: String,
style: ScalarStyle,
scalar_type: ScalarType,
tag: Option<String>,
}
pub(crate) fn trim_white(s: &str) -> &str {
s.trim_matches([' ', '\t'])
}
impl ScalarValue {
pub fn string(value: impl Into<String>) -> Self {
let value = value.into();
let style = Self::detect_style(&value);
let scalar_type = ScalarType::String;
Self {
value,
style,
scalar_type,
tag: None,
}
}
pub fn with_style(value: impl Into<String>, style: ScalarStyle) -> Self {
Self {
value: value.into(),
style,
scalar_type: ScalarType::String,
tag: None,
}
}
pub fn plain(value: impl Into<String>) -> Self {
Self::with_style(value, ScalarStyle::Plain)
}
pub fn single_quoted(value: impl Into<String>) -> Self {
Self::with_style(value, ScalarStyle::SingleQuoted)
}
pub fn double_quoted(value: impl Into<String>) -> Self {
Self::with_style(value, ScalarStyle::DoubleQuoted)
}
pub fn literal(value: impl Into<String>) -> Self {
Self::with_style(value, ScalarStyle::Literal)
}
pub fn folded(value: impl Into<String>) -> Self {
Self::with_style(value, ScalarStyle::Folded)
}
pub fn null() -> Self {
Self {
value: "null".to_string(),
style: ScalarStyle::Plain,
scalar_type: ScalarType::Null,
tag: None,
}
}
pub fn timestamp(value: impl Into<String>) -> Self {
Self {
value: value.into(),
style: ScalarStyle::Plain,
scalar_type: ScalarType::Timestamp,
tag: None,
}
}
pub fn regex(pattern: impl Into<String>) -> Self {
Self {
value: pattern.into(),
style: ScalarStyle::Plain,
scalar_type: ScalarType::Regex,
tag: None,
}
}
pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
self.tag = Some(tag.into());
self
}
pub fn tag(&self) -> Option<&str> {
self.tag.as_deref()
}
pub fn value(&self) -> &str {
&self.value
}
pub fn style(&self) -> ScalarStyle {
self.style
}
pub fn scalar_type(&self) -> ScalarType {
self.scalar_type
}
pub fn to_i64(&self) -> Option<i64> {
if self.scalar_type == ScalarType::Integer {
Self::parse_integer(trim_white(&self.value))
} else {
None
}
}
#[deprecated(since = "0.3.3", note = "to_i64 follows YAML 1.2; use it instead")]
pub fn to_i64_yaml_1_2(&self) -> Option<i64> {
self.to_i64()
}
pub fn to_f64(&self) -> Option<f64> {
if self.scalar_type == ScalarType::Float {
Self::parse_float(trim_white(&self.value))
} else {
None
}
}
pub fn to_bool(&self) -> Option<bool> {
if self.scalar_type == ScalarType::Boolean {
Self::parse_bool(trim_white(&self.value))
} else {
None
}
}
pub fn is_timestamp(&self) -> bool {
self.scalar_type == ScalarType::Timestamp
}
pub fn is_regex(&self) -> bool {
self.scalar_type == ScalarType::Regex
}
#[cfg(feature = "regex")]
pub fn as_regex(&self) -> Option<regex::Regex> {
if self.scalar_type == ScalarType::Regex {
regex::Regex::new(&self.value).ok()
} else {
None
}
}
#[cfg(feature = "regex")]
pub fn try_as_regex(&self) -> Result<regex::Regex, regex::Error> {
regex::Regex::new(&self.value)
}
pub fn coerce_to_type(&self, target_type: ScalarType) -> Option<ScalarValue> {
if self.scalar_type == target_type {
return Some(self.clone());
}
match target_type {
ScalarType::String => Some(ScalarValue {
value: self.value.clone(),
style: ScalarStyle::Plain,
scalar_type: ScalarType::String,
tag: None,
}),
ScalarType::Integer => {
Self::parse_integer(trim_white(&self.value)).map(ScalarValue::from)
}
ScalarType::Float => Self::parse_float(trim_white(&self.value)).map(ScalarValue::from),
ScalarType::Boolean => match self.value.to_lowercase().as_str() {
"true" | "yes" | "on" | "1" => Some(ScalarValue::from(true)),
"false" | "no" | "off" | "0" => Some(ScalarValue::from(false)),
_ => None,
},
ScalarType::Null => match self.value.to_lowercase().as_str() {
"null" | "~" | "" => Some(ScalarValue::null()),
_ => None,
},
#[cfg(feature = "base64")]
ScalarType::Binary => {
if base64_decode(&self.value).is_ok() {
Some(ScalarValue {
value: self.value.clone(),
style: ScalarStyle::Plain,
scalar_type: ScalarType::Binary,
tag: None,
})
} else {
None
}
}
ScalarType::Timestamp => {
if Self::is_valid_timestamp(&self.value) {
Some(ScalarValue::timestamp(&self.value))
} else {
None
}
}
ScalarType::Regex => {
Some(ScalarValue::regex(&self.value))
}
}
}
pub fn parse(value: impl Into<String>) -> Self {
let value = value.into();
let scalar_type = Self::auto_detect_type(&value);
let style = match scalar_type {
ScalarType::String => Self::detect_style(&value),
_ => ScalarStyle::Plain,
};
Self {
value,
style,
scalar_type,
tag: None,
}
}
pub fn from_scalar(scalar: &crate::yaml::Scalar) -> Self {
let value = scalar.as_string();
let raw_text = scalar.value();
let style = if raw_text.starts_with('"') && raw_text.ends_with('"') {
ScalarStyle::DoubleQuoted
} else if raw_text.starts_with('\'') && raw_text.ends_with('\'') {
ScalarStyle::SingleQuoted
} else if scalar.is_block() && raw_text.starts_with('|') {
ScalarStyle::Literal
} else if scalar.is_block() {
ScalarStyle::Folded
} else {
ScalarStyle::Plain
};
let scalar_type = if style != ScalarStyle::Plain {
ScalarType::String
} else {
Self::classify_plain(raw_text.trim_end_matches([' ', '\t'])).into()
};
Self {
value,
style,
scalar_type,
tag: None,
}
}
}
impl fmt::Display for ScalarValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_yaml_string())
}
}
impl From<String> for ScalarValue {
fn from(value: String) -> Self {
Self::string(value)
}
}
impl From<&str> for ScalarValue {
fn from(value: &str) -> Self {
Self::string(value)
}
}
macro_rules! impl_from_number {
($($t:ty => $ty:expr),* $(,)?) => {
$(
impl From<$t> for ScalarValue {
fn from(value: $t) -> Self {
Self {
value: value.to_string(),
style: ScalarStyle::Plain,
scalar_type: $ty,
tag: None,
}
}
}
)*
};
}
impl_from_number! {
i32 => ScalarType::Integer,
i64 => ScalarType::Integer,
}
macro_rules! impl_from_float {
($($t:ty),* $(,)?) => {
$(
impl From<$t> for ScalarValue {
fn from(value: $t) -> Self {
Self {
value: Self::format_float(value.to_string()),
style: ScalarStyle::Plain,
scalar_type: ScalarType::Float,
tag: None,
}
}
}
)*
};
}
impl_from_float!(f32, f64);
impl From<bool> for ScalarValue {
fn from(value: bool) -> Self {
Self {
value: if value { "true" } else { "false" }.to_string(),
style: ScalarStyle::Plain,
scalar_type: ScalarType::Boolean,
tag: None,
}
}
}
impl From<crate::yaml::Scalar> for ScalarValue {
fn from(scalar: crate::yaml::Scalar) -> Self {
Self::from_scalar(&scalar)
}
}
impl crate::AsYaml for ScalarValue {
fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
None
}
fn kind(&self) -> crate::as_yaml::YamlKind {
match self.rendered_tag() {
Some(tag) => crate::as_yaml::YamlKind::Tagged(std::borrow::Cow::Owned(tag.to_string())),
None => crate::as_yaml::YamlKind::Scalar,
}
}
fn build_content(
&self,
builder: &mut rowan::GreenNodeBuilder,
indent: usize,
flow_context: bool,
) -> bool {
self.build(
builder,
indent,
flow_context.then_some(FlowQuoting::Minimal),
)
}
fn build_flow_content(
&self,
builder: &mut rowan::GreenNodeBuilder,
indent: usize,
quoting: FlowQuoting,
) -> bool {
self.build(builder, indent, Some(quoting))
}
fn is_inline(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plain_scalars() {
let scalar = ScalarValue::string("simple");
assert_eq!(scalar.to_yaml_string(), "simple");
let scalar = ScalarValue::string("hello world");
assert_eq!(scalar.to_yaml_string(), "hello world");
}
#[test]
fn test_values_needing_quotes() {
let scalar = ScalarValue::string("true");
assert_eq!(scalar.to_yaml_string(), "'true'");
let scalar = ScalarValue::string("false");
assert_eq!(scalar.to_yaml_string(), "'false'");
let scalar = ScalarValue::string("yes");
assert_eq!(scalar.to_yaml_string(), "'yes'");
let scalar = ScalarValue::string("no");
assert_eq!(scalar.to_yaml_string(), "'no'");
let scalar = ScalarValue::string("null");
assert_eq!(scalar.to_yaml_string(), "'null'");
let scalar = ScalarValue::string("~");
assert_eq!(scalar.to_yaml_string(), "'~'");
let scalar = ScalarValue::string("123");
assert_eq!(scalar.to_yaml_string(), "'123'");
let scalar = ScalarValue::string("3.14");
assert_eq!(scalar.to_yaml_string(), "'3.14'");
let scalar = ScalarValue::string("value: something");
assert_eq!(scalar.to_yaml_string(), "'value: something'");
let scalar = ScalarValue::string("# comment");
assert_eq!(scalar.to_yaml_string(), "'# comment'");
let scalar = ScalarValue::string(" spaces ");
assert_eq!(scalar.to_yaml_string(), "' spaces '");
}
#[test]
fn test_single_quoted() {
let scalar = ScalarValue::single_quoted("value with 'quotes'");
assert_eq!(scalar.to_yaml_string(), "'value with ''quotes'''");
}
#[test]
fn test_double_quoted() {
let scalar = ScalarValue::double_quoted("value with \"quotes\" and \\backslash");
assert_eq!(
scalar.to_yaml_string(),
"\"value with \\\"quotes\\\" and \\\\backslash\""
);
let scalar = ScalarValue::double_quoted("line1\nline2\ttab");
assert_eq!(scalar.to_yaml_string(), "\"line1\\nline2\\ttab\"");
}
#[test]
fn test_carriage_return_is_escaped() {
assert_eq!(ScalarValue::plain("a\rb").to_yaml_string(), "\"a\\rb\"");
assert_eq!(
ScalarValue::single_quoted("a\rb").to_yaml_string(),
"\"a\\rb\""
);
}
#[test]
fn test_multiline() {
let scalar = ScalarValue::string("line1\nline2\nline3");
assert_eq!(scalar.style(), ScalarStyle::Literal);
}
#[test]
fn test_from_types() {
let scalar = ScalarValue::from(42);
assert_eq!(scalar.to_yaml_string(), "42");
let scalar = ScalarValue::from(1.234);
assert_eq!(scalar.to_yaml_string(), "1.234");
let scalar = ScalarValue::from(true);
assert_eq!(scalar.to_yaml_string(), "true");
let scalar = ScalarValue::from(false);
assert_eq!(scalar.to_yaml_string(), "false");
}
#[test]
fn test_empty_string() {
let scalar = ScalarValue::string("");
assert_eq!(scalar.to_yaml_string(), "''");
}
#[test]
fn test_special_start_chars() {
let scalar = ScalarValue::string("-item");
assert_eq!(scalar.to_yaml_string(), "'-item'");
let scalar = ScalarValue::string("?key");
assert_eq!(scalar.to_yaml_string(), "'?key'");
let scalar = ScalarValue::string("[array]");
assert_eq!(scalar.to_yaml_string(), "'[array]'");
}
#[test]
fn test_null_scalar() {
let scalar = ScalarValue::null();
assert_eq!(scalar.to_yaml_string(), "null");
assert_eq!(scalar.scalar_type, ScalarType::Null);
}
#[test]
fn test_indentation_preservation() {
let content_with_indent = " Line 1\n Line 2 more indented\n Line 3";
let scalar = ScalarValue::literal(content_with_indent);
let yaml_output = scalar.to_literal_with_indent(0, 2);
assert_eq!(
yaml_output,
"|2-\n Line 1\n Line 2 more indented\n Line 3"
);
}
#[test]
fn test_literal_with_custom_indent() {
let content = "Line 1\nLine 2\nLine 3";
let scalar = ScalarValue::literal(content);
let yaml_4_spaces = scalar.to_literal_with_indent(0, 4);
assert_eq!(yaml_4_spaces, "|-\n Line 1\n Line 2\n Line 3");
let yaml_1_space = scalar.to_literal_with_indent(0, 1);
assert_eq!(yaml_1_space, "|-\n Line 1\n Line 2\n Line 3");
}
#[test]
fn test_block_indent_counts_from_parent_column() {
let scalar = ScalarValue::literal(" x\n y");
assert_eq!(scalar.to_yaml_string(), "|2-\n x\n y");
assert_eq!(
scalar.to_yaml_string_with_indent(2, 2),
"|2-\n x\n y"
);
assert_eq!(
scalar.to_literal_with_indent(4, 3),
"|3-\n x\n y"
);
assert_eq!(
ScalarValue::folded(" x\n").to_folded_with_indent(2, 2),
">2\n x"
);
assert_eq!(
ScalarValue::literal("x\ny\n").to_yaml_string_with_indent(2, 2),
"|\n x\n y"
);
}
#[test]
fn test_block_indicator_when_first_line_is_indented() {
assert_eq!(
ScalarValue::literal(" a\nb").to_yaml_string(),
"|2-\n a\n b"
);
assert_eq!(
ScalarValue::literal(" L1\n L2\n L3").to_yaml_string_with_indent(2, 2),
"|2-\n L1\n L2\n L3"
);
assert_eq!(
ScalarValue::literal(" \nx").to_yaml_string(),
"|2-\n \n x"
);
assert_eq!(
ScalarValue::literal("a\n b").to_yaml_string(),
"|-\n a\n b"
);
}
#[test]
fn test_block_keeps_whitespace_only_lines() {
assert_eq!(
ScalarValue::literal("a\n \nb").to_yaml_string(),
"|-\n a\n \n b"
);
}
#[test]
#[should_panic(expected = "does not fit in one digit")]
fn test_block_indent_indicator_too_large() {
ScalarValue::literal(" x").to_literal_with_indent(0, 10);
}
#[test]
#[should_panic(expected = "must be indented")]
fn test_block_indent_zero() {
ScalarValue::literal("x").to_literal_with_indent(2, 0);
}
#[test]
fn test_folded_with_custom_indent() {
let content = "Line 1\nLine 2\nLine 3";
let scalar = ScalarValue::folded(content);
let yaml_3_spaces = scalar.to_folded_with_indent(0, 3);
assert_eq!(yaml_3_spaces, ">-\n Line 1\n\n Line 2\n\n Line 3");
}
#[test]
fn test_block_scalar_first_line_more_indented() {
assert_eq!(
ScalarValue::string(" a\nb").to_yaml_string(),
"|2-\n a\n b"
);
assert_eq!(
ScalarValue::string(" x\n y").to_yaml_string_with_indent(2, 2),
"|2-\n x\n y"
);
assert_eq!(
ScalarValue::string("\n x").to_yaml_string_with_indent(2, 2),
"|2-\n\n x"
);
}
#[test]
fn test_block_scalar_whitespace_only_lines() {
assert_eq!(
ScalarValue::literal("a\n ").to_yaml_string(),
"|-\n a\n "
);
assert_eq!(
ScalarValue::literal("a\n \nb").to_yaml_string(),
"|-\n a\n \n b"
);
assert_eq!(ScalarValue::literal(" ").to_yaml_string(), "|2-\n ");
assert_eq!(ScalarValue::literal(" \n").to_yaml_string(), "|2\n ");
assert_eq!(ScalarValue::string("a\n ").to_yaml_string(), "\"a\\n \"");
assert_eq!(ScalarValue::string(" \n").to_yaml_string(), "\" \\n\"");
}
#[test]
fn test_folded_preserves_line_breaks() {
assert_eq!(
ScalarValue::folded("a\nb").to_yaml_string(),
">-\n a\n\n b"
);
assert_eq!(
ScalarValue::folded("a\n\nb\n c\nd\n").to_yaml_string(),
">\n a\n\n\n b\n c\n d"
);
}
#[test]
fn test_single_quoted_line_breaks() {
assert_eq!(
ScalarValue::single_quoted("a\nb").to_yaml_string(),
"'a\n\n b'"
);
assert_eq!(
ScalarValue::single_quoted("a\n\nb").to_yaml_string(),
"'a\n\n\n b'"
);
assert_eq!(
ScalarValue::single_quoted("\nb\n").to_yaml_string(),
"'\n\n b\n\n'"
);
assert_eq!(
ScalarValue::single_quoted("a\nb").to_yaml_string_with_indent(2, 2),
"'a\n\n b'"
);
assert_eq!(
ScalarValue::single_quoted("a \nb").to_yaml_string(),
"\"a \\nb\""
);
assert_eq!(
ScalarValue::single_quoted("a\n b").to_yaml_string(),
"\"a\\n b\""
);
}
#[test]
fn test_mixed_empty_lines_preservation() {
let content_with_empty_lines = "Line 1\n\nLine 3\n\n\nLine 6";
let scalar = ScalarValue::literal(content_with_empty_lines);
let yaml_output = scalar.to_literal_with_indent(0, 2);
assert_eq!(yaml_output, "|-\n Line 1\n\n Line 3\n\n\n Line 6");
let lines: Vec<&str> = yaml_output.lines().collect();
let empty_line_count = lines.iter().filter(|line| line.is_empty()).count();
assert_eq!(empty_line_count, 3);
}
#[test]
fn test_double_quoted_with_escapes() {
let original = "Hello\nWorld\t😃";
let scalar = ScalarValue::double_quoted(original);
let yaml_string = scalar.to_yaml_string();
assert_eq!(yaml_string, "\"Hello\\nWorld\\t\\U0001F603\"");
let parsed = ScalarValue::parse_escape_sequences(&yaml_string[1..yaml_string.len() - 1]);
assert_eq!(parsed, original);
}
#[test]
fn test_unicode_output_formatting() {
let scalar = ScalarValue::double_quoted("Hello 世界 🌍");
let yaml_string = scalar.to_yaml_string();
assert_eq!(yaml_string, "\"Hello \\u4E16\\u754C \\U0001F30D\"");
assert_eq!(scalar.value(), "Hello 世界 🌍");
}
#[test]
fn test_timestamp_creation_and_validation() {
let valid_timestamps = [
"2023-12-25",
"2023-12-25T10:30:45",
"2023-12-25 10:30:45",
"2023-12-25T10:30:45Z",
"2001-12-14 21:59:43.10 -5", "2001-12-15T02:59:43.1Z", "2001-12-14t21:59:43.10-05:00", ];
for ts in valid_timestamps {
let scalar = ScalarValue::timestamp(ts);
assert!(scalar.is_timestamp());
assert_eq!(scalar.scalar_type(), ScalarType::Timestamp);
assert_eq!(scalar.value(), ts);
let yaml_output = scalar.to_yaml_string();
assert_eq!(yaml_output, format!("!!timestamp {}", ts));
let auto_scalar = ScalarValue::parse(ts);
assert_eq!(
auto_scalar.scalar_type(),
ScalarType::Timestamp,
"Failed to auto-detect '{}' as timestamp",
ts
);
}
let invalid_timestamps = [
"not-a-date",
"2023-13-01", "2023-12-32", "12:34:56", "2023/12/25", ];
for ts in invalid_timestamps {
let auto_scalar = ScalarValue::parse(ts);
assert_ne!(
auto_scalar.scalar_type(),
ScalarType::Timestamp,
"'{}' should not be detected as timestamp",
ts
);
}
}
#[test]
fn test_regex_creation() {
let pattern = r"^\d{3}-\d{2}-\d{4}$";
let scalar = ScalarValue::regex(pattern);
assert!(scalar.is_regex());
assert_eq!(scalar.scalar_type(), ScalarType::Regex);
assert_eq!(scalar.value(), pattern);
let yaml_output = scalar.to_yaml_string();
assert_eq!(yaml_output, format!("!!regex {}", pattern));
}
#[test]
fn test_regex_edge_cases() {
let empty_regex = ScalarValue::regex("");
assert!(empty_regex.is_regex());
assert_eq!(empty_regex.value(), "");
assert_eq!(empty_regex.to_yaml_string(), "!!regex");
let special_chars = ScalarValue::regex(r"[.*+?^${}()|[\]\\]");
assert!(special_chars.is_regex());
assert_eq!(special_chars.value(), r"[.*+?^${}()|[\]\\]");
let unicode_regex = ScalarValue::regex(r"\p{L}+");
assert!(unicode_regex.is_regex());
assert_eq!(unicode_regex.value(), r"\p{L}+");
let long_pattern = "a".repeat(1000);
let long_regex = ScalarValue::regex(&long_pattern);
assert!(long_regex.is_regex());
assert_eq!(long_regex.value(), long_pattern);
let quoted_regex = ScalarValue::regex(r#"'quoted' and "double quoted" with \\ backslash"#);
assert!(quoted_regex.is_regex());
assert_eq!(
quoted_regex.value(),
r#"'quoted' and "double quoted" with \\ backslash"#
);
}
#[test]
fn test_regex_type_coercion() {
let regex_scalar = ScalarValue::regex(r"\d+");
let string_scalar = regex_scalar.coerce_to_type(ScalarType::String).unwrap();
assert_eq!(string_scalar.scalar_type(), ScalarType::String);
assert_eq!(string_scalar.value(), r"\d+");
assert!(!string_scalar.is_regex());
let str_scalar = ScalarValue::string("test.*");
let regex_from_string = str_scalar.coerce_to_type(ScalarType::Regex).unwrap();
assert_eq!(regex_from_string.scalar_type(), ScalarType::Regex);
assert_eq!(regex_from_string.value(), "test.*");
assert!(regex_from_string.is_regex());
assert!(regex_scalar.coerce_to_type(ScalarType::Integer).is_none());
assert!(regex_scalar.coerce_to_type(ScalarType::Float).is_none());
assert!(regex_scalar.coerce_to_type(ScalarType::Boolean).is_none());
}
#[test]
#[cfg(feature = "regex")]
fn test_regex_compilation() {
let regex_scalar = ScalarValue::regex(r"\d{3}-\d{4}");
let compiled = regex_scalar.as_regex().unwrap();
assert!(compiled.is_match("555-1234"));
assert!(!compiled.is_match("not-a-phone"));
let string_scalar = ScalarValue::string("not a regex");
assert!(string_scalar.as_regex().is_none());
let pattern_scalar = ScalarValue::string(r"^\w+@\w+\.\w+$");
let email_regex = pattern_scalar.try_as_regex().unwrap();
assert!(email_regex.is_match("test@example.com"));
assert!(!email_regex.is_match("not-an-email"));
let invalid_scalar = ScalarValue::regex(r"[invalid(");
assert!(invalid_scalar.as_regex().is_none());
let invalid_pattern = ScalarValue::string(r"[invalid(");
assert!(invalid_pattern.try_as_regex().is_err());
}
#[test]
#[cfg(feature = "regex")]
fn test_regex_extraction_use_cases() {
let validation_rules = [
ScalarValue::regex(r"^\d{5}$"), ScalarValue::regex(r"^[A-Z]{2}$"), ScalarValue::regex(r"^\(\d{3}\) \d{3}-\d{4}$"), ];
let test_values = ["12345", "CA", "(555) 123-4567"];
for (rule, value) in validation_rules.iter().zip(test_values.iter()) {
let regex = rule.as_regex().unwrap();
assert!(regex.is_match(value), "Pattern should match {}", value);
}
let email_regex = ScalarValue::regex(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$");
let compiled = email_regex.as_regex().unwrap();
assert!(compiled.is_match("user@example.com"));
assert!(compiled.is_match("test.user+tag@sub.domain.org"));
assert!(!compiled.is_match("invalid.email"));
let version_regex = ScalarValue::regex(r"^v(\d+)\.(\d+)\.(\d+)$");
let compiled = version_regex.as_regex().unwrap();
if let Some(captures) = compiled.captures("v1.2.3") {
assert_eq!(captures.get(1).unwrap().as_str(), "1");
assert_eq!(captures.get(2).unwrap().as_str(), "2");
assert_eq!(captures.get(3).unwrap().as_str(), "3");
} else {
panic!("Should have matched version string");
}
}
#[test]
fn test_type_coercion() {
let str_scalar = ScalarValue::string("42");
let int_scalar = str_scalar.coerce_to_type(ScalarType::Integer).unwrap();
assert_eq!(int_scalar.scalar_type(), ScalarType::Integer);
assert_eq!(int_scalar.value(), "42");
let bool_scalar = ScalarValue::string("true")
.coerce_to_type(ScalarType::Boolean)
.unwrap();
assert_eq!(bool_scalar.scalar_type(), ScalarType::Boolean);
assert_eq!(bool_scalar.value(), "true");
let yes_scalar = ScalarValue::string("yes")
.coerce_to_type(ScalarType::Boolean)
.unwrap();
assert_eq!(yes_scalar.value(), "true");
let no_scalar = ScalarValue::string("no")
.coerce_to_type(ScalarType::Boolean)
.unwrap();
assert_eq!(no_scalar.value(), "false");
let str_scalar = ScalarValue::string("not_a_number");
assert!(str_scalar.coerce_to_type(ScalarType::Integer).is_none());
}
#[test]
fn test_auto_type_detection() {
assert_eq!(ScalarValue::auto_detect_type("42"), ScalarType::Integer);
assert_eq!(ScalarValue::auto_detect_type("3.14"), ScalarType::Float);
assert_eq!(ScalarValue::auto_detect_type("true"), ScalarType::Boolean);
assert_eq!(ScalarValue::auto_detect_type("false"), ScalarType::Boolean);
assert_eq!(ScalarValue::auto_detect_type("yes"), ScalarType::String);
assert_eq!(ScalarValue::auto_detect_type("NuLl"), ScalarType::String);
assert_eq!(ScalarValue::auto_detect_type("null"), ScalarType::Null);
assert_eq!(ScalarValue::auto_detect_type("~"), ScalarType::Null);
assert_eq!(ScalarValue::auto_detect_type(""), ScalarType::Null);
assert_eq!(
ScalarValue::auto_detect_type("2023-12-25"),
ScalarType::Timestamp
);
assert_eq!(
ScalarValue::auto_detect_type("2023-12-25T10:30:45"),
ScalarType::Timestamp
);
assert_eq!(
ScalarValue::auto_detect_type("SGVsbG8gV29ybGQ="),
ScalarType::String
);
assert_eq!(
ScalarValue::auto_detect_type("hello world"),
ScalarType::String
);
}
#[test]
fn test_from_yaml_scalar_creation() {
let int_scalar = ScalarValue::parse("123");
assert_eq!(int_scalar.scalar_type(), ScalarType::Integer);
let bool_scalar = ScalarValue::parse("true");
assert_eq!(bool_scalar.scalar_type(), ScalarType::Boolean);
let timestamp_scalar = ScalarValue::parse("2023-12-25");
assert_eq!(timestamp_scalar.scalar_type(), ScalarType::Timestamp);
let string_scalar = ScalarValue::parse("hello world");
assert_eq!(string_scalar.scalar_type(), ScalarType::String);
}
#[test]
fn test_yaml_1_1_timestamp_examples() {
for text in [
"2001-12-15T02:59:43.1Z",
"2001-12-14t21:59:43.10-05:00",
"2001-12-14 21:59:43.10 -5",
"2001-12-15 2:59:43.10",
"2002-12-14",
"2001-1-5T1:02:03Z",
"2001-12-14 21:59:43",
"2001-12-14\t21:59:43",
] {
assert_eq!(
ScalarValue::parse(text).scalar_type(),
ScalarType::Timestamp,
"{text:?}"
);
}
for text in [
"2001-1-5",
"2001-12-5",
"2001-1-15",
"2001-12-14T",
"2001-12-14 123:59:43",
"2001-12-14T21:5:43",
"2001-12-14T21:59:4",
"2001-12-14x21:59:43",
"2001-12-14T24:00:00",
"2001-12-32",
] {
assert_eq!(
ScalarValue::parse(text).scalar_type(),
ScalarType::String,
"{text:?}"
);
}
}
#[test]
#[cfg(feature = "base64")]
fn test_binary_yaml_output_with_tags() {
let data = b"Binary data here";
let scalar = ScalarValue::binary(data);
let yaml_output = scalar.to_yaml_string();
assert!(yaml_output.starts_with("!!binary "));
let base64_part = &yaml_output[9..]; let decoded = base64_decode(base64_part).expect("Should decode");
assert_eq!(decoded, data);
}
#[test]
#[cfg(feature = "base64")]
fn test_special_data_types_with_different_styles() {
let data = b"test";
let binary_scalar = ScalarValue::binary(data);
let mut styled_binary = binary_scalar;
styled_binary.style = ScalarStyle::DoubleQuoted;
assert_eq!(styled_binary.to_yaml_string(), "!!binary \"dGVzdA==\"");
}
#[test]
fn test_type_checking_methods() {
#[cfg(feature = "base64")]
let binary_scalar = ScalarValue::binary(b"test");
let timestamp_scalar = ScalarValue::timestamp("2023-12-25");
let regex_scalar = ScalarValue::regex(r"\d+");
let string_scalar = ScalarValue::string("hello");
#[cfg(feature = "base64")]
assert!(binary_scalar.is_binary());
#[cfg(feature = "base64")]
assert!(!binary_scalar.is_timestamp());
#[cfg(feature = "base64")]
assert!(!binary_scalar.is_regex());
#[cfg(feature = "base64")]
assert!(!timestamp_scalar.is_binary());
assert!(timestamp_scalar.is_timestamp());
assert!(!timestamp_scalar.is_regex());
#[cfg(feature = "base64")]
assert!(!regex_scalar.is_binary());
assert!(!regex_scalar.is_timestamp());
assert!(regex_scalar.is_regex());
#[cfg(feature = "base64")]
assert!(!string_scalar.is_binary());
assert!(!string_scalar.is_timestamp());
assert!(!string_scalar.is_regex());
}
#[test]
fn test_binary_is_not_an_integer() {
for text in ["0b1010", "0B101", "-0b1010", "+0b101"] {
assert_eq!(ScalarValue::parse_integer(text), None, "{text}");
assert_eq!(ScalarValue::auto_detect_type(text), ScalarType::String);
}
}
#[test]
fn test_modern_octal_number_parsing() {
assert_eq!(ScalarValue::parse_integer("0o755"), Some(493)); assert_eq!(ScalarValue::parse_integer("0o644"), Some(420)); assert_eq!(ScalarValue::auto_detect_type("0o755"), ScalarType::Integer);
for text in ["0O777", "-0o755", "+0o644"] {
assert_eq!(ScalarValue::parse_integer(text), None, "{text}");
assert_eq!(ScalarValue::auto_detect_type(text), ScalarType::String);
}
assert_eq!(ScalarValue::parse_integer("0o789"), None); assert_eq!(ScalarValue::parse_integer("0o"), None); }
#[test]
fn test_leading_zero_is_decimal() {
assert_eq!(ScalarValue::parse_integer("0755"), Some(755));
assert_eq!(ScalarValue::parse_integer("0644"), Some(644));
assert_eq!(ScalarValue::parse_integer("-0777"), Some(-777));
assert_eq!(ScalarValue::auto_detect_type("0755"), ScalarType::Integer);
assert_eq!(ScalarValue::auto_detect_type("0644"), ScalarType::Integer);
assert_eq!(ScalarValue::parse_integer("0"), Some(0)); assert_eq!(ScalarValue::parse_integer("00"), Some(0));
assert_eq!(ScalarValue::parse_integer("0789"), Some(789));
assert_eq!(ScalarValue::parse_integer("08"), Some(8));
}
#[test]
fn test_hexadecimal_number_parsing() {
assert_eq!(ScalarValue::parse_integer("0xFF"), Some(255));
assert_eq!(ScalarValue::parse_integer("0x1A"), Some(26));
assert_eq!(ScalarValue::parse_integer("0xff"), Some(255));
assert_eq!(ScalarValue::auto_detect_type("0xFF"), ScalarType::Integer);
for text in ["0XFF", "-0xFF", "+0x1A", "+0x1F", "-0o7", "0X1F", "0O7"] {
assert_eq!(ScalarValue::parse_integer(text), None, "{text}");
assert_eq!(ScalarValue::auto_detect_type(text), ScalarType::String);
assert_eq!(
ScalarValue::classify_plain(text),
CoreScalarType::String,
"{text}"
);
}
}
#[test]
fn test_decimal_number_parsing() {
assert_eq!(ScalarValue::parse_integer("42"), Some(42));
assert_eq!(ScalarValue::parse_integer("123"), Some(123));
assert_eq!(ScalarValue::parse_integer("-42"), Some(-42));
assert_eq!(ScalarValue::parse_integer("+123"), Some(123));
assert_eq!(ScalarValue::auto_detect_type("42"), ScalarType::Integer);
assert_eq!(ScalarValue::auto_detect_type("-123"), ScalarType::Integer);
}
#[test]
fn test_number_format_yaml_output() {
let binary_scalar = ScalarValue::parse("0b1010");
assert_eq!(binary_scalar.scalar_type(), ScalarType::String);
assert_eq!(binary_scalar.to_yaml_string(), "'0b1010'");
let octal_scalar = ScalarValue::parse("0o755");
assert_eq!(octal_scalar.scalar_type(), ScalarType::Integer);
assert_eq!(octal_scalar.value(), "0o755");
let hex_scalar = ScalarValue::parse("0xFF");
assert_eq!(hex_scalar.scalar_type(), ScalarType::Integer);
assert_eq!(hex_scalar.value(), "0xFF");
let legacy_octal_scalar = ScalarValue::parse("0755");
assert_eq!(legacy_octal_scalar.scalar_type(), ScalarType::Integer);
assert_eq!(legacy_octal_scalar.value(), "0755");
assert_eq!(legacy_octal_scalar.to_i64(), Some(755));
}
#[test]
fn test_coerce_to_null() {
let s = ScalarValue::string("null");
assert_eq!(
s.coerce_to_type(ScalarType::Null).map(|v| v.scalar_type()),
Some(ScalarType::Null)
);
assert!(ScalarValue::string("~")
.coerce_to_type(ScalarType::Null)
.is_some());
assert!(ScalarValue::string("")
.coerce_to_type(ScalarType::Null)
.is_some());
assert!(ScalarValue::string("notnull")
.coerce_to_type(ScalarType::Null)
.is_none());
}
#[test]
fn test_parse_preserves_string_style() {
let plain = ScalarValue::parse("hello");
assert_eq!(plain.scalar_type(), ScalarType::String);
assert_eq!(plain.style, ScalarStyle::Plain);
let multiline = ScalarValue::parse("multi line\nvalue");
assert_eq!(multiline.scalar_type(), ScalarType::String);
assert_eq!(multiline.style, ScalarStyle::Literal);
}
#[test]
fn test_from_scalar_type_and_style() {
use crate::yaml::Document;
use rowan::ast::AstNode;
use std::str::FromStr;
let doc = Document::from_str("k: null\n").unwrap();
let mapping = doc.as_mapping().unwrap();
let null_node = mapping.get("k").unwrap();
let null_scalar = crate::yaml::Scalar::cast(null_node.syntax().clone()).unwrap();
let sv = ScalarValue::from_scalar(&null_scalar);
assert_eq!(sv.scalar_type(), ScalarType::Null);
assert_eq!(sv.style, ScalarStyle::Plain);
let doc = Document::from_str("k: \"hi\"\n").unwrap();
let node = doc.as_mapping().unwrap().get("k").unwrap();
let scalar = crate::yaml::Scalar::cast(node.syntax().clone()).unwrap();
let sv = ScalarValue::from_scalar(&scalar);
assert_eq!(sv.scalar_type(), ScalarType::String);
assert_eq!(sv.style, ScalarStyle::DoubleQuoted);
let doc = Document::from_str("k: 'hi'\n").unwrap();
let node = doc.as_mapping().unwrap().get("k").unwrap();
let scalar = crate::yaml::Scalar::cast(node.syntax().clone()).unwrap();
let sv = ScalarValue::from_scalar(&scalar);
assert_eq!(sv.style, ScalarStyle::SingleQuoted);
}
#[test]
fn test_from_scalar_ampersand_in_plain_is_string() {
use crate::yaml::Document;
use rowan::ast::AstNode;
use std::str::FromStr;
let doc = Document::from_str("k: 3.1&1\n").unwrap();
let node = doc.as_mapping().unwrap().get("k").unwrap();
let scalar = crate::yaml::Scalar::cast(node.syntax().clone()).unwrap();
let sv = ScalarValue::from_scalar(&scalar);
assert_eq!(sv.scalar_type(), ScalarType::String);
assert_eq!(sv.style, ScalarStyle::Plain);
}
#[test]
fn test_from_scalar_signed_yaml_infinity_is_float() {
use crate::yaml::Document;
use rowan::ast::AstNode;
use std::str::FromStr;
let doc = Document::from_str("k: +.INF\n").unwrap();
let node = doc.as_mapping().unwrap().get("k").unwrap();
let scalar = crate::yaml::Scalar::cast(node.syntax().clone()).unwrap();
let sv = ScalarValue::from_scalar(&scalar);
assert_eq!(sv.scalar_type(), ScalarType::Float);
}
#[test]
fn test_classify_plain_bare_inf_nan_is_string() {
for input in [
"inf", "Inf", "InF", "INF", "iNf", "nan", "NaN", "NAN", "nAn", "infinity", "Infinity",
"INFINITY", "+inf", "-Inf", "+nan",
] {
assert_eq!(
ScalarValue::classify_plain(input),
CoreScalarType::String,
"bare {input:?} should classify as String"
);
}
for input in [".inf", ".Inf", ".INF", "+.inf", "-.INF", ".nan"] {
assert_eq!(
ScalarValue::classify_plain(input),
CoreScalarType::Float,
"{input:?} should classify as Float"
);
}
}
#[test]
fn test_canonical_integer() {
for (text, expected) in [
("0", "0"),
("-0", "0"),
("+000", "0"),
("0x0", "0"),
("0o0", "0"),
("42", "42"),
("+0042", "42"),
("-42", "-42"),
("0x2A", "42"),
("0o52", "42"),
("-9223372036854775808", "-9223372036854775808"),
("9223372036854775808", "9223372036854775808"),
("+9223372036854775808", "9223372036854775808"),
("0x10000000000000000", "18446744073709551616"),
("0o2000000000000000000000", "18446744073709551616"),
("0x1000000000", "68719476736"),
(
"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"340282366920938463463374607431768211455",
),
(
"0x100000000000000000000000000000000",
"340282366920938463463374607431768211456",
),
(
"-000100000000000000000000000000",
"-100000000000000000000000000",
),
] {
assert_eq!(
ScalarValue::canonical_integer(text).as_deref(),
Some(expected),
"{text}"
);
}
for text in [
"", "+", "-", "0x", "0o8", "-0x1", "+0o1", "1_000", "1.0", " 1",
] {
assert_eq!(ScalarValue::canonical_integer(text), None, "{text:?}");
}
}
#[test]
fn test_from_scalar_block_style_is_string() {
use crate::yaml::Document;
use rowan::ast::AstNode;
use std::str::FromStr;
let doc = Document::from_str("k: |\n 1\n").unwrap();
let node = doc.as_mapping().unwrap().get("k").unwrap();
let scalar = crate::yaml::Scalar::cast(node.syntax().clone()).unwrap();
let sv = ScalarValue::from_scalar(&scalar);
assert_eq!(sv.style, ScalarStyle::Literal);
assert_eq!(sv.scalar_type(), ScalarType::String);
let doc = Document::from_str("k: >\n x\n").unwrap();
let node = doc.as_mapping().unwrap().get("k").unwrap();
let scalar = crate::yaml::Scalar::cast(node.syntax().clone()).unwrap();
let sv = ScalarValue::from_scalar(&scalar);
assert_eq!(sv.style, ScalarStyle::Folded);
assert_eq!(sv.scalar_type(), ScalarType::String);
}
#[test]
fn test_needs_quoting_special_chars() {
let cases = [
("&anchor", "'&anchor'"),
("*alias", "'*alias'"),
("!tag", "'!tag'"),
("a|b", "'a|b'"),
("it's", "\"it's\""),
("a\"b", "'a\"b'"),
("50%", "'50%'"),
("on", "'on'"),
("off", "'off'"),
("On", "'On'"),
("OFF", "'OFF'"),
("yes", "'yes'"),
("no", "'no'"),
("true", "'true'"),
("false", "'false'"),
("null", "'null'"),
];
for (input, expected) in cases {
assert_eq!(ScalarValue::string(input).to_yaml_string(), expected);
}
assert_eq!(ScalarValue::string("plain").to_yaml_string(), "plain");
}
#[test]
fn test_plain_string_with_control_character_is_double_quoted() {
assert_eq!(ScalarValue::plain("\u{7}").to_yaml_string(), "\"\\a\"");
assert_eq!(ScalarValue::plain("a\rb").to_yaml_string(), "\"a\\rb\"");
assert_eq!(
ScalarValue::plain("a\rb").with_tag("!x").to_yaml_string(),
"!x \"a\\rb\""
);
}
#[test]
fn test_literal_and_folded_nonempty() {
let s = ScalarValue::with_style("line one\nline two", ScalarStyle::Literal);
assert_eq!(s.to_yaml_string(), "|-\n line one\n line two");
let s = ScalarValue::with_style("line one\nline two", ScalarStyle::Folded);
assert_eq!(s.to_yaml_string(), ">-\n line one\n\n line two");
}
#[test]
fn test_display_matches_yaml_string() {
let s = ScalarValue::string("true");
assert_eq!(format!("{}", s), s.to_yaml_string());
assert_eq!(format!("{}", s), "'true'");
}
#[test]
fn test_as_yaml_token_kind_per_type() {
use crate::lex::SyntaxKind;
use rowan::ast::AstNode;
let cases = [
(ScalarValue::parse("42"), SyntaxKind::INT),
(ScalarValue::parse("3.14"), SyntaxKind::FLOAT),
(ScalarValue::parse("true"), SyntaxKind::BOOL),
(ScalarValue::parse("null"), SyntaxKind::NULL),
(ScalarValue::string("hello"), SyntaxKind::STRING),
];
for (value, expected) in cases {
let mut builder = rowan::GreenNodeBuilder::new();
crate::AsYaml::build_content(&value, &mut builder, 0, false);
let green = builder.finish();
let node = rowan::SyntaxNode::<crate::yaml::Lang>::new_root(green);
let scalar = crate::yaml::Scalar::cast(node).unwrap();
let kind = scalar
.syntax()
.children_with_tokens()
.filter_map(|c| c.into_token())
.next()
.unwrap()
.kind();
assert_eq!(kind, expected, "value {:?}", value.value());
}
}
#[test]
#[cfg(feature = "regex")]
fn test_as_regex_type_gated() {
let re = ScalarValue::regex(r"\d+");
assert!(re.as_regex().is_some());
let not_re = ScalarValue::string(r"\d+");
assert!(not_re.as_regex().is_none());
assert!(not_re.try_as_regex().is_ok());
assert!(ScalarValue::string("(").try_as_regex().is_err());
}
#[test]
fn test_from_f64_renders_yaml_float() {
let cases: [(f64, &str); 6] = [
(f64::NAN, ".nan"),
(f64::INFINITY, ".inf"),
(f64::NEG_INFINITY, "-.inf"),
(1.0, "1.0"),
(-0.0, "-0.0"),
(2.5, "2.5"),
];
for (value, expected) in cases {
let sv = ScalarValue::from(value);
assert_eq!(sv.to_yaml_string(), expected);
assert_eq!(ScalarValue::classify_plain(expected), CoreScalarType::Float);
}
assert_eq!(ScalarValue::from(1.0f32).to_yaml_string(), "1.0");
assert_eq!(ScalarValue::from(f32::NAN).to_yaml_string(), ".nan");
}
#[test]
fn test_to_f64_special_values() {
let float = |s: &str| ScalarValue {
value: s.to_string(),
style: ScalarStyle::Plain,
scalar_type: ScalarType::Float,
tag: None,
};
assert_eq!(float(".inf").to_f64(), Some(f64::INFINITY));
assert_eq!(float("+.INF").to_f64(), Some(f64::INFINITY));
assert_eq!(float("-.Inf").to_f64(), Some(f64::NEG_INFINITY));
assert!(float(".NaN").to_f64().unwrap().is_nan());
assert_eq!(float("inf").to_f64(), None);
assert_eq!(float("1.5").to_f64(), Some(1.5));
}
#[test]
fn test_parse_does_not_infer_binary() {
for text in ["test", "abcd", "noon", "SGVsbG8="] {
let scalar = ScalarValue::parse(text);
assert_eq!(scalar.scalar_type(), ScalarType::String, "{text}");
assert_eq!(scalar.to_yaml_string(), text);
}
}
#[test]
fn test_sign_after_radix_prefix_is_not_integer() {
for text in ["0x-5", "0x+5", "0o-7", "0o+7", "0b-1", "+-5", "-+5"] {
assert_eq!(ScalarValue::parse_integer(text), None, "{text}");
assert_eq!(
ScalarValue::classify_plain(text),
CoreScalarType::String,
"{text}"
);
assert_eq!(
ScalarValue::auto_detect_type(text),
ScalarType::String,
"{text}"
);
let doc: crate::yaml::Document = format!("k: {text}\n").parse().unwrap();
let node = doc.as_mapping().unwrap().get("k").unwrap();
assert_eq!(node.as_scalar().unwrap().as_i64(), None, "{text}");
}
}
#[test]
fn test_integers_outside_i64() {
assert_eq!(
ScalarValue::parse_integer("-9223372036854775808"),
Some(i64::MIN)
);
assert_eq!(ScalarValue::parse_integer("9223372036854775808"), None);
for text in [
"9223372036854775808",
"-9223372036854775808",
"0xFFFFFFFFFFFFFFFF",
"123456789012345678901234567890",
] {
assert_eq!(
ScalarValue::classify_plain(text),
CoreScalarType::Integer,
"{text}"
);
assert_eq!(
ScalarValue::auto_detect_type(text),
ScalarType::Integer,
"{text}"
);
}
assert_eq!(
ScalarValue::parse("-9223372036854775808").to_i64(),
Some(i64::MIN)
);
assert_eq!(ScalarValue::parse("9223372036854775808").to_i64(), None);
}
#[test]
fn test_coerce_to_special_types() {
let coerce = |value: &str, target: ScalarType| {
ScalarValue::string(value)
.coerce_to_type(target)
.map(|s| (s.scalar_type(), s.value().to_string()))
};
#[cfg(feature = "base64")]
{
assert_eq!(
coerce("aGk=", ScalarType::Binary),
Some((ScalarType::Binary, "aGk=".to_string()))
);
assert_eq!(coerce("!!", ScalarType::Binary), None);
assert_eq!(ScalarValue::string("aGk=").as_binary(), None);
assert_eq!(
ScalarValue::binary(b"hi").as_binary(),
Some(Ok(b"hi".to_vec()))
);
}
assert_eq!(
coerce("2001-12-14", ScalarType::Timestamp),
Some((ScalarType::Timestamp, "2001-12-14".to_string()))
);
assert_eq!(coerce("x", ScalarType::Timestamp), None);
assert_eq!(
coerce("a+", ScalarType::Regex),
Some((ScalarType::Regex, "a+".to_string()))
);
assert_eq!(
coerce("~", ScalarType::Null),
Some((ScalarType::Null, "null".to_string()))
);
assert_eq!(coerce("x", ScalarType::Null), None);
}
#[test]
#[should_panic(expected = "does not fit in one digit")]
fn test_block_indent_indicator_from_first_line_only() {
ScalarValue::literal("\n x\ny").to_literal_with_indent(0, 10);
}
}