1use std::collections::{BTreeMap, BTreeSet};
9use std::string::String;
10use std::vec::Vec;
11
12use crate::foundation::{ports_of, PortDir};
13
14use crate::{KnotDef, PortRefDef, ThreadDef, ValidationError, WeaveDef};
15
16#[cfg(feature = "schema")]
17use schemars::JsonSchema;
18#[cfg(feature = "serde")]
19use serde::{Deserialize, Serialize};
20#[cfg(feature = "schema")]
21use std::borrow::ToOwned;
22
23#[derive(Clone, Debug, PartialEq, Eq)]
25#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
26#[cfg_attr(feature = "schema", derive(JsonSchema))]
27pub struct PatternExportDef {
28 pub name: String,
30 pub port: PortRefDef,
32}
33
34impl PatternExportDef {
35 pub fn new(name: impl Into<String>, knot: impl Into<String>, port: impl Into<String>) -> Self {
37 Self {
38 name: name.into(),
39 port: PortRefDef::new(knot, port),
40 }
41 }
42}
43
44#[derive(Clone, Debug, PartialEq)]
46#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
47#[cfg_attr(feature = "schema", derive(JsonSchema))]
48pub struct PatternDef {
49 pub id: String,
51 pub inner: WeaveDef,
53 pub inputs: Vec<PatternExportDef>,
55 pub outputs: Vec<PatternExportDef>,
57}
58
59#[derive(Clone, Debug, PartialEq)]
61pub struct Pattern {
62 id: String,
63 inner: WeaveDef,
64 inputs: Vec<PatternExportDef>,
65 outputs: Vec<PatternExportDef>,
66}
67
68impl Pattern {
69 pub fn id(&self) -> &str {
71 &self.id
72 }
73
74 pub fn inner(&self) -> &WeaveDef {
76 &self.inner
77 }
78
79 pub fn inputs(&self) -> &[PatternExportDef] {
81 &self.inputs
82 }
83
84 pub fn outputs(&self) -> &[PatternExportDef] {
86 &self.outputs
87 }
88
89 pub fn to_def(&self) -> PatternDef {
91 PatternDef {
92 id: self.id.clone(),
93 inner: self.inner.clone(),
94 inputs: self.inputs.clone(),
95 outputs: self.outputs.clone(),
96 }
97 }
98}
99
100impl TryFrom<PatternDef> for Pattern {
101 type Error = ValidationError;
102
103 fn try_from(def: PatternDef) -> Result<Self, Self::Error> {
104 if def.id.is_empty() || def.id.contains('/') {
105 return Err(ValidationError::InvalidPatternId {
106 pattern_id: def.id,
107 reason: "must be non-empty and contain no slash",
108 });
109 }
110 if let Some(knot) = def.inner.knots.iter().find(|knot| knot.id.contains('/')) {
111 return Err(ValidationError::InvalidKnotId {
112 knot_id: knot.id.clone(),
113 reason: "pattern inner knot ids must contain no slash",
114 });
115 }
116 let index: BTreeMap<&str, &KnotDef> =
117 def.inner.knots.iter().map(|k| (k.id.as_str(), k)).collect();
118 let mut names = BTreeSet::new();
119 let mut external = BTreeSet::new();
120 let internally_connected: BTreeSet<(String, String)> = def
121 .inner
122 .threads
123 .iter()
124 .map(|thread| (thread.to.knot.clone(), thread.to.port.clone()))
125 .collect();
126 let mut physical_inputs: BTreeMap<(String, String), String> = BTreeMap::new();
127 for export in &def.inputs {
128 if !names.insert(export.name.as_str()) {
129 return Err(ValidationError::DuplicateExport {
130 export: export.name.clone(),
131 });
132 }
133 check_export(&index, export, PortDir::In)?;
134 let endpoint = (export.port.knot.clone(), export.port.port.clone());
135 if internally_connected.contains(&endpoint) {
136 return Err(ValidationError::PatternInputAlreadyConnected {
137 export: export.name.clone(),
138 knot_id: export.port.knot.clone(),
139 port: export.port.port.clone(),
140 });
141 }
142 if let Some(first_export) =
143 physical_inputs.insert(endpoint.clone(), export.name.clone())
144 {
145 return Err(ValidationError::DuplicatePatternInput {
146 knot_id: endpoint.0,
147 port: endpoint.1,
148 first_export,
149 duplicate_export: export.name.clone(),
150 });
151 }
152 external.insert(endpoint);
153 }
154 names.clear();
155 for export in &def.outputs {
156 if !names.insert(export.name.as_str()) {
157 return Err(ValidationError::DuplicateExport {
158 export: export.name.clone(),
159 });
160 }
161 check_export(&index, export, PortDir::Out)?;
162 }
163 crate::authoring::validate::validate_def_with_external_inputs(&def.inner, &external)?;
164 Ok(Self {
165 id: def.id,
166 inner: def.inner,
167 inputs: def.inputs,
168 outputs: def.outputs,
169 })
170 }
171}
172
173impl From<Pattern> for PatternDef {
174 fn from(pattern: Pattern) -> Self {
175 Self {
176 id: pattern.id,
177 inner: pattern.inner,
178 inputs: pattern.inputs,
179 outputs: pattern.outputs,
180 }
181 }
182}
183
184fn check_export(
185 index: &BTreeMap<&str, &KnotDef>,
186 export: &PatternExportDef,
187 expected: PortDir,
188) -> Result<(), ValidationError> {
189 let knot =
190 index
191 .get(export.port.knot.as_str())
192 .ok_or_else(|| ValidationError::UnknownKnot {
193 knot_id: export.port.knot.clone(),
194 })?;
195 let ports = ports_of(&knot.kind);
196 let info = ports
197 .iter()
198 .find(|p| p.name == export.port.port)
199 .ok_or_else(|| ValidationError::UnknownPort {
200 knot_id: knot.id.clone(),
201 port: export.port.port.clone(),
202 expected: ports.iter().map(|p| String::from(p.name)).collect(),
203 })?;
204 if info.dir != expected {
205 return Err(ValidationError::WrongPortDirection {
206 knot_id: knot.id.clone(),
207 port: export.port.port.clone(),
208 expected,
209 actual: info.dir,
210 });
211 }
212 Ok(())
213}
214
215pub(crate) struct ExpandedPattern {
216 pub knots: Vec<KnotDef>,
217 pub threads: Vec<ThreadDef>,
218 pub inputs: BTreeMap<String, PortRefDef>,
219 pub outputs: BTreeMap<String, PortRefDef>,
220}
221
222pub(crate) fn expand(
223 instance_id: &str,
224 pattern: &Pattern,
225) -> Result<ExpandedPattern, crate::BuildError> {
226 if instance_id.is_empty() || instance_id.contains('/') {
227 return Err(crate::BuildError::InvalidId {
228 id: String::from(instance_id),
229 reason: "pattern instance ids must be non-empty and contain no slash",
230 });
231 }
232 let prefix = format_prefix(instance_id);
233 let knots = pattern
234 .inner
235 .knots
236 .iter()
237 .map(|knot| KnotDef {
238 id: prefixed(&prefix, &knot.id),
239 kind: knot.kind.clone(),
240 })
241 .collect();
242 let threads = pattern
243 .inner
244 .threads
245 .iter()
246 .map(|thread| ThreadDef {
247 from: PortRefDef::new(
248 prefixed(&prefix, &thread.from.knot),
249 thread.from.port.clone(),
250 ),
251 to: PortRefDef::new(prefixed(&prefix, &thread.to.knot), thread.to.port.clone()),
252 })
253 .collect();
254 let inputs = pattern
255 .inputs
256 .iter()
257 .map(|export| {
258 (
259 export.name.clone(),
260 PortRefDef::new(
261 prefixed(&prefix, &export.port.knot),
262 export.port.port.clone(),
263 ),
264 )
265 })
266 .collect();
267 let outputs = pattern
268 .outputs
269 .iter()
270 .map(|export| {
271 (
272 export.name.clone(),
273 PortRefDef::new(
274 prefixed(&prefix, &export.port.knot),
275 export.port.port.clone(),
276 ),
277 )
278 })
279 .collect();
280 Ok(ExpandedPattern {
281 knots,
282 threads,
283 inputs,
284 outputs,
285 })
286}
287
288fn format_prefix(instance_id: &str) -> String {
289 let mut value = String::from(instance_id);
290 value.push('/');
291 value
292}
293fn prefixed(prefix: &str, knot: &str) -> String {
294 let mut value = String::from(prefix);
295 value.push_str(knot);
296 value
297}