use std::fmt;
use crate::value::ElementProperty;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum ValueType {
Null,
Boolean,
Int64,
Float64,
String,
Uuid,
Array(Box<ValueType>),
Object,
Union(Vec<ValueType>),
}
impl fmt::Display for ValueType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ValueType::Null => f.write_str("null"),
ValueType::Boolean => f.write_str("boolean"),
ValueType::Int64 => f.write_str("int64"),
ValueType::Float64 => f.write_str("float64"),
ValueType::String => f.write_str("string"),
ValueType::Uuid => f.write_str("uuid"),
ValueType::Array(inner) => write!(f, "array<{inner}>"),
ValueType::Object => f.write_str("object"),
ValueType::Union(variants) => {
f.write_str("union<")?;
for (index, variant) in variants.iter().enumerate() {
if index > 0 {
f.write_str("|")?;
}
write!(f, "{variant}")?;
}
f.write_str(">")
}
}
}
}
pub fn type_of(prop: &ElementProperty) -> Option<ValueType> {
match prop {
ElementProperty::Null => Some(ValueType::Null),
ElementProperty::Boolean(_) => Some(ValueType::Boolean),
ElementProperty::Number(n) => Some(if n.is_i64() {
ValueType::Int64
} else {
ValueType::Float64
}),
ElementProperty::String(_) | ElementProperty::Compressed(_) => Some(ValueType::String),
ElementProperty::Uuid(_) => Some(ValueType::Uuid),
ElementProperty::Array(arr) => {
let mut variants = Vec::new();
for item in arr.iter() {
flatten_union(&mut variants, type_of(item)?);
}
Some(ValueType::Array(Box::new(if variants.len() == 1 {
variants.swap_remove(0)
} else {
ValueType::Union(variants)
})))
}
ElementProperty::Object(map) => {
for value in map.values() {
type_of(value)?;
}
Some(ValueType::Object)
}
ElementProperty::Map(map) => {
for value in map.values() {
type_of(value)?;
}
Some(ValueType::Object)
}
ElementProperty::Vertex(_) | ElementProperty::Edge(_) => None,
}
}
pub(crate) fn value_type_name(value: &ElementProperty) -> String {
if let Some(ty) = type_of(value) {
return ty.to_string();
}
match value {
ElementProperty::Vertex(_) => "vertex".to_string(),
ElementProperty::Edge(_) => "edge".to_string(),
ElementProperty::Array(_) => "array".to_string(),
_ => "object".to_string(),
}
}
fn flatten_union(out: &mut Vec<ValueType>, ty: ValueType) {
match ty {
ValueType::Union(inner) => {
for item in inner {
flatten_union(out, item);
}
}
other => {
if !out.contains(&other) {
out.push(other);
}
}
}
}
pub(crate) const PATTERN_SIZE_LIMIT: usize = 1 << 20;
#[derive(Clone)]
pub struct SchemaPattern {
regex: regex::Regex,
}
impl SchemaPattern {
pub fn new(pattern: impl AsRef<str>) -> Result<Self, crate::error::SchemaError> {
let pattern = pattern.as_ref();
regex::RegexBuilder::new(pattern)
.size_limit(PATTERN_SIZE_LIMIT)
.build()
.map(|regex| Self { regex })
.map_err(|err| crate::error::SchemaError::InvalidPattern {
pattern: pattern.to_string(),
reason: pattern_error_reason(&err),
})
}
pub fn as_str(&self) -> &str {
self.regex.as_str()
}
pub fn is_match(&self, value: &str) -> bool {
self.regex.is_match(value)
}
}
pub(crate) fn pattern_error_reason(err: ®ex::Error) -> String {
match err {
regex::Error::Syntax(text) => text
.lines()
.rev()
.find_map(|line| line.trim().strip_prefix("error: "))
.unwrap_or(text.as_str())
.to_string(),
regex::Error::CompiledTooBig(limit) => {
format!("the compiled expression exceeds the size limit of {limit} bytes")
}
other => other.to_string(),
}
}
impl PartialEq for SchemaPattern {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl fmt::Debug for SchemaPattern {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("SchemaPattern")
.field(&self.as_str())
.finish()
}
}
impl fmt::Display for SchemaPattern {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}