1use serde::{Deserialize, Serialize};
2
3use crate::errors::{DataError, DataErrorCode, DataResult};
4use crate::model::{TypeExpr, Value, ValueType};
5
6#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
14pub struct GpuHints {
15 pub requires_gpu: bool,
16 pub preferred_memory: Option<MemoryLocation>,
17}
18
19#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
27pub enum MemoryLocation {
28 Host,
29 Device,
30 Shared,
31}
32
33#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
53pub struct DataDescriptor {
54 pub id: DescriptorId,
55 pub version: DescriptorVersion,
56 pub label: Option<String>,
57 pub settable: bool,
58 pub default: Option<Value>,
59 pub schema: Option<String>,
60 pub codecs: Vec<String>,
61 pub converters: Vec<String>,
62 pub feature_flags: Vec<String>,
63 pub gpu: Option<GpuHints>,
64 pub type_expr: Option<TypeExpr>,
65}
66
67impl DataDescriptor {
68 pub fn validate(&self) -> DataResult<()> {
88 self.id.validate()?;
89 self.version.validate()?;
90 if let Some(default) = &self.default {
91 if self.type_expr.is_none() {
92 return Err(DataError::new(
93 DataErrorCode::InvalidDescriptor,
94 "type_expr is required when default is present",
95 ));
96 }
97 validate_default(self.type_expr.as_ref().unwrap(), default)?;
98 }
99 Ok(())
100 }
101
102 pub fn normalize(mut self) -> Self {
123 self.codecs.sort();
124 self.converters.sort();
125 self.feature_flags.sort();
126 self
127 }
128}
129
130pub struct DescriptorBuilder {
152 inner: DataDescriptor,
153}
154
155impl DescriptorBuilder {
156 pub fn new(id: impl Into<String>, version: impl Into<String>) -> Self {
157 Self {
158 inner: DataDescriptor {
159 id: DescriptorId::new(id.into()),
160 version: DescriptorVersion::new(version.into()),
161 label: None,
162 settable: false,
163 default: None,
164 schema: None,
165 codecs: Vec::new(),
166 converters: Vec::new(),
167 feature_flags: Vec::new(),
168 gpu: None,
169 type_expr: None,
170 },
171 }
172 }
173
174 pub fn label(mut self, label: impl Into<String>) -> Self {
175 self.inner.label = Some(label.into());
176 self
177 }
178
179 pub fn settable(mut self, settable: bool) -> Self {
180 self.inner.settable = settable;
181 self
182 }
183
184 pub fn default(mut self, default: Value) -> Self {
185 self.inner.default = Some(default);
186 self
187 }
188
189 pub fn schema(mut self, schema: impl Into<String>) -> Self {
190 self.inner.schema = Some(schema.into());
191 self
192 }
193
194 pub fn codec(mut self, codec: impl Into<String>) -> Self {
195 self.inner.codecs.push(codec.into());
196 self
197 }
198
199 pub fn converter(mut self, conv: impl Into<String>) -> Self {
200 self.inner.converters.push(conv.into());
201 self
202 }
203
204 pub fn feature_flag(mut self, flag: impl Into<String>) -> Self {
205 self.inner.feature_flags.push(flag.into());
206 self
207 }
208
209 pub fn gpu_hints(mut self, hints: GpuHints) -> Self {
210 self.inner.gpu = Some(hints);
211 self
212 }
213
214 pub fn type_expr(mut self, ty: TypeExpr) -> Self {
215 self.inner.type_expr = Some(ty.normalize());
216 self
217 }
218
219 pub fn build(self) -> DataResult<DataDescriptor> {
220 let desc = self.inner.normalize();
221 desc.validate()?;
222 Ok(desc)
223 }
224}
225
226#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
248pub struct TypeDescriptor {
249 pub ty: TypeExpr,
250 pub descriptor: DataDescriptor,
251}
252
253#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)]
261pub struct DescriptorId(pub String);
262
263impl DescriptorId {
264 pub fn new(id: impl Into<String>) -> Self {
265 Self(id.into())
266 }
267
268 pub fn namespaced(namespace: impl Into<String>, name: impl Into<String>) -> Self {
269 let ns = namespace.into();
270 let name = name.into();
271 if ns.is_empty() {
272 return Self(name);
273 }
274 Self(format!("{ns}.{name}"))
275 }
276 pub fn validate(&self) -> DataResult<()> {
277 if self.0.is_empty() {
278 return Err(DataError::new(
279 DataErrorCode::InvalidDescriptor,
280 "id must not be empty",
281 ));
282 }
283 if !self.0.chars().all(|c| {
284 c.is_ascii_lowercase() || c.is_ascii_digit() || c == '.' || c == '_' || c == '-'
285 }) {
286 return Err(DataError::new(
287 DataErrorCode::InvalidDescriptor,
288 "id must be lowercase/digit/._-",
289 ));
290 }
291 Ok(())
292 }
293}
294
295#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)]
303pub struct DescriptorVersion(pub String);
304
305impl DescriptorVersion {
306 pub fn new(v: impl Into<String>) -> Self {
307 Self(v.into())
308 }
309 pub fn validate(&self) -> DataResult<()> {
310 let parts: Vec<_> = self.0.split('.').collect();
311 if parts.len() < 2 {
312 return Err(DataError::new(
313 DataErrorCode::InvalidDescriptor,
314 "version must be at least major.minor",
315 ));
316 }
317 if parts
318 .iter()
319 .any(|p| p.is_empty() || p.chars().any(|c| !c.is_ascii_digit()))
320 {
321 return Err(DataError::new(
322 DataErrorCode::InvalidDescriptor,
323 "version segments must be numeric",
324 ));
325 }
326 Ok(())
327 }
328}
329
330fn validate_default(ty: &TypeExpr, value: &Value) -> DataResult<()> {
331 match (ty, value) {
332 (TypeExpr::Scalar(ValueType::Unit), Value::Unit) => Ok(()),
333 (TypeExpr::Scalar(ValueType::Bool), Value::Bool(_)) => Ok(()),
334 (TypeExpr::Scalar(ValueType::I32 | ValueType::U32 | ValueType::Int), Value::Int(_)) => {
335 Ok(())
336 }
337 (TypeExpr::Scalar(ValueType::F32 | ValueType::Float), Value::Float(_)) => Ok(()),
338 (TypeExpr::Scalar(ValueType::String), Value::String(_)) => Ok(()),
339 (TypeExpr::Scalar(ValueType::Bytes), Value::Bytes(_)) => Ok(()),
340 (TypeExpr::Optional(inner), v) => validate_default(inner, v),
341 (TypeExpr::List(inner), Value::List(items)) => {
342 for v in items {
343 validate_default(inner, v)?;
344 }
345 Ok(())
346 }
347 (TypeExpr::Map(k_ty, v_ty), Value::Map(entries)) => {
348 for (k, v) in entries {
349 validate_default(k_ty, k)?;
350 validate_default(v_ty, v)?;
351 }
352 Ok(())
353 }
354 (TypeExpr::Tuple(types), Value::Tuple(values)) => {
355 if types.len() != values.len() {
356 return Err(DataError::new(
357 DataErrorCode::InvalidType,
358 "tuple length mismatch",
359 ));
360 }
361 for (t, v) in types.iter().zip(values.iter()) {
362 validate_default(t, v)?;
363 }
364 Ok(())
365 }
366 (TypeExpr::Struct(fields), Value::Struct(values)) => {
367 if fields.len() != values.len() {
368 return Err(DataError::new(
369 DataErrorCode::InvalidType,
370 "struct field count mismatch",
371 ));
372 }
373 for (field, val) in fields.iter().zip(values.iter()) {
374 if field.name != val.name {
375 return Err(DataError::new(
376 DataErrorCode::InvalidType,
377 "struct field name mismatch",
378 ));
379 }
380 validate_default(&field.ty, &val.value)?;
381 }
382 Ok(())
383 }
384 (TypeExpr::Enum(variants), Value::Enum(ev)) => {
385 let variant = variants.iter().find(|v| v.name == ev.name).ok_or_else(|| {
386 DataError::new(DataErrorCode::InvalidType, "enum variant not found")
387 })?;
388 match (&variant.ty, &ev.value) {
389 (None, None) => Ok(()),
390 (Some(t), Some(v)) => validate_default(t, v),
391 (None, Some(_)) | (Some(_), None) => Err(DataError::new(
392 DataErrorCode::InvalidType,
393 "enum payload mismatch",
394 )),
395 }
396 }
397 _ => Err(DataError::new(
398 DataErrorCode::InvalidType,
399 "default does not match type_expr",
400 )),
401 }
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407 use crate::model::{StructField, StructFieldValue};
408
409 #[test]
410 fn normalize_sorts_fields() {
411 let desc = DataDescriptor {
412 id: DescriptorId::new("id"),
413 version: DescriptorVersion::new("v1"),
414 label: None,
415 settable: true,
416 default: None,
417 schema: None,
418 codecs: vec!["b".into(), "a".into()],
419 converters: vec!["y".into(), "x".into()],
420 feature_flags: vec!["f2".into(), "f1".into()],
421 gpu: None,
422 type_expr: None,
423 }
424 .normalize();
425 assert_eq!(desc.codecs, vec!["a", "b"]);
426 assert_eq!(desc.converters, vec!["x", "y"]);
427 assert_eq!(desc.feature_flags, vec!["f1", "f2"]);
428 }
429
430 #[test]
431 fn serde_preserves_sorted_order() {
432 let desc = DescriptorBuilder::new("id", "1.0")
433 .codec("z")
434 .codec("a")
435 .converter("b")
436 .converter("a")
437 .feature_flag("beta")
438 .feature_flag("alpha")
439 .build()
440 .expect("build");
441 let json = serde_json::to_string(&desc).unwrap();
442 assert!(json.find("a").unwrap() < json.find("z").unwrap());
443 assert!(json.find("alpha").unwrap() < json.find("beta").unwrap());
444 }
445
446 #[test]
474 fn registry_fixture_compiles() {
475 }
477
478 #[test]
479 fn golden_descriptor_serialization_is_stable() {
480 let desc = DescriptorBuilder::new("id", "1.0")
481 .label("Example")
482 .settable(true)
483 .codec("json")
484 .converter("int_to_string")
485 .feature_flag("core")
486 .build()
487 .expect("build");
488 let json = serde_json::to_string(&desc).unwrap();
489 assert_eq!(
490 json,
491 r#"{"id":"id","version":"1.0","label":"Example","settable":true,"default":null,"schema":null,"codecs":["json"],"converters":["int_to_string"],"feature_flags":["core"],"gpu":null,"type_expr":null}"#
492 );
493 }
494
495 #[test]
496 fn validates_default_against_type() {
497 let desc = DescriptorBuilder::new("id", "1.0")
498 .type_expr(TypeExpr::Scalar(ValueType::String))
499 .default(Value::String("ok".into()))
500 .build()
501 .unwrap();
502 assert_eq!(desc.id.0, "id");
503
504 let err = DescriptorBuilder::new("id2", "1.0")
505 .type_expr(TypeExpr::Scalar(ValueType::Int))
506 .default(Value::Bool(true))
507 .build()
508 .unwrap_err();
509 assert_eq!(err.code(), DataErrorCode::InvalidType);
510
511 let err = DescriptorBuilder::new("id3", "1.0")
512 .type_expr(TypeExpr::Struct(vec![StructField {
513 name: "a".into(),
514 ty: TypeExpr::Scalar(ValueType::Int),
515 }]))
516 .default(Value::Struct(vec![StructFieldValue {
517 name: "a".into(),
518 value: Value::Int(1),
519 }]))
520 .build()
521 .unwrap();
522 assert_eq!(err.id.0, "id3");
523 }
524}