1use std::collections::{BTreeMap, BTreeSet};
16use std::fmt::Debug;
17use std::ops::{AddAssign, SubAssign};
18
19use uuid::Uuid;
20
21use crate::transaction::{Transaction, TransactionMode};
22
23pub trait Field: Debug + Clone + Ord + PartialEq {}
27impl<T: Debug + Clone + Ord + PartialEq> Field for T {}
28
29pub trait WeightValue:
31 Debug
32 + Clone
33 + PartialEq
34 + PartialOrd
35 + AddAssign
36 + SubAssign
37 + num_traits::Signed
38 + num_traits::AsPrimitive<f64>
39{
40}
41impl<
42 T: Debug
43 + Clone
44 + PartialEq
45 + PartialOrd
46 + AddAssign
47 + SubAssign
48 + num_traits::Signed
49 + num_traits::AsPrimitive<f64>,
50> WeightValue for T
51{
52}
53
54pub trait AnnotationValue: Debug + Clone + PartialEq {}
56impl<T: Debug + Clone + PartialEq> AnnotationValue for T {}
57
58pub type SimpleMetadata = Metadata<String, String, String, String, f64, String, serde_json::Value>;
60
61#[derive(Clone, Debug, PartialEq, bon::Builder)]
63#[cfg_attr(
64 feature = "serde",
65 derive(serde::Serialize, serde::Deserialize),
66 serde(default, rename_all = "camelCase")
67)]
68#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
69#[cfg_attr(feature = "reactive", derive(reactive_stores::Store))]
70#[builder(on(String, into))]
71pub struct Metadata<N, K, L, W, WV, A, AV>
72where
73 N: Field,
74 K: Field,
75 L: Field,
76 W: Field,
77 WV: WeightValue,
78 A: Field,
79 AV: AnnotationValue,
80{
81 #[builder(default=Uuid::new_v4())]
83 pub id: Uuid,
84
85 #[builder(into)]
87 pub name: Option<N>,
88
89 #[builder(into)]
91 pub kind: Option<K>,
92
93 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "BTreeSet::is_empty"))]
95 #[builder(default=BTreeSet::new())]
96 pub labels: BTreeSet<L>,
97
98 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "BTreeMap::is_empty"))]
100 #[builder(default=BTreeMap::new())]
101 pub weights: BTreeMap<W, WV>,
102
103 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "BTreeMap::is_empty"))]
105 #[builder(default=BTreeMap::new())]
106 pub annotations: BTreeMap<A, AV>,
107}
108
109impl<N, K, L, W, WV, A, AV> Default for Metadata<N, K, L, W, WV, A, AV>
110where
111 N: Field,
112 K: Field,
113 L: Field,
114 W: Field,
115 WV: WeightValue,
116 A: Field,
117 AV: AnnotationValue,
118{
119 fn default() -> Self {
120 Self {
121 id: Uuid::new_v4(),
122 name: Default::default(),
123 kind: Default::default(),
124 labels: Default::default(),
125 weights: Default::default(),
126 annotations: Default::default(),
127 }
128 }
129}
130
131impl<'a, N, K, L, W, WV, A, AV> ReadMetadata<'a, N, K, L, W, WV, A, AV>
132 for Metadata<N, K, L, W, WV, A, AV>
133where
134 N: Field,
135 K: Field,
136 L: Field,
137 W: Field,
138 WV: WeightValue,
139 A: Field,
140 AV: AnnotationValue,
141{
142 fn id(&'a self) -> &'a Uuid {
143 &self.id
144 }
145
146 fn name(&'a self) -> Option<&'a N> {
147 self.name.as_ref()
148 }
149
150 fn kind(&'a self) -> Option<&'a K> {
151 self.kind.as_ref()
152 }
153
154 fn labels(&'a self) -> impl Iterator<Item = &'a L>
155 where
156 L: 'a,
157 {
158 self.labels.iter()
159 }
160
161 fn weights(&'a self) -> impl Iterator<Item = (&'a W, &'a WV)>
162 where
163 W: 'a,
164 WV: 'a,
165 {
166 self.weights.iter()
167 }
168
169 fn annotations(&'a self) -> impl Iterator<Item = (&'a A, &'a AV)>
170 where
171 A: 'a,
172 AV: 'a,
173 {
174 self.annotations.iter()
175 }
176}
177
178impl<N, K, L, W, WV, A, AV> Metadata<N, K, L, W, WV, A, AV>
179where
180 N: Field,
181 K: Field,
182 L: Field,
183 W: Field,
184 WV: WeightValue,
185 A: Field,
186 AV: AnnotationValue,
187{
188 pub fn as_replace_transaction(self) -> Transaction<N, K, L, W, WV, A, AV> {
190 let Metadata {
191 id,
192 name,
193 kind,
194 labels,
195 weights,
196 annotations,
197 } = self;
198 Transaction::builder()
199 .mode(TransactionMode::Replace)
200 .id(id)
201 .maybe_name(name)
202 .maybe_kind(kind)
203 .labels(labels)
204 .weights(weights)
205 .annotations(annotations)
206 .build()
207 }
208
209 pub fn as_append_transaction(self) -> Transaction<N, K, L, W, WV, A, AV> {
211 let Metadata {
212 id,
213 name,
214 kind,
215 labels,
216 weights,
217 annotations,
218 } = self;
219 Transaction::builder()
220 .mode(TransactionMode::Append)
221 .id(id)
222 .maybe_name(name)
223 .maybe_kind(kind)
224 .labels(labels)
225 .weights(weights)
226 .annotations(annotations)
227 .build()
228 }
229}
230
231impl<N, K, L, W, WV, A, AV> Metadata<N, K, L, W, WV, A, AV>
232where
233 N: Field,
234 K: Field,
235 L: Field,
236 W: Field,
237 WV: WeightValue,
238 A: Field,
239 AV: AnnotationValue,
240{
241 pub fn as_ref<'a>(&'a self) -> MetadataRef<'a, N, K, L, W, WV, A, AV> {
243 MetadataRef::builder()
244 .id(self.id)
245 .maybe_name(self.name.as_ref())
246 .maybe_kind(self.kind.as_ref())
247 .labels(self.labels.iter().collect())
248 .weights(self.weights.iter().collect())
249 .annotations(self.annotations.iter().collect())
250 .build()
251 }
252}
253
254#[derive(Clone, Debug, PartialEq, bon::Builder)]
256pub struct MetadataRef<'a, N, K, L, W, WV, A, AV>
257where
258 N: Field,
259 K: Field,
260 L: Field,
261 W: Field,
262 WV: WeightValue,
263 A: Field,
264 AV: AnnotationValue,
265{
266 pub id: Uuid,
268
269 pub name: Option<&'a N>,
271
272 pub kind: Option<&'a K>,
274
275 #[builder(default=BTreeSet::new())]
277 pub labels: BTreeSet<&'a L>,
278
279 #[builder(default=BTreeMap::new())]
281 pub weights: BTreeMap<&'a W, &'a WV>,
282
283 #[builder(default=BTreeMap::new())]
285 pub annotations: BTreeMap<&'a A, &'a AV>,
286}
287
288pub type SimpleMetadataRef<'a> =
291 MetadataRef<'a, String, String, String, String, f64, String, serde_json::Value>;
292
293impl<'a, N, K, L, W, WV, A, AV> MetadataRef<'a, N, K, L, W, WV, A, AV>
294where
295 N: Field,
296 K: Field,
297 L: Field,
298 W: Field,
299 WV: WeightValue,
300 A: Field,
301 AV: AnnotationValue,
302{
303 pub fn cloned(&self) -> Metadata<N, K, L, W, WV, A, AV> {
305 Metadata {
306 id: self.id,
307 name: self.name.cloned(),
308 kind: self.kind.cloned(),
309 labels: self.labels.iter().map(|&label| label.to_owned()).collect(),
310 weights: self
311 .weights
312 .iter()
313 .map(|(&k, &v)| (k.to_owned(), v.to_owned()))
314 .collect(),
315 annotations: self
316 .annotations
317 .iter()
318 .map(|(&k, &v)| (k.to_owned(), v.to_owned()))
319 .collect(),
320 }
321 }
322}
323
324impl<'a, N, K, L, W, WV, A, AV> ReadMetadata<'a, N, K, L, W, WV, A, AV>
325 for MetadataRef<'a, N, K, L, W, WV, A, AV>
326where
327 N: Field,
328 K: Field,
329 L: Field,
330 W: Field,
331 WV: WeightValue,
332 A: Field,
333 AV: AnnotationValue,
334{
335 fn id(&'a self) -> &'a Uuid {
336 &self.id
337 }
338
339 fn name(&'a self) -> Option<&'a N> {
340 self.name
341 }
342
343 fn kind(&'a self) -> Option<&'a K> {
344 self.kind
345 }
346
347 fn labels(&'a self) -> impl Iterator<Item = &'a L>
348 where
349 L: 'a,
350 {
351 self.labels.iter().copied()
352 }
353
354 fn weights(&'a self) -> impl Iterator<Item = (&'a W, &'a WV)>
355 where
356 W: 'a,
357 WV: 'a,
358 {
359 self.weights.clone().into_iter()
360 }
361
362 fn annotations(&'a self) -> impl Iterator<Item = (&'a A, &'a AV)>
363 where
364 A: 'a,
365 AV: 'a,
366 {
367 self.annotations.clone().into_iter()
368 }
369}
370
371pub trait ReadMetadata<'a, N, K, L, W, WV, A, AV>
373where
374 N: Field,
375 K: Field,
376 L: Field,
377 W: Field,
378 WV: WeightValue,
379 A: Field,
380 AV: AnnotationValue,
381{
382 fn id(&'a self) -> &'a Uuid;
384
385 fn name(&'a self) -> Option<&'a N>;
387
388 fn kind(&'a self) -> Option<&'a K>;
390
391 fn labels(&'a self) -> impl Iterator<Item = &'a L>
393 where
394 L: 'a;
395
396 fn weights(&'a self) -> impl Iterator<Item = (&'a W, &'a WV)>
398 where
399 W: 'a,
400 WV: 'a;
401
402 fn annotations(&'a self) -> impl Iterator<Item = (&'a A, &'a AV)>
404 where
405 A: 'a,
406 AV: 'a;
407}
408
409#[cfg(test)]
410pub mod tests {
411 #[allow(unused_imports)]
412 use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
413 use serde_json::json;
414
415 #[allow(unused_imports)]
416 use super::*;
417
418 pub type SimpleTransaction =
419 Transaction<String, String, String, String, f64, String, serde_json::Value>;
420
421 #[derive(
422 Copy,
423 Clone,
424 Debug,
425 Default,
426 PartialEq,
427 PartialOrd,
428 Eq,
429 Ord,
430 strum::AsRefStr,
431 strum::Display,
432 strum::EnumIter,
433 strum::EnumString,
434 strum::FromRepr,
435 strum::IntoStaticStr,
436 strum::VariantNames,
437 )]
438 #[cfg_attr(
439 feature = "serde",
440 derive(serde::Serialize, serde::Deserialize),
441 serde(rename_all = "camelCase")
442 )]
443 pub enum N {
444 #[default]
445 Foo,
446 Bar,
447 Baz,
448 Quux,
449 }
450
451 #[derive(
452 Copy,
453 Clone,
454 Debug,
455 Default,
456 PartialEq,
457 PartialOrd,
458 Eq,
459 Ord,
460 strum::AsRefStr,
461 strum::Display,
462 strum::EnumIter,
463 strum::EnumString,
464 strum::FromRepr,
465 strum::IntoStaticStr,
466 strum::VariantNames,
467 )]
468 #[cfg_attr(
469 feature = "serde",
470 derive(serde::Serialize, serde::Deserialize),
471 serde(rename_all = "camelCase")
472 )]
473 pub enum K {
474 #[default]
475 A,
476 B,
477 C,
478 }
479
480 #[derive(
481 Copy,
482 Clone,
483 Debug,
484 Default,
485 PartialEq,
486 PartialOrd,
487 Eq,
488 Ord,
489 strum::AsRefStr,
490 strum::Display,
491 strum::EnumIter,
492 strum::EnumString,
493 strum::FromRepr,
494 strum::IntoStaticStr,
495 strum::VariantNames,
496 )]
497 #[cfg_attr(
498 feature = "serde",
499 derive(serde::Serialize, serde::Deserialize),
500 serde(rename_all = "camelCase")
501 )]
502 pub enum L {
503 #[default]
504 A,
505 B,
506 C,
507 }
508
509 #[derive(
510 Copy,
511 Clone,
512 Debug,
513 Default,
514 PartialEq,
515 PartialOrd,
516 Eq,
517 Ord,
518 strum::AsRefStr,
519 strum::Display,
520 strum::EnumIter,
521 strum::EnumString,
522 strum::FromRepr,
523 strum::IntoStaticStr,
524 strum::VariantNames,
525 )]
526 #[cfg_attr(
527 feature = "serde",
528 derive(serde::Serialize, serde::Deserialize),
529 serde(rename_all = "camelCase")
530 )]
531 pub enum W {
532 #[default]
533 A,
534 B,
535 C,
536 }
537
538 #[derive(
539 Copy,
540 Clone,
541 Debug,
542 Default,
543 PartialEq,
544 PartialOrd,
545 Eq,
546 Ord,
547 strum::AsRefStr,
548 strum::Display,
549 strum::EnumIter,
550 strum::EnumString,
551 strum::FromRepr,
552 strum::IntoStaticStr,
553 strum::VariantNames,
554 )]
555 #[cfg_attr(
556 feature = "serde",
557 derive(serde::Serialize, serde::Deserialize),
558 serde(rename_all = "camelCase")
559 )]
560 pub enum A {
561 #[default]
562 A,
563 B,
564 C,
565 }
566
567 pub type TestMeta = Metadata<N, K, L, W, f64, A, serde_json::Value>;
569 pub type TestRef<'a> = MetadataRef<'a, N, K, L, W, f64, A, serde_json::Value>;
570 pub type TestTx = Transaction<N, K, L, W, f64, A, serde_json::Value>;
571 impl TestMeta {
572 pub fn foo() -> TestMeta {
573 Metadata::builder()
574 .name(N::Foo)
575 .kind(K::A)
576 .labels(bon::set![L::A, L::B])
577 .weights(bon::map![W::A: 1.0])
578 .annotations(bon::map![A::A: "FOO"])
579 .build()
580 }
581
582 pub fn bar() -> TestMeta {
583 Metadata::builder()
584 .name(N::Bar)
585 .kind(K::B)
586 .labels(bon::set![L::B, L::C])
587 .weights(bon::map! {W::B: 2.0})
588 .annotations(bon::map! {A::A: 3.0, A::B: "hello"})
589 .build()
590 }
591
592 pub fn baz() -> TestMeta {
593 Metadata::builder()
594 .name(N::Baz)
595 .kind(K::C)
596 .labels(bon::set![L::C])
597 .weights(bon::map! {W::B: 2.0})
598 .annotations(bon::map! {A::A: 3.0, A::B: "world"})
599 .build()
600 }
601
602 pub fn quux() -> TestMeta {
603 Metadata::builder()
604 .name(N::Quux) .annotations(bon::map! {A::A: "has no kind, labels, or weights"})
606 .build()
607 }
608
609 pub fn foobarbazquux() -> Vec<TestMeta> {
610 vec![Self::foo(), Self::bar(), Self::baz(), Self::quux()]
611 }
612 }
613
614 #[test]
615 fn test_meta_helpers() {
616 let foo = TestMeta::foo();
617
618 assert_eq!(foo.kind, Some(K::A));
619 assert_eq!(foo.labels, BTreeSet::from_iter([L::A, L::B]));
620 assert_eq!(foo.weights, BTreeMap::from_iter([(W::A, 1.0)]));
621 }
622
623 #[test]
624 fn test_tx_helpers() {
625 let mut bar = TestMeta::bar().as_replace_transaction();
626
627 assert_eq!(bar.name, Some(N::Bar));
628 assert_eq!(bar.kind, Some(K::B));
629 assert_eq!(bar.labels, Some(BTreeSet::from_iter([L::B, L::C])));
630 assert_eq!(bar.weights, Some(BTreeMap::from_iter([(W::B, 2.0)])));
631 assert_eq!(
632 bar.annotations,
633 Some(BTreeMap::from_iter([
634 (
635 A::A,
636 serde_json::Value::Number(serde_json::Number::from_f64(3.0).unwrap())
637 ),
638 (A::B, serde_json::Value::String("hello".to_string()))
639 ]))
640 );
641
642 assert!(bar.id.is_some());
643 bar.strip_id();
644 assert!(bar.id.is_none());
645 }
646
647 #[test]
648 fn test_metadata_default() {
649 let metadata: SimpleMetadata = Default::default();
650 assert_eq!(metadata.name, None);
651 assert_eq!(metadata.kind, None);
652 assert!(metadata.labels.is_empty());
653 assert!(metadata.weights.is_empty());
654 assert!(metadata.annotations.is_empty());
655 }
656
657 #[test]
658 fn test_metadata_transaction_default() {
659 let transaction: SimpleTransaction = Default::default();
660 assert_eq!(transaction.id, None);
661 assert_eq!(transaction.name, None);
662 assert_eq!(transaction.kind, None);
663 assert_eq!(transaction.labels, None);
664 assert_eq!(transaction.weights, None);
665 assert_eq!(transaction.annotations, None);
666 }
667
668 #[test]
669 fn test_metadata_serialization() {
670 let metadata = Metadata {
671 id: Uuid::new_v4(),
672 name: Some("TestName".to_string()),
673 kind: Some("TestKind".to_string()),
674 labels: BTreeSet::new(),
675 weights: BTreeMap::new(),
676 annotations: BTreeMap::new(),
677 };
678
679 let serialized = serde_json::to_string(&metadata).unwrap();
680 let deserialized: SimpleMetadata = serde_json::from_str(&serialized).unwrap();
681
682 assert_eq!(metadata, deserialized);
683 }
684
685 #[test]
686 fn test_metadata_transaction_serialization() {
687 let transaction = Transaction {
688 name: Some("TestName".to_string()),
689 ..Default::default()
690 };
691
692 let serialized = serde_json::to_string(&transaction).unwrap();
693 let deserialized: SimpleTransaction = serde_json::from_str(&serialized).unwrap();
694
695 assert_eq!(transaction, deserialized);
696 }
697
698 #[test]
699 fn test_metadata_with_labels_and_weights() {
700 let mut labels = BTreeSet::new();
701 labels.insert("label1".to_string());
702 labels.insert("label2".to_string());
703
704 let mut weights = BTreeMap::new();
705 weights.insert("weight1".to_string(), 1.0);
706 weights.insert("weight2".to_string(), 2.0);
707
708 let metadata: SimpleMetadata = Metadata {
709 name: Some("TestName".to_string()),
710 kind: Some("TestKind".to_string()),
711 labels,
712 weights,
713 ..Default::default()
714 };
715
716 assert!(!metadata.id.is_nil());
717 assert_eq!(metadata.labels.len(), 2);
718 assert_eq!(metadata.weights.len(), 2);
719 }
720
721 #[test]
722 fn test_metadata_transaction_with_annotations() {
723 let mut annotations = BTreeMap::new();
724 annotations.insert("key1".to_string(), json!("value1"));
725 annotations.insert("key2".to_string(), json!("value2"));
726
727 let transaction = Transaction {
728 mode: Default::default(),
729 id: Some(Uuid::new_v4()),
730 name: Some("TestName".to_string()),
731 kind: Some("TestKind".to_string()),
732 labels: None::<BTreeSet<String>>,
733 weights: None::<BTreeMap<String, f64>>,
734 annotations: Some(annotations),
735 };
736
737 assert_eq!(transaction.annotations.as_ref().unwrap().len(), 2);
738 }
739
740 #[test]
741 fn test_metadata_builder() {
742 let metadata = SimpleMetadata::builder()
743 .name("TestName".to_string())
744 .kind("TestKind".to_string())
745 .build();
746
747 assert_eq!(metadata.name, Some("TestName".to_string()));
748 assert_eq!(metadata.kind, Some("TestKind".to_string()));
749 assert!(metadata.labels.is_empty());
750 assert!(metadata.weights.is_empty());
751 assert!(metadata.annotations.is_empty());
752 }
753
754 #[test]
755 fn test_metadata_transaction_builder() {
756 let transaction = SimpleTransaction::builder()
757 .id(Uuid::new_v4())
758 .name("TestName".to_string())
759 .kind("TestKind".to_string())
760 .build();
761
762 assert!(transaction.id.is_some());
763 assert_eq!(transaction.name, Some("TestName".to_string()));
764 assert_eq!(transaction.kind, Some("TestKind".to_string()));
765 assert!(transaction.labels.is_none());
766 assert!(transaction.weights.is_none());
767 assert!(transaction.annotations.is_none());
768 }
769}