Skip to content

Commit

Permalink
Merge 6e464cd into f9f868c
Browse files Browse the repository at this point in the history
  • Loading branch information
chris-olszewski committed Oct 26, 2023
2 parents f9f868c + 6e464cd commit 61d298c
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 80 deletions.
22 changes: 11 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/turborepo-lockfiles/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ regex = "1"
semver = "1.0.17"
serde = { version = "1.0.126", features = ["derive", "rc"] }
serde_json = "1.0.86"
serde_yaml = "0.9"
serde_yaml = "0.9.27"
thiserror = "1.0.38"
turbopath = { path = "../turborepo-paths" }

Expand Down
31 changes: 31 additions & 0 deletions crates/turborepo-lockfiles/fixtures/berry_semver.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!

__metadata:
version: 6
cacheKey: 8

"file-source@npm:2":
version: 2.6.1
resolution: "file-source@npm:2.6.1"
dependencies:
stream-source: 0.10
checksum: db27232df214b27ddd7026bbd202caf0bde31123811ebac0a9772d6a41735efdc3ee8050661f56d02d43811bde79938adb5ed8cbfe5f813c7cd09b86d19b6c84
languageName: node
linkType: hard

"foo@workspace:packages/foo":
version: 0.0.0-use.local
resolution: "foo@workspace:packages/foo"
dependencies:
file-source: 2
languageName: unknown
linkType: soft

"stream-source@npm:0.10":
version: 0.10.12
resolution: "stream-source@npm:0.10.12"
checksum: 65a0e0a38014dcfa6e219e92d83bde5fe716cf16df339e9d7e951525c0d129771416de4d77c3389c38cdd20a43d9575f90cc91041d6fa0c2f053b989780e3591
languageName: node
linkType: hard

177 changes: 132 additions & 45 deletions crates/turborepo-lockfiles/src/berry/de.rs
Original file line number Diff line number Diff line change
@@ -1,63 +1,150 @@
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

// Newtype used for correct deserialization
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Default, Clone)]
pub struct SemverString(pub String);
use serde::Deserialize;

impl From<SemverString> for String {
fn from(value: SemverString) -> Self {
value.0
}
use super::{BerryPackage, DependencyMeta, LockfileData, Metadata};

const METADATA_KEY: &str = "__metadata";

/// Union type of yarn.lock metadata entry and package entries.
/// Only as a workaround for serde_yaml behavior around parsing numbers as
/// strings.
// In the ideal world this would be an enum, but serde_yaml currently has behavior
// where using `#[serde(untagged)]` or `#[serde(flatten)]` affects how it handles
// YAML numbers being parsed as Strings.
// If these macros are present, then it will refuse to parse 1 or 1.0 as a String
// and will instead only parse them as an int/float respectively.
// If these macros aren't present, then it will happily parse 1 or 1.0 as
// "1" and "1.0".
#[derive(Debug, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Entry {
version: String,
language_name: Option<String>,
dependencies: Option<BTreeMap<String, String>>,
peer_dependencies: Option<BTreeMap<String, String>>,
dependencies_meta: Option<BTreeMap<String, DependencyMeta>>,
peer_dependencies_meta: Option<BTreeMap<String, DependencyMeta>>,
bin: Option<BTreeMap<String, String>>,
link_type: Option<String>,
resolution: Option<String>,
checksum: Option<String>,
conditions: Option<String>,
cache_key: Option<String>,
}

impl AsRef<str> for SemverString {
fn as_ref(&self) -> &str {
self.0.as_str()
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("missing resolution for entry {0}")]
MissingResolution(String),
#[error("multiple entry {0} has fields that should only appear in metadata")]
InvalidMetadataFields(String),
#[error("lockfile missing {METADATA_KEY} entry")]
MissingMetadata,
}

impl<'de> Deserialize<'de> for SemverString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
// We use this to massage numerical semver versions to strings
// e.g. 2
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrNum {
String(String),
Int(u64),
Float(f32),
}
impl TryFrom<BTreeMap<String, Entry>> for LockfileData {
type Error = Error;

match StringOrNum::deserialize(deserializer)? {
StringOrNum::String(s) => Ok(SemverString(s)),
StringOrNum::Int(x) => Ok(SemverString(x.to_string())),
StringOrNum::Float(f) => Ok(SemverString(f.to_string())),
fn try_from(mut value: BTreeMap<String, Entry>) -> Result<Self, Self::Error> {
let Entry {
version, cache_key, ..
} = value.remove(METADATA_KEY).ok_or(Error::MissingMetadata)?;
let metadata = Metadata { version, cache_key };
let mut packages = BTreeMap::new();
for (key, entry) in value {
let Entry {
version,
language_name,
dependencies,
peer_dependencies,
dependencies_meta,
peer_dependencies_meta,
bin,
link_type,
resolution,
checksum,
conditions,
cache_key,
} = entry;
if cache_key.is_some() {
return Err(Error::InvalidMetadataFields(key));
}
let resolution = resolution.ok_or_else(|| Error::MissingResolution(key.clone()))?;
packages.insert(
key,
BerryPackage {
version,
language_name,
dependencies,
peer_dependencies,
dependencies_meta,
peer_dependencies_meta,
bin,
link_type,
resolution,
checksum,
conditions,
},
);
}

Ok(LockfileData { metadata, packages })
}
}

#[cfg(test)]
mod test {
use std::collections::HashMap;

use super::*;

#[test]
fn test_semver() {
let input = "foo: 1.2.3
bar: 2
baz: latest
oop: 1.3
";

let result: HashMap<String, SemverString> = serde_yaml::from_str(input).unwrap();

assert_eq!(result["foo"].as_ref(), "1.2.3");
assert_eq!(result["bar"].as_ref(), "2");
assert_eq!(result["baz"].as_ref(), "latest");
assert_eq!(result["oop"].as_ref(), "1.3")
fn test_requires_metadata() {
let data = BTreeMap::new();
assert!(LockfileData::try_from(data).is_err());
}

#[test]
fn test_rejects_cache_key_in_packages() {
let mut data = BTreeMap::new();
data.insert(
METADATA_KEY.to_string(),
Entry {
version: "1".into(),
cache_key: Some("8".into()),
..Default::default()
},
);
data.insert(
"foo".to_string(),
Entry {
version: "1".into(),
resolution: Some("resolved".into()),
cache_key: Some("8".into()),
..Default::default()
},
);
assert!(LockfileData::try_from(data).is_err());
}

#[test]
fn test_requires_resolution() {
let mut data = BTreeMap::new();
data.insert(
METADATA_KEY.to_string(),
Entry {
version: "1".into(),
cache_key: Some("8".into()),
..Default::default()
},
);
data.insert(
"foo".to_string(),
Entry {
version: "1".into(),
resolution: None,
..Default::default()
},
);
assert!(LockfileData::try_from(data).is_err());
}
}

0 comments on commit 61d298c

Please sign in to comment.