flow_expression_parser/parse/
v0.rs

1use std::collections::HashMap;
2
3use once_cell::sync::Lazy;
4use regex::Regex;
5
6use crate::{parse, Error};
7
8pub(crate) static CONNECTION_TARGET_REGEX: Lazy<Regex> = Lazy::new(|| {
9  Regex::new(&format!(
10    r"^({}|{}|{}|{}|{}|[a-zA-Z][a-zA-Z0-9_]*)(?:\[(\w*)\])?$",
11    DEFAULT_ID,
12    parse::SCHEMATIC_INPUT,
13    parse::SCHEMATIC_OUTPUT,
14    parse::NS_LINK,
15    parse::CORE_ID
16  ))
17  .unwrap()
18});
19
20/// The separator in a connection between connection targets.
21pub static CONNECTION_SEPARATOR: &str = "=>";
22
23/// The reserved identifier representing an as-of-yet-undetermined default value.
24const DEFAULT_ID: &str = "<>";
25
26type Result<T> = std::result::Result<T, Error>;
27
28/// Parse a string as connection target pieces.
29pub fn parse_target(s: &str) -> Result<(Option<&str>, Option<&str>)> {
30  CONNECTION_TARGET_REGEX.captures(s.trim()).map_or_else(
31    || {
32      Err(Error::ConnectionTargetSyntax(
33        s.to_owned(),
34        "Unspecified error".to_owned(),
35      ))
36    },
37    |captures| {
38      Ok((
39        captures.get(1).map(|m| m.as_str().trim()),
40        captures.get(2).map(|m| m.as_str().trim()),
41      ))
42    },
43  )
44}
45
46/// Parse a fully qualified component ID into its namespace & name parts.
47pub fn parse_id(id: &str) -> Result<(&str, &str)> {
48  if !id.contains("::") {
49    Err(Error::ComponentIdError(id.to_owned()))
50  } else {
51    id.split_once("::")
52      .ok_or_else(|| Error::ComponentIdError(id.to_owned()))
53  }
54}
55
56type ConnectionDefinitionParts = (String, String, Option<HashMap<String, serde_json::Value>>);
57
58fn parse_from_or_sender(from: &str, default_port: Option<&str>) -> Result<ConnectionDefinitionParts> {
59  match parse_target(from) {
60    Ok((from_ref, from_port)) => Ok((
61      match from_ref {
62        Some(DEFAULT_ID) => parse::SCHEMATIC_INPUT,
63        Some(v) => v,
64        None => return Err(Error::NoDefaultReference(from.to_owned())),
65      }
66      .to_owned(),
67      from_port
68        .or(default_port)
69        .ok_or_else(|| Error::NoDefaultPort(from.to_owned()))?
70        .to_owned(),
71      Default::default(),
72    )),
73    // Validating JSON by parsing into a serde_json::Value is recommended by the docs
74    Err(_e) => match serde_json::from_str::<serde_json::Value>(from) {
75      Ok(_) => Ok((
76        parse::SENDER_ID.to_owned(),
77        parse::SENDER_PORT.to_owned(),
78        serde_json::from_str::<serde_json::Value>(from.trim())
79          .map(|v| Some(HashMap::from([("default".to_owned(), v)])))
80          .map_err(|e| Error::InvalidSenderData(e.to_string()))?,
81      )),
82      Err(e) => Err(Error::ConnectionTargetSyntax(from.to_owned(), e.to_string())),
83    },
84  }
85}
86
87/// Parse a string as a connection and return its parts.
88pub fn parse_connection(s: &str) -> Result<(ConnectionDefinitionParts, ConnectionDefinitionParts)> {
89  let s = s.trim();
90  s.split_once(CONNECTION_SEPARATOR).map_or_else(
91    || Err(Error::ConnectionDefinitionSyntax(s.to_owned())),
92    |(from, to)| {
93      let (to_ref, to_port) = parse_target(to.trim())?;
94      let from = parse_from_or_sender(from.trim(), to_port)?;
95      let to = (
96        match to_ref {
97          Some(DEFAULT_ID) => parse::SCHEMATIC_OUTPUT,
98          Some(v) => v,
99          None => return Err(Error::NoDefaultReference(s.to_owned())),
100        }
101        .to_owned(),
102        to_port
103          .map(|s| s.to_owned())
104          .or_else(|| Some(from.1.clone()))
105          .ok_or_else(|| Error::NoDefaultPort(s.to_owned()))?,
106        Default::default(),
107      );
108      Ok((from, to))
109    },
110  )
111}
112
113#[cfg(test)]
114mod tests {
115
116  use anyhow::Result;
117  use pretty_assertions::assert_eq;
118
119  use super::*;
120  #[test_logger::test]
121  fn test_reserved() -> Result<()> {
122    let parsed = parse_target("input[foo]")?;
123    assert_eq!(parsed, (Some("input"), Some("foo")));
124    Ok(())
125  }
126
127  #[test_logger::test]
128  fn test_basic() -> Result<()> {
129    let parsed = parse_target("ref[foo]")?;
130    assert_eq!(parsed, (Some("ref"), Some("foo")));
131    Ok(())
132  }
133
134  #[test_logger::test]
135  fn test_default_with_port() -> Result<()> {
136    let parsed = parse_target("<>[foo]")?;
137    assert_eq!(parsed, (Some(DEFAULT_ID), Some("foo")));
138    Ok(())
139  }
140
141  #[test_logger::test]
142  fn test_default() -> Result<()> {
143    let parsed = parse_target("<>")?;
144    assert_eq!(parsed, (Some(DEFAULT_ID), None));
145    Ok(())
146  }
147
148  #[test_logger::test]
149  fn test_connection_basic() -> Result<()> {
150    let parsed = parse_connection("ref1[in]  =>  ref2[out]")?;
151    assert_eq!(
152      parsed,
153      (
154        ("ref1".to_owned(), "in".to_owned(), Default::default(),),
155        ("ref2".to_owned(), "out".to_owned(), Default::default(),),
156      )
157    );
158    Ok(())
159  }
160
161  #[test_logger::test]
162  fn test_bare_num_default() -> Result<()> {
163    let parsed = parse_connection("5  =>  ref2[out]")?;
164    let num = 5;
165
166    assert_eq!(
167      parsed,
168      (
169        (
170          parse::SENDER_ID.to_owned(),
171          parse::SENDER_PORT.to_owned(),
172          Some(HashMap::from([("default".into(), num.into())])),
173        ),
174        ("ref2".to_owned(), "out".to_owned(), Default::default(),),
175      )
176    );
177    Ok(())
178  }
179
180  #[test_logger::test]
181  fn test_connection_default_input_named_port() -> Result<()> {
182    let parsed = parse_connection("<>[in] => ref2[out]")?;
183    assert_eq!(
184      parsed,
185      (
186        (parse::SCHEMATIC_INPUT.to_owned(), "in".to_owned(), Default::default(),),
187        ("ref2".to_owned(), "out".to_owned(), Default::default(),),
188      )
189    );
190    Ok(())
191  }
192
193  #[test_logger::test]
194  fn test_connection_default_output_named_port() -> Result<()> {
195    let parsed = parse_connection("ref1[in] => <>[out]")?;
196    assert_eq!(
197      parsed,
198      (
199        ("ref1".to_owned(), "in".to_owned(), Default::default(),),
200        (parse::SCHEMATIC_OUTPUT.to_owned(), "out".to_owned(), Default::default(),),
201      )
202    );
203    Ok(())
204  }
205
206  #[test_logger::test]
207  fn test_connection_default_output() -> Result<()> {
208    let parsed = parse_connection("ref1[port] => <>")?;
209    assert_eq!(
210      parsed,
211      (
212        ("ref1".to_owned(), "port".to_owned(), Default::default(),),
213        (
214          parse::SCHEMATIC_OUTPUT.to_owned(),
215          "port".to_owned(),
216          Default::default(),
217        ),
218      )
219    );
220    Ok(())
221  }
222
223  #[test_logger::test]
224  fn test_connection_default_input() -> Result<()> {
225    let parsed = parse_connection("<>  =>  ref1[port]")?;
226    assert_eq!(
227      parsed,
228      (
229        (parse::SCHEMATIC_INPUT.to_owned(), "port".to_owned(), Default::default(),),
230        ("ref1".to_owned(), "port".to_owned(), Default::default(),),
231      )
232    );
233    Ok(())
234  }
235
236  #[test_logger::test]
237  fn test_connection_with_default_data() -> Result<()> {
238    let parsed = parse_connection(r#""default"=>ref1[port]"#)?;
239    assert_eq!(
240      parsed,
241      (
242        (
243          parse::SENDER_ID.to_owned(),
244          parse::SENDER_PORT.to_owned(),
245          Some(HashMap::from([("default".into(), "default".into())])),
246        ),
247        ("ref1".to_owned(), "port".to_owned(), Default::default(),),
248      )
249    );
250    Ok(())
251  }
252
253  #[test_logger::test]
254  fn regression_1() -> Result<()> {
255    let parsed = parse_connection(r#""1234512345" => <>[output]"#)?;
256    assert_eq!(
257      parsed,
258      (
259        (
260          parse::SENDER_ID.to_owned(),
261          parse::SENDER_PORT.to_owned(),
262          Some(HashMap::from([("default".into(), "1234512345".into())])),
263        ),
264        (
265          parse::SCHEMATIC_OUTPUT.to_owned(),
266          "output".to_owned(),
267          Default::default(),
268        ),
269      )
270    );
271    Ok(())
272  }
273}