Skip to content

Commit 2a88609

Browse files
Sujay JayakarConvex, Inc.
authored andcommitted
Move FunctionName to sync_types (#25401)
I'm going to start adding function paths that are namespaced by component, so I wanted to be able to reuse `FunctionName`. This also makes our function name handling more typesafe. GitOrigin-RevId: c309d9e310d9a171b2872de687efe9bc5dec6eef
1 parent 1e79389 commit 2a88609

File tree

13 files changed

+116
-101
lines changed

13 files changed

+116
-101
lines changed

crates/application/src/application_function_runner/mod.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1650,10 +1650,8 @@ impl<RT: Runtime> ApplicationFunctionRunner<RT> {
16501650
))));
16511651
};
16521652
let name = cron_spec.udf_path.function_name();
1653-
let Some(scheduled_function) = scheduled_module
1654-
.functions
1655-
.iter()
1656-
.find(|f| f.name.as_ref() == name)
1653+
let Some(scheduled_function) =
1654+
scheduled_module.functions.iter().find(|f| &f.name == name)
16571655
else {
16581656
return Ok(Err(JsError::from_message(format!(
16591657
"The cron job '{identifier}' schedules a function that does not exist: {}",

crates/application/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ use sync_types::{
236236
AuthenticationToken,
237237
CanonicalizedModulePath,
238238
CanonicalizedUdfPath,
239+
FunctionName,
239240
UdfPath,
240241
};
241242
use table_summary_worker::{
@@ -1831,7 +1832,7 @@ impl<RT: Runtime> Application<RT> {
18311832
// 2. get the function type
18321833
let mut analyzed_function = None;
18331834
for function in &analyzed_module.functions {
1834-
if function.name.as_ref() == "default" {
1835+
if function.name.is_default_export() {
18351836
analyzed_function = Some(function.clone());
18361837
} else {
18371838
anyhow::bail!("Only `export default` is supported.");
@@ -1852,7 +1853,7 @@ impl<RT: Runtime> Application<RT> {
18521853
.await?;
18531854

18541855
// 4. run the function within the transaction
1855-
let path = CanonicalizedUdfPath::new(module_path, "default".to_owned());
1856+
let path = CanonicalizedUdfPath::new(module_path, FunctionName::default_export());
18561857
let arguments = parse_udf_args(&path.clone().into(), args)?;
18571858
let (result, log_lines) = match analyzed_function.udf_type {
18581859
UdfType::Query => self

crates/application/src/tests/analyze.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,13 @@ async fn test_analyze(rt: ProdRuntime) -> anyhow::Result<()> {
7979
assert_eq!(modules[&"a.js".parse()?].functions.len(), 1);
8080
let module = &modules[&"a.js".parse()?].functions[0];
8181
assert_eq!(module.udf_type, UdfType::Query);
82-
assert_eq!(module.name.as_ref(), "isolateFunction");
82+
assert_eq!(&module.name[..], "isolateFunction");
8383
assert!(module.pos.is_none());
8484

8585
assert_eq!(modules[&"b.js".parse()?].functions.len(), 1);
8686
let module = &modules[&"b.js".parse()?].functions[0];
8787
assert_eq!(module.udf_type, UdfType::Action);
88-
assert_eq!(module.name.as_ref(), "nodeFunction");
88+
assert_eq!(&module.name[..], "nodeFunction");
8989
assert!(module.pos.is_none());
9090

9191
Ok(())
@@ -178,19 +178,19 @@ export { hello, internalHello };
178178

179179
assert_eq!(modules[&"isolate_source.js".parse()?].functions.len(), 2);
180180
let module = &modules[&"isolate_source.js".parse()?];
181-
assert_eq!(module.functions[0].name.as_ref(), "hello");
181+
assert_eq!(&module.functions[0].name[..], "hello");
182182
assert_eq!(module.functions[0].udf_type, UdfType::Action);
183183
assert_eq!(module.functions[0].pos.as_ref().unwrap().start_lineno, 28);
184-
assert_eq!(module.functions[1].name.as_ref(), "internalHello");
184+
assert_eq!(&module.functions[1].name[..], "internalHello");
185185
assert_eq!(module.functions[1].udf_type, UdfType::Action);
186186
assert_eq!(module.functions[1].pos.as_ref().unwrap().start_lineno, 31);
187187

188188
assert_eq!(modules[&"node_source.js".parse()?].functions.len(), 2);
189189
let module = &modules[&"node_source.js".parse()?];
190-
assert_eq!(module.functions[0].name.as_ref(), "hello");
190+
assert_eq!(&module.functions[0].name[..], "hello");
191191
assert_eq!(module.functions[0].udf_type, UdfType::Action);
192192
assert_eq!(module.functions[0].pos.as_ref().unwrap().start_lineno, 28);
193-
assert_eq!(module.functions[1].name.as_ref(), "internalHello");
193+
assert_eq!(&module.functions[1].name[..], "internalHello");
194194
assert_eq!(module.functions[1].udf_type, UdfType::Action);
195195
assert_eq!(module.functions[1].pos.as_ref().unwrap().start_lineno, 31);
196196

@@ -231,13 +231,13 @@ async fn test_analyze_crons(rt: ProdRuntime) -> anyhow::Result<()> {
231231
assert_eq!(modules[&"a.js".parse()?].functions.len(), 1);
232232
let module = &modules[&"a.js".parse()?].functions[0];
233233
assert_eq!(module.udf_type, UdfType::Query);
234-
assert_eq!(module.name.as_ref(), "isolateFunction");
234+
assert_eq!(&module.name[..], "isolateFunction");
235235
assert!(module.pos.is_none());
236236

237237
assert_eq!(modules[&"b.js".parse()?].functions.len(), 1);
238238
let module = &modules[&"b.js".parse()?].functions[0];
239239
assert_eq!(module.udf_type, UdfType::Action);
240-
assert_eq!(module.name.as_ref(), "nodeFunction");
240+
assert_eq!(&module.name[..], "nodeFunction");
241241
assert!(module.pos.is_none());
242242

243243
let application = Application::new_for_tests(&rt).await?;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use std::{
2+
ops::Deref,
3+
str::FromStr,
4+
};
5+
6+
use crate::identifier::check_valid_identifier;
7+
8+
#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, derive_more::Display)]
9+
pub struct FunctionName(String);
10+
11+
impl FromStr for FunctionName {
12+
type Err = anyhow::Error;
13+
14+
fn from_str(s: &str) -> Result<Self, Self::Err> {
15+
check_valid_identifier(s)?;
16+
Ok(FunctionName(s.to_string()))
17+
}
18+
}
19+
20+
impl Deref for FunctionName {
21+
type Target = str;
22+
23+
fn deref(&self) -> &str {
24+
&self.0[..]
25+
}
26+
}
27+
28+
impl From<FunctionName> for String {
29+
fn from(function_name: FunctionName) -> Self {
30+
function_name.0
31+
}
32+
}
33+
34+
impl FunctionName {
35+
pub fn default_export() -> Self {
36+
Self("default".to_string())
37+
}
38+
39+
pub fn is_default_export(&self) -> bool {
40+
&self.0 == "default"
41+
}
42+
}
43+
44+
#[cfg(any(test, feature = "testing"))]
45+
impl proptest::arbitrary::Arbitrary for FunctionName {
46+
type Parameters = ();
47+
type Strategy = proptest::strategy::BoxedStrategy<Self>;
48+
49+
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
50+
use proptest::prelude::*;
51+
52+
use crate::identifier::arbitrary_regexes::IDENTIFIER_REGEX;
53+
IDENTIFIER_REGEX
54+
.prop_filter_map("Invalid IdentifierFieldName", |s| {
55+
FunctionName::from_str(&s).ok()
56+
})
57+
.boxed()
58+
}
59+
}

crates/convex/sync_types/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod backoff;
2+
pub mod function_name;
23
pub mod headers;
34
pub mod identifier;
45
pub mod json;
@@ -10,6 +11,7 @@ pub mod types;
1011
pub mod udf_path;
1112

1213
pub use crate::{
14+
function_name::FunctionName,
1315
module_path::{
1416
CanonicalizedModulePath,
1517
ModulePath,

crates/convex/sync_types/src/udf_path.rs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ use super::module_path::{
1010
CanonicalizedModulePath,
1111
ModulePath,
1212
};
13-
use crate::identifier::check_valid_identifier;
13+
use crate::function_name::FunctionName;
1414

1515
/// User-specified path to a function, consisting of a module path and an
1616
/// optional function name, separated by a colon. If a function name isn't
1717
/// provided, the UDF loader uses the default export from the module.
1818
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
1919
pub struct UdfPath {
2020
module: ModulePath,
21-
function: Option<String>,
21+
function: Option<FunctionName>,
2222
}
2323

2424
impl UdfPath {
@@ -33,8 +33,8 @@ impl UdfPath {
3333
}
3434

3535
/// What is the function name for this UDF?
36-
pub fn function_name(&self) -> &str {
37-
self.function.as_ref().map(|s| &s[..]).unwrap_or("default")
36+
pub fn function_name(&self) -> Option<&FunctionName> {
37+
self.function.as_ref()
3838
}
3939

4040
pub fn assume_canonicalized(self) -> anyhow::Result<CanonicalizedUdfPath> {
@@ -47,7 +47,7 @@ impl UdfPath {
4747

4848
pub fn canonicalize(self) -> CanonicalizedUdfPath {
4949
let module = self.module.canonicalize();
50-
let function = self.function.unwrap_or_else(|| "default".to_string());
50+
let function = self.function.unwrap_or_else(FunctionName::default_export);
5151
CanonicalizedUdfPath { module, function }
5252
}
5353
}
@@ -57,10 +57,7 @@ impl FromStr for UdfPath {
5757

5858
fn from_str(p: &str) -> Result<Self, Self::Err> {
5959
let (module, function) = match p.rsplit_once(':') {
60-
Some((module, function)) => {
61-
check_valid_identifier(function)?;
62-
(module.parse()?, Some(function.to_owned()))
63-
},
60+
Some((module, function)) => (module.parse()?, Some(function.parse()?)),
6461
None => (p.parse()?, None),
6562
};
6663
Ok(Self { module, function })
@@ -133,11 +130,11 @@ impl proptest::arbitrary::Arbitrary for UdfPath {
133130
#[derive(Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
134131
pub struct CanonicalizedUdfPath {
135132
module: CanonicalizedModulePath,
136-
function: String,
133+
function: FunctionName,
137134
}
138135

139136
impl CanonicalizedUdfPath {
140-
pub fn new(module: CanonicalizedModulePath, function: String) -> Self {
137+
pub fn new(module: CanonicalizedModulePath, function: FunctionName) -> Self {
141138
Self { module, function }
142139
}
143140

@@ -149,12 +146,12 @@ impl CanonicalizedUdfPath {
149146
&self.module
150147
}
151148

152-
pub fn function_name(&self) -> &str {
149+
pub fn function_name(&self) -> &FunctionName {
153150
&self.function
154151
}
155152

156153
pub fn strip(self) -> UdfPath {
157-
let function = if self.function == "default" {
154+
let function = if self.function.is_default_export() {
158155
None
159156
} else {
160157
Some(self.function)

crates/isolate/src/environment/analyze.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@ use model::{
4949
modules::{
5050
args_validator::ArgsValidator,
5151
module_versions::{
52+
invalid_function_name_error,
5253
AnalyzedFunction,
5354
AnalyzedHttpRoute,
5455
AnalyzedModule,
5556
AnalyzedSourcePosition,
56-
FunctionName,
5757
MappedModule,
5858
ModuleSource,
5959
SourceMap,
@@ -67,6 +67,7 @@ use rand_chacha::ChaCha12Rng;
6767
use serde_json::Value as JsonValue;
6868
use sync_types::{
6969
CanonicalizedModulePath,
70+
FunctionName,
7071
ModulePath,
7172
};
7273
use value::{
@@ -564,7 +565,9 @@ fn udf_analyze<RT: Runtime>(
564565
)
565566
};
566567

567-
let canonicalized_name = FunctionName::from_untrusted(&property_name)?;
568+
let canonicalized_name: FunctionName = property_name
569+
.parse()
570+
.map_err(|e| invalid_function_name_error(&e))?;
568571
if let Some(Some(token)) = fn_source_map.as_ref().map(|sm| sm.lookup_token(lineno, linecol))
569572
// This condition is in place so that we don't have to jump to source in source mappings
570573
// to get back to the original source. This logic gets complicated and is not strictly necessary now

crates/isolate/src/environment/helpers/module_loader.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ pub trait ModuleLoader<RT: Runtime>: Sync + Send + 'static {
7575
.ok_or_else(|| anyhow::anyhow!("Expected analyze result for {udf_path:?}"))?;
7676

7777
for function in &analyzed_module.functions {
78-
if function.name.as_ref() == udf_path.function_name() {
78+
if &function.name == udf_path.function_name() {
7979
return Ok(Ok(function.clone()));
8080
}
8181
}

crates/isolate/src/environment/helpers/validation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ pub async fn validate_schedule_args<RT: Runtime>(
108108
let found = analyze_result
109109
.functions
110110
.iter()
111-
.any(|f| f.name.as_ref() == function_name);
111+
.any(|f| &f.name == function_name);
112112
if !found {
113113
anyhow::bail!(ErrorMetadata::bad_request(
114114
"InvalidScheduledFunction",

crates/isolate/src/tests/analyze.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,11 @@ async fn test_analyze_module(rt: TestRuntime) -> anyhow::Result<()> {
7575

7676
for (i, (name, expected_type, mapped_lineno)) in expected.iter().enumerate() {
7777
let function = &module.functions[i];
78-
assert_eq!(&function.name.as_ref(), name);
78+
assert_eq!(&function.name[..], *name);
7979
assert_eq!(&function.udf_type, expected_type);
8080

8181
let mapped_function = &source_mapped.functions[i];
82-
assert_eq!(&mapped_function.name.as_ref(), name);
82+
assert_eq!(&mapped_function.name[..], *name);
8383
assert_eq!(
8484
mapped_function.pos.as_ref().unwrap().start_lineno,
8585
*mapped_lineno

0 commit comments

Comments
 (0)