convex_sync_types/
path.rs1use 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}