Skip to content

Commit 0b161c7

Browse files
authored
Merge pull request #1306 from spkenv/ident-parts-buf-eq
Fix IdentPartsBuf not respecting version equality semantics
2 parents a117a4c + 35504f9 commit 0b161c7

File tree

7 files changed

+107
-59
lines changed

7 files changed

+107
-59
lines changed

crates/spk-schema/crates/foundation/src/ident/ident_any.rs

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -191,37 +191,12 @@ impl FromStr for AnyIdent {
191191
}
192192
}
193193

194-
impl TryFrom<&IdentPartsBuf> for AnyIdent {
195-
type Error = crate::ident::Error;
196-
197-
fn try_from(parts: &IdentPartsBuf) -> Result<Self> {
198-
if parts.repository_name.is_some() {
199-
return Err("Ident may not have a repository name".into());
200-
}
201-
202-
let name = parts.pkg_name.parse::<PkgNameBuf>()?;
203-
let version = parts
204-
.version_str
205-
.as_ref()
206-
.map(|v| v.parse::<Version>())
207-
.transpose()?
208-
.unwrap_or_default();
209-
let build = parts
210-
.build_str
211-
.as_ref()
212-
.map(|v| v.parse::<Build>())
213-
.transpose()?;
214-
215-
Ok(VersionIdent::new(name, version).into_any_ident(build))
216-
}
217-
}
218-
219194
impl From<&AnyIdent> for IdentPartsBuf {
220195
fn from(ident: &AnyIdent) -> Self {
221196
IdentPartsBuf {
222197
repository_name: None,
223198
pkg_name: ident.name().to_string(),
224-
version_str: Some(ident.version().to_string()),
199+
version_str: Some(ident.version().into()),
225200
build_str: ident.build().map(|b| b.to_string()),
226201
}
227202
}
@@ -240,15 +215,6 @@ impl From<PkgNameBuf> for Box<AnyIdent> {
240215
}
241216
}
242217

243-
impl PartialEq<&AnyIdent> for IdentPartsBuf {
244-
fn eq(&self, other: &&AnyIdent) -> bool {
245-
self.repository_name.is_none()
246-
&& self.pkg_name == other.name().as_str()
247-
&& self.version_str == Some(other.version().to_string())
248-
&& self.build_str == other.build().map(|b| b.to_string())
249-
}
250-
}
251-
252218
impl TryFrom<RangeIdent> for AnyIdent {
253219
type Error = crate::ident::Error;
254220

crates/spk-schema/crates/foundation/src/ident/ident_build.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ impl TryFrom<EmbeddedSourcePackage> for BuildIdent {
5959
};
6060
};
6161
Ok(Self::new(
62-
VersionIdent::new(pkg_name.try_into()?, version.try_into()?),
62+
VersionIdent::new(pkg_name.try_into()?, version.as_str().try_into()?),
6363
build.try_into()?,
6464
))
6565
}
@@ -198,7 +198,7 @@ impl TryFrom<&IdentPartsBuf> for BuildIdent {
198198
let version = parts
199199
.version_str
200200
.as_ref()
201-
.map(|v| v.parse::<Version>())
201+
.map(|v| v.as_str().parse::<Version>())
202202
.transpose()?
203203
.unwrap_or_default();
204204
let build = parts
@@ -238,7 +238,7 @@ impl From<&BuildIdent> for IdentPartsBuf {
238238
IdentPartsBuf {
239239
repository_name: None,
240240
pkg_name: ident.name().to_string(),
241-
version_str: Some(ident.version().to_string()),
241+
version_str: Some(ident.version().into()),
242242
build_str: Some(ident.build().to_string()),
243243
}
244244
}
@@ -248,7 +248,10 @@ impl PartialEq<&BuildIdent> for IdentPartsBuf {
248248
fn eq(&self, other: &&BuildIdent) -> bool {
249249
self.repository_name.is_none()
250250
&& self.pkg_name == other.name().as_str()
251-
&& self.version_str == Some(other.version().to_string())
251+
&& self
252+
.version_str
253+
.as_ref()
254+
.is_some_and(|s| *s == other.version().into())
252255
&& self.build_str == Some(other.build().to_string())
253256
}
254257
}

crates/spk-schema/crates/foundation/src/ident_build/build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ impl EmbeddedSourcePackage {
6363
repository_name: None,
6464
name: self.ident.pkg_name.parse()?,
6565
version: VersionFilter::single(VersionRange::DoubleEquals(DoubleEqualsVersion::from(
66-
version_str.parse::<Version>()?,
66+
version_str.as_str().parse::<Version>()?,
6767
))),
6868
components: self.components.clone(),
6969
build: Some(build_str.parse()?),

crates/spk-schema/crates/foundation/src/ident_build/parsing/build.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,15 @@ where
9696
+ FromExternalError<&'b str, std::num::ParseIntError>
9797
+ TagError<&'b str, &'static str>,
9898
{
99-
map(
99+
map_res(
100100
delimited(tag("["), cut(ident_parts_with_components), cut(tag("]"))),
101101
|(ident, components)| {
102-
EmbeddedSource::Package(Box::new(EmbeddedSourcePackage {
103-
ident: ident.to_owned(),
104-
components,
105-
}))
102+
Ok::<_, crate::version::Error>(EmbeddedSource::Package(Box::new(
103+
EmbeddedSourcePackage {
104+
ident: ident.to_owned()?,
105+
components,
106+
},
107+
)))
106108
},
107109
)(input)
108110
}

crates/spk-schema/crates/foundation/src/ident_ops/parsing/ident.rs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ use crate::ident_build::parsing::build;
2121
use crate::ident_component::Component;
2222
use crate::name::RepositoryName;
2323
use crate::name::parsing::package_name;
24+
use crate::version::Version;
2425
use crate::version::parsing::version_str;
2526

27+
// This type should _not_ derive Eq because `version_str` is not guaranteed to
28+
// be normalized.
2629
#[derive(Debug)]
2730
pub struct IdentParts<'s> {
2831
pub repository_name: Option<&'s str>,
@@ -31,22 +34,49 @@ pub struct IdentParts<'s> {
3134
pub build_str: Option<&'s str>,
3235
}
3336

37+
/// A version string that is normalized.
38+
///
39+
/// A normalized version string is required for equality comparisons.
40+
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
41+
pub struct NormalizedVersionString(String);
42+
43+
impl NormalizedVersionString {
44+
/// Access to the inner string.
45+
pub fn as_str(&self) -> &str {
46+
&self.0
47+
}
48+
}
49+
50+
impl std::fmt::Display for NormalizedVersionString {
51+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52+
self.0.fmt(f)
53+
}
54+
}
55+
56+
impl From<&Version> for NormalizedVersionString {
57+
fn from(version: &Version) -> Self {
58+
NormalizedVersionString(version.to_storage_string())
59+
}
60+
}
61+
3462
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
3563
pub struct IdentPartsBuf {
3664
pub repository_name: Option<String>,
3765
pub pkg_name: String,
38-
pub version_str: Option<String>,
66+
pub version_str: Option<NormalizedVersionString>,
3967
pub build_str: Option<String>,
4068
}
4169

4270
impl IdentParts<'_> {
43-
pub fn to_owned(&self) -> IdentPartsBuf {
44-
IdentPartsBuf {
71+
pub fn to_owned(&self) -> crate::version::Result<IdentPartsBuf> {
72+
let version: Option<Version> = self.version_str.map(|s| s.parse()).transpose()?;
73+
74+
Ok(IdentPartsBuf {
4575
repository_name: self.repository_name.map(|o| o.to_owned()),
4676
pkg_name: self.pkg_name.to_owned(),
47-
version_str: self.version_str.map(|o| o.to_owned()),
77+
version_str: version.as_ref().map(Into::into),
4878
build_str: self.build_str.map(|o| o.to_owned()),
49-
}
79+
})
5080
}
5181
}
5282

crates/spk-schema/crates/foundation/src/version/mod.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -586,14 +586,6 @@ impl TryFrom<&str> for Version {
586586
}
587587
}
588588

589-
impl TryFrom<String> for Version {
590-
type Error = Error;
591-
592-
fn try_from(value: String) -> Result<Self> {
593-
parse_version(value)
594-
}
595-
}
596-
597589
impl FromStr for Version {
598590
type Err = Error;
599591

crates/spk-storage/src/storage/repository_test.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ use std::str::FromStr;
66

77
use rstest::rstest;
88
use spk_schema::foundation::ident_component::Component;
9-
use spk_schema::foundation::pkg_name;
109
use spk_schema::foundation::spec_ops::Named;
10+
use spk_schema::foundation::{pkg_name, version_ident};
1111
use spk_schema::ident::{AsVersionIdent, parse_build_ident, parse_version_ident};
12+
use spk_schema::ident_build::{Build, EmbeddedSource};
1213
use spk_schema::{
1314
Deprecate,
1415
DeprecateMut,
@@ -389,6 +390,60 @@ async fn test_repo_publish_package_creates_embed_stubs(#[case] repo: RepoKind) {
389390
);
390391
}
391392

393+
/// If an embedded package is declared with a non-normalized version,
394+
/// the buildid created for the stub should contain a normalized version.
395+
///
396+
/// This is necessary to be able to compare build ids correctly.
397+
#[rstest]
398+
#[case::mem(RepoKind::Mem)]
399+
#[case::spfs(RepoKind::Spfs)]
400+
#[tokio::test]
401+
async fn test_embedded_stub_build_version_is_normalized(#[case] repo: RepoKind) {
402+
let repo = make_repo(repo).await;
403+
let recipe = recipe!({
404+
"pkg": "my-pkg/1.0.0",
405+
"install": {
406+
"embedded": [
407+
{"pkg": "my-embedded-pkg/1.0.0.0.0"}
408+
]
409+
}
410+
});
411+
repo.publish_recipe(&recipe).await.unwrap();
412+
let spec = spec!({
413+
"pkg": "my-pkg/1.0.0/3I42H3S6",
414+
"install": {
415+
"embedded": [
416+
{"pkg": "my-embedded-pkg/1.0.0.0.0/embedded"}
417+
]
418+
}
419+
});
420+
repo.publish_package(
421+
&spec,
422+
&vec![(Component::Run, empty_layer_digest())]
423+
.into_iter()
424+
.collect(),
425+
)
426+
.await
427+
.unwrap();
428+
assert_eq!(
429+
repo.list_package_builds(&version_ident!("my-embedded-pkg/1.0.0"))
430+
.await
431+
.unwrap()
432+
.into_iter()
433+
.filter_map(|build_ident| match build_ident.build() {
434+
Build::Embedded(EmbeddedSource::Package(embedded_source)) =>
435+
// Checking the "raw" version string that was encoded into
436+
// the build id.
437+
embedded_source.ident.version_str.to_owned(),
438+
_ => None,
439+
})
440+
.next()
441+
.expect("should have at least one (embedded) build")
442+
.as_str(),
443+
"1.0.0"
444+
);
445+
}
446+
392447
#[rstest]
393448
#[case::mem(RepoKind::Mem)]
394449
#[case::spfs(RepoKind::Spfs)]

0 commit comments

Comments
 (0)