1use crate::{Node, PackId, Relation, StateError};
2use serde::{Deserialize, Serialize};
3use serde_json::Value as JsonValue;
4use std::collections::{BTreeMap, BTreeSet};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7pub struct Pack {
8 pub id: PackId,
9 pub name: String,
10 pub version: String,
11 pub object_types: BTreeMap<String, ObjectType>,
12 pub relation_types: BTreeMap<String, RelationType>,
13 pub policies: Vec<String>,
14 pub prompts: Vec<String>,
15 pub settings: JsonValue,
16}
17
18impl Pack {
19 pub fn new(id: PackId, name: impl Into<String>, version: impl Into<String>) -> Self {
20 Self {
21 id,
22 name: name.into(),
23 version: version.into(),
24 object_types: BTreeMap::new(),
25 relation_types: BTreeMap::new(),
26 policies: Vec::new(),
27 prompts: Vec::new(),
28 settings: JsonValue::Object(Default::default()),
29 }
30 }
31
32 pub fn add_object_type(mut self, object_type: ObjectType) -> Self {
33 self.object_types
34 .insert(object_type.kind.clone(), object_type);
35 self
36 }
37
38 pub fn add_relation_type(mut self, relation_type: RelationType) -> Self {
39 self.relation_types
40 .insert(relation_type.rel.clone(), relation_type);
41 self
42 }
43
44 pub fn validate_node(&self, node: &Node) -> Result<(), StateError> {
45 let Some(object_type) = self.object_types.get(&node.kind) else {
46 return Ok(());
47 };
48
49 for required in &object_type.required_props {
50 if node.props.get(required).is_none_or(|value| value.is_null()) {
51 return Err(StateError::Validation(format!(
52 "node {} of kind {} is missing required prop {}",
53 node.id, node.kind, required
54 )));
55 }
56 }
57
58 Ok(())
59 }
60
61 pub fn validate_relation(
62 &self,
63 relation: &Relation,
64 from: &Node,
65 to: &Node,
66 ) -> Result<(), StateError> {
67 let Some(relation_type) = self.relation_types.get(&relation.rel) else {
68 return Ok(());
69 };
70
71 if !relation_type.from_kinds.is_empty() && !relation_type.from_kinds.contains(&from.kind) {
72 return Err(StateError::Validation(format!(
73 "relation {} cannot start from kind {}",
74 relation.rel, from.kind
75 )));
76 }
77
78 if !relation_type.to_kinds.is_empty() && !relation_type.to_kinds.contains(&to.kind) {
79 return Err(StateError::Validation(format!(
80 "relation {} cannot point to kind {}",
81 relation.rel, to.kind
82 )));
83 }
84
85 Ok(())
86 }
87}
88
89#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
90pub struct ObjectType {
91 pub kind: String,
92 pub required_props: BTreeSet<String>,
93 pub description: Option<String>,
94}
95
96impl ObjectType {
97 pub fn new(kind: impl Into<String>) -> Self {
98 Self {
99 kind: kind.into(),
100 required_props: BTreeSet::new(),
101 description: None,
102 }
103 }
104
105 pub fn require(mut self, prop: impl Into<String>) -> Self {
106 self.required_props.insert(prop.into());
107 self
108 }
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
112pub struct RelationType {
113 pub rel: String,
114 pub from_kinds: BTreeSet<String>,
115 pub to_kinds: BTreeSet<String>,
116 pub description: Option<String>,
117}
118
119impl RelationType {
120 pub fn new(rel: impl Into<String>) -> Self {
121 Self {
122 rel: rel.into(),
123 from_kinds: BTreeSet::new(),
124 to_kinds: BTreeSet::new(),
125 description: None,
126 }
127 }
128
129 pub fn from_kind(mut self, kind: impl Into<String>) -> Self {
130 self.from_kinds.insert(kind.into());
131 self
132 }
133
134 pub fn to_kind(mut self, kind: impl Into<String>) -> Self {
135 self.to_kinds.insert(kind.into());
136 self
137 }
138}