1use serde::{Deserialize, Serialize};
17use std::fmt;
18use thiserror::Error;
19
20const ARRAY_TAG: &str = "array";
22const LIST_TAG: &str = "list";
24const MAP_TAG: &str = "map";
26const RECORD_TAG: &str = "record";
28
29#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum DataType {
43 Named(String),
46 Array(Box<DataType>),
47 List(Box<DataType>),
48 Map(Box<DataType>),
50 Record(Vec<RecordField>),
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct RecordField {
60 pub name: String,
61 pub r#type: DataType,
62 pub nullable: bool,
63}
64
65impl DataType {
66 pub fn named(name: impl Into<String>) -> Self {
68 DataType::Named(name.into())
69 }
70
71 pub fn contains_record(&self) -> bool {
76 match self {
77 DataType::Named(_) => false,
78 DataType::Array(inner) | DataType::List(inner) | DataType::Map(inner) => {
79 inner.contains_record()
80 }
81 DataType::Record(_) => true,
82 }
83 }
84
85 pub fn is_pseudo_token(&self) -> bool {
91 match self {
92 DataType::Named(name) => name == RECORD_TAG || name == LIST_TAG || name == MAP_TAG,
93 _ => false,
94 }
95 }
96
97 pub fn contains_pseudo_token(&self) -> bool {
99 match self {
100 DataType::Named(_) => self.is_pseudo_token(),
101 DataType::Array(inner) | DataType::List(inner) | DataType::Map(inner) => {
102 inner.contains_pseudo_token()
103 }
104 DataType::Record(fields) => fields.iter().any(|f| f.r#type.contains_pseudo_token()),
105 }
106 }
107}
108
109impl fmt::Display for DataType {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 match self {
117 DataType::Named(name) => f.write_str(name),
118 DataType::Array(inner) => write!(f, "{}[]", inner),
119 DataType::List(inner) => write!(f, "{} list", inner),
120 DataType::Map(value) => write!(f, "map[text=>{}]", value),
121 DataType::Record(fields) => {
122 f.write_str("record(")?;
123 for (i, field) in fields.iter().enumerate() {
124 if i > 0 {
125 f.write_str(",")?;
126 }
127 write!(f, "{}: {}", field.name, field.r#type)?;
128 if field.nullable {
129 f.write_str("?")?;
130 }
131 }
132 f.write_str(")")
133 }
134 }
135 }
136}
137
138impl DataType {
139 pub(crate) fn to_json(&self) -> String {
141 serde_json::to_string(&TypeLock::from_data_type(self))
142 .expect("TypeLock is always serializable")
143 }
144
145 pub(crate) fn from_json(json: &str) -> Result<DataType, DataTypeJsonError> {
147 serde_json::from_str::<TypeLock>(json)?
148 .into_data_type()
149 .map_err(DataTypeJsonError::Structure)
150 }
151}
152
153#[derive(Error, Debug)]
155pub(crate) enum DataTypeJsonError {
156 #[error("malformed stored type")]
157 Malformed(#[from] serde_json::Error),
158 #[error(transparent)]
159 Structure(TypeLockError),
160}
161
162#[derive(Error, Debug)]
164pub enum TypeLockError {
165 #[error("type `{tag}` does not take an element type")]
166 UnexpectedElement { tag: String },
167 #[error("type `{tag}` requires an element type")]
168 MissingElement { tag: String },
169 #[error("type `{tag}` does not take fields")]
170 UnexpectedFields { tag: String },
171}
172
173#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
180pub(crate) struct TypeLock {
181 #[serde(rename = "type")]
182 pub name: String,
183 #[serde(default, skip_serializing_if = "Option::is_none")]
184 pub of: Option<Box<TypeLock>>,
185 #[serde(default, skip_serializing_if = "Vec::is_empty")]
186 pub fields: Vec<FieldLock>,
187}
188
189#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
192pub(crate) struct FieldLock {
193 pub name: String,
194 #[serde(rename = "type")]
195 pub type_name: String,
196 #[serde(default, skip_serializing_if = "Option::is_none")]
197 pub of: Option<Box<TypeLock>>,
198 #[serde(default, skip_serializing_if = "Vec::is_empty")]
199 pub fields: Vec<FieldLock>,
200 pub nullable: bool,
201}
202
203impl TypeLock {
204 pub(crate) fn from_data_type(r#type: &DataType) -> Self {
205 let (name, of, fields) = split_data_type(r#type);
206 TypeLock { name, of, fields }
207 }
208
209 pub(crate) fn into_data_type(self) -> Result<DataType, TypeLockError> {
210 join_data_type(self.name, self.of, self.fields)
211 }
212}
213
214impl FieldLock {
215 pub(crate) fn from_record_field(field: &RecordField) -> Self {
216 let (type_name, of, fields) = split_data_type(&field.r#type);
217 FieldLock {
218 name: field.name.clone(),
219 type_name,
220 of,
221 fields,
222 nullable: field.nullable,
223 }
224 }
225
226 pub(crate) fn into_record_field(self) -> Result<RecordField, TypeLockError> {
227 Ok(RecordField {
228 name: self.name,
229 r#type: join_data_type(self.type_name, self.of, self.fields)?,
230 nullable: self.nullable,
231 })
232 }
233}
234
235pub(crate) fn split_data_type(
240 r#type: &DataType,
241) -> (String, Option<Box<TypeLock>>, Vec<FieldLock>) {
242 match r#type {
243 DataType::Named(name) => (name.clone(), None, Vec::new()),
244 DataType::Array(inner) => (
245 ARRAY_TAG.into(),
246 Some(Box::new(TypeLock::from_data_type(inner))),
247 Vec::new(),
248 ),
249 DataType::List(inner) => (
250 LIST_TAG.into(),
251 Some(Box::new(TypeLock::from_data_type(inner))),
252 Vec::new(),
253 ),
254 DataType::Map(value) => (
255 MAP_TAG.into(),
256 Some(Box::new(TypeLock::from_data_type(value))),
257 Vec::new(),
258 ),
259 DataType::Record(fields) => (
260 RECORD_TAG.into(),
261 None,
262 fields.iter().map(FieldLock::from_record_field).collect(),
263 ),
264 }
265}
266
267pub(crate) fn join_data_type(
270 name: String,
271 of: Option<Box<TypeLock>>,
272 fields: Vec<FieldLock>,
273) -> Result<DataType, TypeLockError> {
274 enum Tag {
275 Array,
276 List,
277 Map,
278 Record,
279 Named,
280 }
281
282 let tag = match name.as_str() {
283 ARRAY_TAG => Tag::Array,
284 LIST_TAG => Tag::List,
285 MAP_TAG => Tag::Map,
286 RECORD_TAG => Tag::Record,
287 _ => Tag::Named,
288 };
289
290 let takes_element = matches!(tag, Tag::Array | Tag::List | Tag::Map);
291 if of.is_some() && !takes_element {
292 return Err(TypeLockError::UnexpectedElement { tag: name });
293 }
294 if !fields.is_empty() && !matches!(tag, Tag::Record) {
295 return Err(TypeLockError::UnexpectedFields { tag: name });
296 }
297
298 match tag {
304 Tag::List | Tag::Map if of.is_none() => return Ok(DataType::Named(name)),
305 Tag::Record if fields.is_empty() => return Ok(DataType::Named(name)),
306 _ => {}
307 }
308
309 let element = match of {
310 Some(of) => Some(Box::new(of.into_data_type()?)),
311 None if takes_element => return Err(TypeLockError::MissingElement { tag: name }),
312 None => None,
313 };
314
315 Ok(match tag {
316 Tag::Array => DataType::Array(element.expect("array element")),
319 Tag::List => DataType::List(element.expect("list element")),
320 Tag::Map => DataType::Map(element.expect("map value")),
321 Tag::Record => DataType::Record(
322 fields
323 .into_iter()
324 .map(FieldLock::into_record_field)
325 .collect::<Result<_, _>>()?,
326 ),
327 Tag::Named => DataType::Named(name),
328 })
329}