1use serde::{Deserialize, Serialize};
7use std::fmt;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[non_exhaustive]
12pub enum DataType {
13 Float64,
15 Float32,
17 Int64,
19 Bool,
21 Utf8,
23 Bytes,
25 Json,
27 Messages,
32}
33
34impl DataType {
35 pub fn can_coerce_to(&self, target: &DataType) -> bool {
56 use DataType::*;
57
58 if self == target {
59 return true;
60 }
61 if matches!(self, Json) || matches!(target, Json) {
63 return true;
64 }
65 if self.is_numeric() && target.is_numeric() {
66 return true;
67 }
68 matches!((self, target), (Utf8, Messages) | (Messages, Utf8))
69 }
70
71 pub fn is_numeric(&self) -> bool {
74 matches!(
75 self,
76 Self::Float64 | Self::Float32 | Self::Int64 | Self::Bool
77 )
78 }
79}
80
81impl fmt::Display for DataType {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 match self {
84 Self::Float64 => write!(f, "f64"),
85 Self::Float32 => write!(f, "f32"),
86 Self::Int64 => write!(f, "i64"),
87 Self::Bool => write!(f, "bool"),
88 Self::Utf8 => write!(f, "str"),
89 Self::Bytes => write!(f, "bytes"),
90 Self::Json => write!(f, "json"),
91 Self::Messages => write!(f, "messages"),
92 }
93 }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104pub struct Schema {
105 pub dtype: DataType,
107
108 pub shape: Option<Vec<Dimension>>,
111}
112
113#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
115pub enum Dimension {
116 Fixed(usize),
118 Dynamic(String),
120}
121
122impl fmt::Display for Dimension {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 match self {
125 Self::Fixed(n) => write!(f, "{n}"),
126 Self::Dynamic(name) => write!(f, "{name}"),
127 }
128 }
129}
130
131impl Schema {
132 pub fn vector(dtype: DataType, len: usize) -> Self {
134 Self {
135 dtype,
136 shape: Some(vec![Dimension::Fixed(len)]),
137 }
138 }
139
140 pub fn matrix(dtype: DataType, rows: usize, cols: usize) -> Self {
142 Self {
143 dtype,
144 shape: Some(vec![Dimension::Fixed(rows), Dimension::Fixed(cols)]),
145 }
146 }
147
148 pub fn batched(dtype: DataType, feature_dims: &[usize]) -> Self {
150 let mut dims = vec![Dimension::Dynamic("batch".into())];
151 dims.extend(feature_dims.iter().map(|&d| Dimension::Fixed(d)));
152 Self {
153 dtype,
154 shape: Some(dims),
155 }
156 }
157
158 pub fn scalar(dtype: DataType) -> Self {
160 Self {
161 dtype,
162 shape: Some(vec![]),
163 }
164 }
165
166 pub fn json() -> Self {
168 Self {
169 dtype: DataType::Json,
170 shape: None,
171 }
172 }
173
174 pub fn text() -> Self {
180 Self {
181 dtype: DataType::Utf8,
182 shape: None,
183 }
184 }
185
186 pub fn messages() -> Self {
188 Self {
189 dtype: DataType::Messages,
190 shape: None,
191 }
192 }
193
194 pub fn bytes() -> Self {
196 Self {
197 dtype: DataType::Bytes,
198 shape: None,
199 }
200 }
201
202 pub fn dynamic(dtype: DataType) -> Self {
204 Self { dtype, shape: None }
205 }
206
207 pub fn is_incompatible_with(&self, other: &Schema) -> bool {
214 !self.dtype.can_coerce_to(&other.dtype)
215 }
216
217 pub fn is_compatible_with(&self, other: &Schema) -> bool {
225 if self.dtype != other.dtype {
226 return false;
227 }
228
229 match (&self.shape, &other.shape) {
230 (None, _) | (_, None) => true, (Some(a), Some(b)) => {
232 if a.len() != b.len() {
233 return false;
234 }
235 a.iter().zip(b.iter()).all(|(da, db)| match (da, db) {
236 (Dimension::Fixed(x), Dimension::Fixed(y)) => x == y,
237 _ => true, })
239 }
240 }
241 }
242
243 pub fn rank(&self) -> Option<usize> {
245 self.shape.as_ref().map(|s| s.len())
246 }
247}
248
249impl fmt::Display for Schema {
250 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251 write!(f, "{}", self.dtype)?;
252 if let Some(shape) = &self.shape {
253 if shape.is_empty() {
254 write!(f, " (scalar)")?;
255 } else {
256 let dims: Vec<String> = shape.iter().map(|d| d.to_string()).collect();
257 write!(f, "[{}]", dims.join(", "))?;
258 }
259 }
260 Ok(())
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn schema_display() {
270 assert_eq!(
271 Schema::scalar(DataType::Float64).to_string(),
272 "f64 (scalar)"
273 );
274 assert_eq!(
275 Schema::vector(DataType::Float64, 128).to_string(),
276 "f64[128]"
277 );
278 assert_eq!(
279 Schema::matrix(DataType::Float64, 100, 50).to_string(),
280 "f64[100, 50]"
281 );
282 assert_eq!(
283 Schema::batched(DataType::Float32, &[128]).to_string(),
284 "f32[batch, 128]"
285 );
286 assert_eq!(Schema::json().to_string(), "json");
287 }
288
289 #[test]
290 fn compatible_same_schema() {
291 let s = Schema::vector(DataType::Float64, 128);
292 assert!(s.is_compatible_with(&s));
293 }
294
295 #[test]
296 fn compatible_dynamic_with_fixed() {
297 let dynamic = Schema::batched(DataType::Float64, &[128]);
298 let fixed = Schema::matrix(DataType::Float64, 32, 128);
299 assert!(dynamic.is_compatible_with(&fixed));
300 assert!(fixed.is_compatible_with(&dynamic));
301 }
302
303 #[test]
304 fn compatible_unknown_shape() {
305 let unknown = Schema::dynamic(DataType::Float64);
306 let known = Schema::vector(DataType::Float64, 128);
307 assert!(unknown.is_compatible_with(&known));
308 assert!(known.is_compatible_with(&unknown));
309 }
310
311 #[test]
312 fn incompatible_different_dtype() {
313 let f64_schema = Schema::vector(DataType::Float64, 128);
314 let i64_schema = Schema::vector(DataType::Int64, 128);
315 assert!(!f64_schema.is_compatible_with(&i64_schema));
316 }
317
318 #[test]
319 fn incompatible_different_fixed_dims() {
320 let a = Schema::vector(DataType::Float64, 128);
321 let b = Schema::vector(DataType::Float64, 256);
322 assert!(!a.is_compatible_with(&b));
323 }
324
325 #[test]
326 fn incompatible_different_rank() {
327 let vec = Schema::vector(DataType::Float64, 128);
328 let mat = Schema::matrix(DataType::Float64, 128, 64);
329 assert!(!vec.is_compatible_with(&mat));
330 }
331
332 #[test]
333 fn json_compatible_with_json() {
334 assert!(Schema::json().is_compatible_with(&Schema::json()));
335 }
336
337 #[test]
338 fn json_incompatible_with_tensor() {
339 assert!(!Schema::json().is_compatible_with(&Schema::vector(DataType::Float64, 10)));
340 }
341
342 #[test]
343 fn serde_roundtrip() {
344 let schemas = vec![
345 Schema::scalar(DataType::Float64),
346 Schema::vector(DataType::Float32, 100),
347 Schema::batched(DataType::Float64, &[128, 64]),
348 Schema::json(),
349 Schema::dynamic(DataType::Int64),
350 ];
351 for s in schemas {
352 let json = serde_json::to_string(&s).unwrap();
353 let deserialized: Schema = serde_json::from_str(&json).unwrap();
354 assert_eq!(s, deserialized);
355 }
356 }
357
358 #[test]
359 fn rank() {
360 assert_eq!(Schema::scalar(DataType::Float64).rank(), Some(0));
361 assert_eq!(Schema::vector(DataType::Float64, 10).rank(), Some(1));
362 assert_eq!(Schema::matrix(DataType::Float64, 10, 5).rank(), Some(2));
363 assert_eq!(Schema::json().rank(), None);
364 }
365}