use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Schema {
#[serde(default)]
pub fields: Vec<Field>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Field {
pub name: String,
#[serde(rename = "type")]
pub type_name: String,
pub nullable: bool,
}
pub const PRIMITIVE_TYPES: &[&str] = &[
"boolean", "integer", "decimal", "string", "binary", "date", "time", "datetime", "duration",
];
pub const COMPOSITE_TYPES: &[&str] = &["list", "map", "object", "tuple"];
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LogicalType {
Primitive(String),
Composite {
kind: String,
params: Vec<String>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TypeParseError {
Unknown(String),
BareComposite(String),
Malformed(String),
UnknownParameter(String),
}
pub fn parse_logical_type(type_name: &str) -> Result<LogicalType, TypeParseError> {
let type_name = type_name.trim();
if type_name.is_empty() {
return Err(TypeParseError::Malformed("type is empty".into()));
}
if PRIMITIVE_TYPES.contains(&type_name) {
return Ok(LogicalType::Primitive(type_name.to_string()));
}
if COMPOSITE_TYPES.contains(&type_name) {
return Err(TypeParseError::BareComposite(type_name.to_string()));
}
if let Some(open) = type_name.find('<') {
let kind = type_name[..open].trim();
if !COMPOSITE_TYPES.contains(&kind) {
return Err(TypeParseError::Unknown(kind.to_string()));
}
let Some(close) = type_name.rfind('>') else {
return Err(TypeParseError::Malformed(type_name.to_string()));
};
if close <= open {
return Err(TypeParseError::Malformed(type_name.to_string()));
}
let inner = type_name[open + 1..close].trim();
if inner.is_empty() {
return Err(TypeParseError::Malformed(type_name.to_string()));
}
let params: Vec<String> = inner
.split(',')
.map(str::trim)
.filter(|part| !part.is_empty())
.map(str::to_string)
.collect();
if params.is_empty() {
return Err(TypeParseError::Malformed(type_name.to_string()));
}
for param in ¶ms {
if parse_logical_type(param).is_err() {
return Err(TypeParseError::UnknownParameter(param.clone()));
}
}
return Ok(LogicalType::Composite {
kind: kind.to_string(),
params,
});
}
Err(TypeParseError::Unknown(type_name.to_string()))
}
#[must_use]
pub fn is_known_logical_type(type_name: &str) -> bool {
parse_logical_type(type_name).is_ok()
}