Skip to content

Commit c9d183c

Browse files
nipunn1313Convex, Inc.
authored andcommitted
Allow unknown client version hashes (#24596)
GitOrigin-RevId: c21214f1c2884968b9d203defd2a19816dcdddd7
1 parent bd3e77b commit c9d183c

File tree

2 files changed

+100
-32
lines changed

2 files changed

+100
-32
lines changed

crates/common/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#![feature(error_iter)]
2323
#![feature(impl_trait_in_assoc_type)]
2424
#![feature(round_char_boundary)]
25+
#![feature(never_type)]
2526

2627
pub mod async_compat;
2728
pub mod auth;

crates/common/src/version.rs

Lines changed: 99 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use std::{
77
sync::LazyLock,
88
};
99

10-
use anyhow::Context;
1110
use cmd_util::env::env_config;
1211
use errors::ErrorMetadata;
1312
pub use metrics::{
@@ -73,7 +72,38 @@ impl ClientVersionState {
7372
#[derive(PartialEq, Eq, Clone, Ord, PartialOrd)]
7473
pub struct ClientVersion {
7574
client: ClientType,
76-
version: Version,
75+
version: ClientVersionIdent,
76+
}
77+
78+
#[derive(PartialEq, Eq, Clone, Ord, PartialOrd)]
79+
pub enum ClientVersionIdent {
80+
Semver(Version),
81+
Unrecognized(String),
82+
}
83+
84+
impl Display for ClientVersionIdent {
85+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86+
match self {
87+
Self::Semver(v) => write!(f, "{v}"),
88+
Self::Unrecognized(ident) => write!(f, "{ident}"),
89+
}
90+
}
91+
}
92+
93+
impl ClientVersionIdent {
94+
fn below_threshold(&self, threshold: &Version) -> bool {
95+
match self {
96+
Self::Semver(version) => version <= threshold,
97+
Self::Unrecognized(_) => true,
98+
}
99+
}
100+
101+
fn above_threshold(&self, threshold: &Version) -> bool {
102+
match self {
103+
Self::Semver(version) => version >= threshold,
104+
Self::Unrecognized(_) => true,
105+
}
106+
}
77107
}
78108

79109
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
@@ -95,7 +125,7 @@ pub enum ClientType {
95125
}
96126

97127
impl FromStr for ClientType {
98-
type Err = anyhow::Error;
128+
type Err = !;
99129

100130
fn from_str(s: &str) -> Result<Self, Self::Err> {
101131
let client_type = match &*s.to_ascii_lowercase() {
@@ -189,25 +219,38 @@ impl FromStr for ClientVersion {
189219
type Err = anyhow::Error;
190220

191221
fn from_str(s: &str) -> anyhow::Result<Self> {
192-
let em = ErrorMetadata::bad_request(
193-
"InvalidVersion",
194-
format!(
195-
"Failed to parse client version string: '{s}'. Expected format is \
196-
{{client_name}}-{{semver}}, e.g. my-esolang-client-0.0.1"
197-
),
198-
);
199-
200222
// Use the longest parseable semver spec from the right.
201223
let parts: Vec<&str> = s.split('-').collect();
202-
let (n, version) = (1..parts.len())
203-
.find_map(|n| {
204-
Version::parse(parts[n..parts.len()].join("-").as_str())
205-
.map(|v| (n, v))
206-
.ok()
207-
})
208-
.context(em.clone())?;
209-
let client_str = parts[0..n].join("-");
210-
let client = client_str.parse::<ClientType>().context(em)?;
224+
225+
if parts.len() < 2 {
226+
anyhow::bail!(ErrorMetadata::bad_request(
227+
"InvalidVersion",
228+
format!(
229+
"Failed to parse client version string: '{s}'. Expected format is \
230+
{{client_name}}-{{semver}}, e.g. my-esolang-client-0.0.1"
231+
),
232+
));
233+
}
234+
235+
let split = (1..parts.len()).find_map(|n| {
236+
Version::parse(parts[n..parts.len()].join("-").as_str())
237+
.map(|v| (n, v))
238+
.ok()
239+
});
240+
let (client_str, version) = match split {
241+
Some((n, version)) => {
242+
let client_str = parts[0..n].join("-");
243+
(client_str, ClientVersionIdent::Semver(version))
244+
},
245+
None => {
246+
let client_str = parts[0].to_string();
247+
let version = ClientVersionIdent::Unrecognized(parts[1..].join("-"));
248+
(client_str, version)
249+
},
250+
};
251+
let Ok(client) = client_str.parse::<ClientType>() else {
252+
unreachable!()
253+
};
211254
Ok(Self { client, version })
212255
}
213256
}
@@ -217,7 +260,7 @@ impl ClientVersion {
217260
&self.client
218261
}
219262

220-
pub fn version(&self) -> &Version {
263+
pub fn version(&self) -> &ClientVersionIdent {
221264
&self.version
222265
}
223266

@@ -227,15 +270,15 @@ impl ClientVersion {
227270
let upgrade_instructions = self.client.upgrade_instructions();
228271

229272
if let Some(unsupported_threshold) = self.client.unsupported_threshold()
230-
&& self.version <= unsupported_threshold
273+
&& self.version.below_threshold(&unsupported_threshold)
231274
{
232275
return ClientVersionState::Unsupported(format!(
233276
"The Convex {client} package at version {version} is no longer supported. \
234277
{upgrade_instructions}"
235278
));
236279
}
237280
if let Some(upgrade_required_threshold) = self.client.upgrade_required_threshold()
238-
&& self.version <= upgrade_required_threshold
281+
&& self.version.below_threshold(&upgrade_required_threshold)
239282
{
240283
return ClientVersionState::UpgradeRequired(format!(
241284
"The Convex {client} package at {version} is deprecated and will no longer be \
@@ -257,14 +300,14 @@ impl ClientVersion {
257300
} else {
258301
ClientType::CLI
259302
},
260-
version: v,
303+
version: ClientVersionIdent::Semver(v),
261304
}
262305
}
263306

264307
pub fn unknown() -> ClientVersion {
265308
Self {
266309
client: ClientType::Unrecognized("unknown".into()),
267-
version: Version::new(0, 0, 0),
310+
version: ClientVersionIdent::Unrecognized("unknownversion".into()),
268311
}
269312
}
270313

@@ -274,9 +317,9 @@ impl ClientVersion {
274317
pub fn should_require_format_param(&self) -> bool {
275318
match self.client() {
276319
ClientType::CLI | ClientType::NPM | ClientType::Actions => {
277-
self.version() >= &Version::new(1, 4, 1)
320+
self.version().above_threshold(&Version::new(1, 4, 1))
278321
},
279-
ClientType::Python => self.version() >= &Version::new(0, 5, 0),
322+
ClientType::Python => self.version().above_threshold(&Version::new(0, 5, 0)),
280323
ClientType::Rust
281324
| ClientType::StreamingImport
282325
| ClientType::AirbyteExport
@@ -316,6 +359,7 @@ mod tests {
316359
};
317360
use crate::version::{
318361
ClientType,
362+
ClientVersionIdent,
319363
DeprecationThreshold,
320364
DEPRECATION_THRESHOLD,
321365
};
@@ -341,12 +385,23 @@ mod tests {
341385
);
342386
let client_version = ClientVersion {
343387
client: ClientType::NPM,
344-
version: upgrade_required_version_plus_one,
388+
version: ClientVersionIdent::Semver(upgrade_required_version_plus_one),
345389
};
346390
assert_eq!(
347391
client_version.current_state(),
348392
ClientVersionState::Supported
349393
);
394+
395+
// Unknown version of NPM are unsupported
396+
let client_version = ClientVersion {
397+
client: ClientType::NPM,
398+
version: ClientVersionIdent::Unrecognized("asdfdsasdf".to_string()),
399+
};
400+
assert_matches!(
401+
client_version.current_state(),
402+
ClientVersionState::Unsupported(_)
403+
);
404+
350405
// Versions higher than what we know about are also considered latest.
351406
assert_eq!(
352407
"python-convex-1000.0.0"
@@ -358,10 +413,16 @@ mod tests {
358413
"streaming-import-0.0.10".parse::<ClientVersion>()?,
359414
ClientVersion {
360415
client: ClientType::StreamingImport,
361-
version: Version::new(0, 0, 10)
416+
version: ClientVersionIdent::Semver(Version::new(0, 0, 10))
362417
}
363418
);
364-
assert!("npm-1.2.3.4".parse::<ClientVersion>().is_err());
419+
420+
// Not a valid semver
421+
assert_matches!(
422+
"npm-1.2.3.4".parse::<ClientVersion>()?.current_state(),
423+
ClientVersionState::Unsupported(_)
424+
);
425+
365426
assert_eq!(
366427
&format!("{}", "npm-0.0.10".parse::<ClientVersion>()?),
367428
"npm-0.0.10"
@@ -386,15 +447,21 @@ mod tests {
386447
"some-custom-thing-1.2.3-4.5.6-alpha.7".parse::<ClientVersion>()?,
387448
ClientVersion {
388449
client: ClientType::Unrecognized("some-custom-thing".to_string()),
389-
version: Version {
450+
version: ClientVersionIdent::Semver(Version {
390451
major: 1,
391452
minor: 2,
392453
patch: 3,
393454
pre: Prerelease::new("4.5.6-alpha.7")?,
394455
build: BuildMetadata::EMPTY
395-
}
456+
})
396457
}
397458
);
459+
assert_eq!(
460+
"big_brain-20240412T160958Z-baea64010a12"
461+
.parse::<ClientVersion>()?
462+
.current_state(),
463+
ClientVersionState::Supported
464+
);
398465
Ok(())
399466
}
400467

0 commit comments

Comments
 (0)