use std::collections::HashMap;
use log::debug;
use regex::Regex;
use saphyr::AnnotatedMapping;
use saphyr::MarkedYaml;
use saphyr::Scalar;
use saphyr::YamlData;
use crate::loader;
use crate::schemas::StringFormat;
use crate::utils::format_hash_map;
use crate::utils::format_marker;
#[derive(Default)]
pub struct StringSchema {
pub min_length: Option<usize>,
pub max_length: Option<usize>,
pub pattern: Option<Regex>,
pub format: Option<StringFormat>,
}
impl std::fmt::Debug for StringSchema {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut h = HashMap::new();
if let Some(min_length) = self.min_length {
h.insert("minLength".to_string(), min_length.to_string());
}
if let Some(max_length) = self.max_length {
h.insert("maxLength".to_string(), max_length.to_string());
}
if let Some(pattern) = &self.pattern {
h.insert("pattern".to_string(), pattern.as_str().to_string());
}
if let Some(format) = &self.format {
h.insert("format".to_string(), format.to_string());
}
write!(f, "StringSchema {}", format_hash_map(&h))
}
}
impl StringSchema {
pub fn builder() -> StringSchemaBuilder {
StringSchemaBuilder::new()
}
}
impl PartialEq for StringSchema {
fn eq(&self, other: &Self) -> bool {
self.min_length == other.min_length
&& self.max_length == other.max_length
&& are_patterns_equivalent(&self.pattern, &other.pattern)
&& self.format == other.format
}
}
impl TryFrom<&MarkedYaml<'_>> for StringSchema {
type Error = crate::Error;
fn try_from(value: &MarkedYaml) -> Result<StringSchema, Self::Error> {
if let YamlData::Mapping(mapping) = &value.data {
Ok(StringSchema::try_from(mapping)?)
} else {
Err(expected_mapping!(value))
}
}
}
impl TryFrom<&AnnotatedMapping<'_, MarkedYaml<'_>>> for StringSchema {
type Error = crate::Error;
fn try_from(mapping: &AnnotatedMapping<'_, MarkedYaml<'_>>) -> crate::Result<Self> {
let mut string_schema = StringSchema::default();
for (key, value) in mapping.iter() {
if let YamlData::Value(Scalar::String(key)) = &key.data {
match key.as_ref() {
"minLength" => {
if let Ok(i) = loader::load_integer_marked(value) {
string_schema.min_length = Some(i as usize);
} else {
return Err(unsupported_type!(
"minLength expected integer, but got: {:?}",
value
));
}
}
"maxLength" => {
if let Ok(i) = loader::load_integer_marked(value) {
string_schema.max_length = Some(i as usize);
} else {
return Err(unsupported_type!(
"maxLength expected integer, but got: {:?}",
value
));
}
}
"pattern" => {
if let YamlData::Value(Scalar::String(s)) = &value.data {
let regex = regex::Regex::new(s.as_ref())?;
string_schema.pattern = Some(regex);
} else {
return Err(unsupported_type!(
"pattern expected string, but got: {:?}",
value
));
}
}
"format" => {
if let YamlData::Value(Scalar::String(s)) = &value.data {
string_schema.format = Some(
s.as_ref()
.parse::<StringFormat>()
.unwrap_or_else(|e| match e {}),
);
} else {
return Err(unsupported_type!(
"format expected string, but got: {:?}",
value
));
}
}
"type" => {
if let YamlData::Value(Scalar::String(s)) = &value.data {
if s != "string" {
return Err(unsupported_type!(
"Expected type: string, but got: {}",
s
));
}
} else if let YamlData::Sequence(values) = &value.data {
if !values
.iter()
.any(|v| v.data == MarkedYaml::value_from_str("string").data)
{
return Err(unsupported_type!(
"Expected type: string, but got: {:?}",
value
));
}
} else {
return Err(expected_type_is_string!(value));
}
}
_ => {
debug!("[StringSchema] Unsupported key for `type: string`: {key}");
}
}
} else {
return Err(expected_scalar!(
"{} Expected a scalar key, got: {:?}",
format_marker(&key.span.start),
key
));
}
}
Ok(string_schema)
}
}
fn are_patterns_equivalent(a: &Option<Regex>, b: &Option<Regex>) -> bool {
match (a, b) {
(Some(a), Some(b)) => a.as_str() == b.as_str(),
(None, None) => true,
_ => false,
}
}
impl std::fmt::Display for StringSchema {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"StringSchema {{ min_length: {:?}, max_length: {:?}, pattern: {:?}, format: {:?} }}",
self.min_length, self.max_length, self.pattern, self.format
)
}
}
pub struct StringSchemaBuilder(StringSchema);
impl Default for StringSchemaBuilder {
fn default() -> Self {
Self::new()
}
}
impl StringSchemaBuilder {
pub fn new() -> Self {
Self(StringSchema::default())
}
pub fn build(&mut self) -> StringSchema {
std::mem::take(&mut self.0)
}
pub fn min_length(&mut self, min_length: usize) -> &mut Self {
self.0.min_length = Some(min_length);
self
}
pub fn max_length(&mut self, max_length: usize) -> &mut Self {
self.0.max_length = Some(max_length);
self
}
pub fn pattern(&mut self, pattern: Regex) -> &mut Self {
self.0.pattern = Some(pattern);
self
}
pub fn format(&mut self, format: StringFormat) -> &mut Self {
self.0.format = Some(format);
self
}
}