convex_sync_types/
path.rs

1use std::{
2    ops::Deref,
3    str::FromStr,
4};
5
6use crate::{
7    identifier::MAX_IDENTIFIER_LEN,
8    FunctionName,
9};
10
11pub fn check_valid_path_component(s: &str) -> anyhow::Result<()> {
12    if s.len() > MAX_IDENTIFIER_LEN {
13        anyhow::bail!(
14            "Path component is too long ({} > maximum {}): {}...",
15            s.len(),
16            MAX_IDENTIFIER_LEN,
17            &s[..s.len().min(MAX_IDENTIFIER_LEN)]
18        );
19    }
20    if !s
21        .chars()
22        .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.')
23    {
24        anyhow::bail!(
25            "Path component {s} can only contain alphanumeric characters, underscores, or periods."
26        );
27    }
28    if !s.chars().any(|c| c.is_ascii_alphanumeric()) {
29        anyhow::bail!("Path component {s} must have at least one alphanumeric character.");
30    }
31    Ok(())
32}
33
34#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
35pub struct PathComponent(String);
36
37impl FromStr for PathComponent {
38    type Err = anyhow::Error;
39
40    fn from_str(s: &str) -> Result<Self, Self::Err> {
41        check_valid_path_component(s)?;
42        Ok(Self(s.to_owned()))
43    }
44}
45
46impl Deref for PathComponent {
47    type Target = str;
48
49    fn deref(&self) -> &Self::Target {
50        &self.0
51    }
52}
53
54impl From<PathComponent> for String {
55    fn from(p: PathComponent) -> Self {
56        p.0
57    }
58}
59
60impl From<FunctionName> for PathComponent {
61    fn from(function_name: FunctionName) -> Self {
62        function_name
63            .parse()
64            .expect("FunctionName isn't a valid PathComponent")
65    }
66}
67
68#[cfg(any(test, feature = "testing"))]
69impl proptest::arbitrary::Arbitrary for PathComponent {
70    type Parameters = ();
71    type Strategy = proptest::strategy::BoxedStrategy<Self>;
72
73    fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
74        use proptest::prelude::*;
75        "_?[a-zA-Z0-9_]{1,60}(\\.js)?"
76            .prop_filter_map("Invalid path component", |s| s.parse().ok())
77            .boxed()
78    }
79}