Skip to content

Commit ff3de6d

Browse files
committed
wip: add 'directory' to packages
1 parent cee594b commit ff3de6d

File tree

8 files changed

+228
-9
lines changed

8 files changed

+228
-9
lines changed

src/command.rs

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ pub async fn init(kind: Option<PackageType>, name: Option<PackageName>) -> miett
6060
Ok(PackageManifest {
6161
kind,
6262
name,
63+
directory: None,
6364
version: INITIAL_VERSION,
6465
description: None,
6566
})

src/lock.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use url::Url;
2323

2424
use crate::{
2525
errors::{DeserializationError, FileExistsError, FileNotFound, SerializationError, WriteError},
26-
package::{Package, PackageName},
26+
package::{Package, PackageDirectory, PackageName},
2727
registry::RegistryUri,
2828
ManagedFile,
2929
};
@@ -41,6 +41,8 @@ pub const LOCKFILE: &str = "Proto.lock";
4141
pub struct LockedPackage {
4242
/// The name of the package
4343
pub name: PackageName,
44+
/// Directory where the package's contents are stored
45+
pub directory: Option<PackageDirectory>,
4446
/// The cryptographic digest of the package contents
4547
pub digest: Digest,
4648
/// The URI of the registry that contains the package
@@ -67,6 +69,7 @@ impl LockedPackage {
6769
) -> Self {
6870
Self {
6971
name: package.name().to_owned(),
72+
directory: package.directory().cloned(),
7073
registry,
7174
repository,
7275
digest: DigestAlgorithm::SHA256.digest(&package.tgz),

src/manifest.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use tokio::fs;
2525

2626
use crate::{
2727
errors::{DeserializationError, FileExistsError, SerializationError, WriteError},
28-
package::{PackageName, PackageType},
28+
package::{PackageDirectory, PackageName, PackageType},
2929
registry::RegistryUri,
3030
ManagedFile,
3131
};
@@ -377,12 +377,23 @@ pub struct PackageManifest {
377377
pub kind: PackageType,
378378
/// Name of the package
379379
pub name: PackageName,
380+
/// Directory in which to put the cache. If unset, defaults to the package name
381+
pub directory: Option<PackageDirectory>,
380382
/// Version of the package
381383
pub version: Version,
382384
/// Description of the api package
383385
pub description: Option<String>,
384386
}
385387

388+
impl PackageManifest {
389+
/// Get the directory where the package contents will be stored.
390+
///
391+
/// This fallbacks to `name` if `directory` is unset.
392+
pub fn directory(&self) -> &str {
393+
self.directory.as_deref().unwrap_or(self.name.as_ref())
394+
}
395+
}
396+
386397
/// Represents a single project dependency
387398
#[derive(Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq)]
388399
pub struct Dependency {

src/package/compressed.rs

+27
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ use crate::{
3232
ManagedFile,
3333
};
3434

35+
use super::PackageDirectory;
36+
3537
/// An in memory representation of a `buffrs` package
3638
#[derive(Clone, Debug, PartialEq, Eq)]
3739
pub struct Package {
@@ -208,6 +210,31 @@ impl Package {
208210
.name
209211
}
210212

213+
/// The directory of this package
214+
#[inline]
215+
pub fn directory(&self) -> Option<&PackageDirectory> {
216+
assert!(self.manifest.package.is_some());
217+
218+
self.manifest
219+
.package
220+
.as_ref()
221+
.expect("compressed package contains invalid manifest (package section missing)")
222+
.directory
223+
.as_ref()
224+
}
225+
226+
/// Directory for this oackage
227+
#[inline]
228+
pub fn directory_str(&self) -> &str {
229+
assert!(self.manifest.package.is_some());
230+
231+
self.manifest
232+
.package
233+
.as_ref()
234+
.expect("compressed package contains invalid manifest (package section missing)")
235+
.directory()
236+
}
237+
211238
/// The version of this package
212239
#[inline]
213240
pub fn version(&self) -> &Version {

src/package/directory.rs

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
// Copyright 2023 Helsing GmbH
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use std::{fmt, ops::Deref, str::FromStr};
16+
17+
use miette::IntoDiagnostic;
18+
use serde::{Deserialize, Serialize};
19+
20+
/// A `buffrs` package directory for parsing and type safety
21+
#[derive(Clone, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug)]
22+
#[serde(try_from = "String", into = "String")]
23+
pub struct PackageDirectory(String);
24+
25+
/// Errors that can be generated parsing [`PackageDirectory`], see [`PackageDirectory::new()`].
26+
#[derive(thiserror::Error, Debug, PartialEq)]
27+
pub enum PackageDirectoryError {
28+
/// Empty package directory.
29+
#[error("package directory must be at least one character long, but was empty")]
30+
Empty,
31+
/// Too long.
32+
#[error("package directories must be at most 128 characters long, but was {0:}")]
33+
TooLong(usize),
34+
/// Invalid start character.
35+
#[error("package directory must start with alphabetic character, but was {0:}")]
36+
InvalidStart(char),
37+
/// Invalid character.
38+
#[error("package directory must consist of only ASCII lowercase and dashes (-, _), but contains {0:} at position {1:}")]
39+
InvalidCharacter(char, usize),
40+
}
41+
42+
impl super::ParseError for PackageDirectoryError {
43+
#[inline]
44+
fn empty() -> Self {
45+
Self::Empty
46+
}
47+
48+
#[inline]
49+
fn too_long(current_length: usize) -> Self {
50+
Self::TooLong(current_length)
51+
}
52+
53+
#[inline]
54+
fn invalid_start(first: char) -> Self {
55+
Self::InvalidStart(first)
56+
}
57+
58+
#[inline]
59+
fn invalid_character(found: char, pos: usize) -> Self {
60+
Self::InvalidCharacter(found, pos)
61+
}
62+
}
63+
64+
impl PackageDirectory {
65+
const MAX_LENGTH: usize = 128;
66+
67+
/// New package directory from string.
68+
pub fn new<S: Into<String>>(value: S) -> Result<Self, PackageDirectoryError> {
69+
let value = value.into();
70+
Self::validate(&value)?;
71+
Ok(Self(value))
72+
}
73+
74+
/// Validate a package directory.
75+
pub fn validate(directory: impl AsRef<str>) -> Result<(), PackageDirectoryError> {
76+
super::validate(directory.as_ref(), &[b'-', b'_'], Self::MAX_LENGTH)
77+
}
78+
}
79+
80+
impl TryFrom<String> for PackageDirectory {
81+
type Error = PackageDirectoryError;
82+
83+
fn try_from(value: String) -> Result<Self, Self::Error> {
84+
Self::new(value)
85+
}
86+
}
87+
88+
impl FromStr for PackageDirectory {
89+
type Err = miette::Report;
90+
91+
fn from_str(input: &str) -> Result<Self, Self::Err> {
92+
Self::new(input).into_diagnostic()
93+
}
94+
}
95+
96+
impl From<PackageDirectory> for String {
97+
fn from(s: PackageDirectory) -> Self {
98+
s.to_string()
99+
}
100+
}
101+
102+
impl Deref for PackageDirectory {
103+
type Target = str;
104+
105+
fn deref(&self) -> &Self::Target {
106+
&self.0
107+
}
108+
}
109+
110+
impl fmt::Display for PackageDirectory {
111+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112+
self.0.fmt(f)
113+
}
114+
}
115+
116+
#[cfg(test)]
117+
mod test {
118+
use super::*;
119+
120+
#[test]
121+
fn ascii_lowercase() {
122+
assert_eq!(
123+
PackageDirectory::new("abc"),
124+
Ok(PackageDirectory("abc".into()))
125+
);
126+
}
127+
128+
#[test]
129+
fn short() {
130+
assert_eq!(PackageDirectory::new("a"), Ok(PackageDirectory("a".into())));
131+
assert_eq!(
132+
PackageDirectory::new("ab"),
133+
Ok(PackageDirectory("ab".into()))
134+
);
135+
}
136+
137+
#[test]
138+
fn long() {
139+
assert_eq!(
140+
PackageDirectory::new("a".repeat(PackageDirectory::MAX_LENGTH)),
141+
Ok(PackageDirectory("a".repeat(PackageDirectory::MAX_LENGTH)))
142+
);
143+
144+
assert_eq!(
145+
PackageDirectory::new("a".repeat(PackageDirectory::MAX_LENGTH + 1)),
146+
Err(PackageDirectoryError::TooLong(
147+
PackageDirectory::MAX_LENGTH + 1
148+
))
149+
);
150+
}
151+
152+
#[test]
153+
fn empty() {
154+
assert_eq!(PackageDirectory::new(""), Err(PackageDirectoryError::Empty));
155+
}
156+
157+
#[test]
158+
fn numeric_start() {
159+
assert_eq!(
160+
PackageDirectory::new("4abc"),
161+
Err(PackageDirectoryError::InvalidStart('4'))
162+
);
163+
}
164+
165+
#[test]
166+
fn underscore_and_dash() {
167+
assert_eq!(
168+
PackageDirectory::new("with_underscore-and-dash"),
169+
Ok(PackageDirectory("with_underscore-and-dash".into())),
170+
);
171+
}
172+
}

src/package/mod.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,15 @@
1313
// limitations under the License.
1414

1515
mod compressed;
16+
mod directory;
1617
mod name;
1718
mod store;
1819
mod r#type;
1920

20-
pub use self::{compressed::Package, name::PackageName, r#type::PackageType, store::PackageStore};
21+
pub use self::{
22+
compressed::Package, directory::PackageDirectory, name::PackageName, r#type::PackageType,
23+
store::PackageStore,
24+
};
2125

2226
trait ParseError {
2327
fn empty() -> Self;

src/package/store.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ impl PackageStore {
6060

6161
/// Path to where the package contents are populated.
6262
fn populated_path(&self, manifest: &PackageManifest) -> PathBuf {
63-
self.proto_vendor_path().join(manifest.name.to_string())
63+
self.proto_vendor_path().join(manifest.directory())
6464
}
6565

6666
/// Creates the expected directory structure for `buffrs`
@@ -96,7 +96,7 @@ impl PackageStore {
9696

9797
/// Unpacks a package into a local directory
9898
pub async fn unpack(&self, package: &Package) -> miette::Result<()> {
99-
let pkg_dir = self.locate(package.name());
99+
let pkg_dir = self.locate(package.directory_str());
100100

101101
package.unpack(&pkg_dir).await?;
102102

@@ -181,8 +181,8 @@ impl PackageStore {
181181
}
182182

183183
/// Directory for the vendored installation of a package
184-
pub fn locate(&self, package: &PackageName) -> PathBuf {
185-
self.proto_vendor_path().join(&**package)
184+
pub fn locate(&self, directory: &str) -> PathBuf {
185+
self.proto_vendor_path().join(directory)
186186
}
187187

188188
/// Collect .proto files in a given path
@@ -214,7 +214,7 @@ impl PackageStore {
214214
/// Sync this stores proto files to the vendor directory
215215
pub async fn populate(&self, manifest: &PackageManifest) -> miette::Result<()> {
216216
let source_path = self.proto_path();
217-
let target_dir = self.proto_vendor_path().join(manifest.name.to_string());
217+
let target_dir = self.populated_path(manifest);
218218

219219
if tokio::fs::try_exists(&target_dir)
220220
.await

src/registry/cache.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ impl LocalRegistry {
6868
let path = self.base_dir.join(PathBuf::from(format!(
6969
"{}/{}/{}-{}.tgz",
7070
repository,
71-
package.name(),
71+
package.directory_str(),
7272
package.name(),
7373
package.version(),
7474
)));
@@ -115,6 +115,7 @@ mod tests {
115115
Some(PackageManifest {
116116
kind: PackageType::Api,
117117
name: "test-api".parse().unwrap(),
118+
directory: None,
118119
version: "0.1.0".parse().unwrap(),
119120
description: None,
120121
}),

0 commit comments

Comments
 (0)