Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add directory to support installing in a different directory than 'package-name' #236

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ pub async fn init(kind: Option<PackageType>, name: Option<PackageName>) -> miett
Ok(PackageManifest {
kind,
name,
directory: None,
version: INITIAL_VERSION,
description: None,
})
Expand Down
5 changes: 4 additions & 1 deletion src/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use url::Url;

use crate::{
errors::{DeserializationError, FileExistsError, FileNotFound, SerializationError, WriteError},
package::{Package, PackageName},
package::{Package, PackageDirectory, PackageName},
registry::RegistryUri,
ManagedFile,
};
Expand All @@ -41,6 +41,8 @@ pub const LOCKFILE: &str = "Proto.lock";
pub struct LockedPackage {
/// The name of the package
pub name: PackageName,
/// Directory where the package's contents are stored
pub directory: Option<PackageDirectory>,
/// The cryptographic digest of the package contents
pub digest: Digest,
/// The URI of the registry that contains the package
Expand All @@ -67,6 +69,7 @@ impl LockedPackage {
) -> Self {
Self {
name: package.name().to_owned(),
directory: package.directory().cloned(),
registry,
repository,
digest: DigestAlgorithm::SHA256.digest(&package.tgz),
Expand Down
13 changes: 12 additions & 1 deletion src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use tokio::fs;

use crate::{
errors::{DeserializationError, FileExistsError, SerializationError, WriteError},
package::{PackageName, PackageType},
package::{PackageDirectory, PackageName, PackageType},
registry::RegistryUri,
ManagedFile,
};
Expand Down Expand Up @@ -377,12 +377,23 @@ pub struct PackageManifest {
pub kind: PackageType,
/// Name of the package
pub name: PackageName,
/// Directory in which to put the cache. If unset, defaults to the package name
pub directory: Option<PackageDirectory>,
/// Version of the package
pub version: Version,
/// Description of the api package
pub description: Option<String>,
}

impl PackageManifest {
/// Get the directory where the package contents will be stored.
///
/// This fallbacks to `name` if `directory` is unset.
pub fn directory(&self) -> &str {
self.directory.as_deref().unwrap_or(self.name.as_ref())
}
}

/// Represents a single project dependency
#[derive(Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq)]
pub struct Dependency {
Expand Down
27 changes: 27 additions & 0 deletions src/package/compressed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ use crate::{
ManagedFile,
};

use super::PackageDirectory;

/// An in memory representation of a `buffrs` package
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Package {
Expand Down Expand Up @@ -208,6 +210,31 @@ impl Package {
.name
}

/// The directory of this package
#[inline]
pub fn directory(&self) -> Option<&PackageDirectory> {
assert!(self.manifest.package.is_some());

self.manifest
.package
.as_ref()
.expect("compressed package contains invalid manifest (package section missing)")
.directory
.as_ref()
}

/// Directory for this oackage
#[inline]
pub fn directory_str(&self) -> &str {
assert!(self.manifest.package.is_some());

self.manifest
.package
.as_ref()
.expect("compressed package contains invalid manifest (package section missing)")
.directory()
}

/// The version of this package
#[inline]
pub fn version(&self) -> &Version {
Expand Down
172 changes: 172 additions & 0 deletions src/package/directory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Copyright 2023 Helsing GmbH
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{fmt, ops::Deref, str::FromStr};

use miette::IntoDiagnostic;
use serde::{Deserialize, Serialize};

/// A `buffrs` package directory for parsing and type safety
#[derive(Clone, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug)]
#[serde(try_from = "String", into = "String")]
pub struct PackageDirectory(String);

/// Errors that can be generated parsing [`PackageDirectory`], see [`PackageDirectory::new()`].
#[derive(thiserror::Error, Debug, PartialEq)]
pub enum PackageDirectoryError {
/// Empty package directory.
#[error("package directory must be at least one character long, but was empty")]
Empty,
/// Too long.
#[error("package directories must be at most 128 characters long, but was {0:}")]
TooLong(usize),
/// Invalid start character.
#[error("package directory must start with alphabetic character, but was {0:}")]
InvalidStart(char),
/// Invalid character.
#[error("package directory must consist of only ASCII lowercase and dashes (-, _), but contains {0:} at position {1:}")]
InvalidCharacter(char, usize),
}

impl super::ParseError for PackageDirectoryError {
#[inline]
fn empty() -> Self {
Self::Empty
}

#[inline]
fn too_long(current_length: usize) -> Self {
Self::TooLong(current_length)
}

#[inline]
fn invalid_start(first: char) -> Self {
Self::InvalidStart(first)
}

#[inline]
fn invalid_character(found: char, pos: usize) -> Self {
Self::InvalidCharacter(found, pos)
}
}

impl PackageDirectory {
const MAX_LENGTH: usize = 128;

/// New package directory from string.
pub fn new<S: Into<String>>(value: S) -> Result<Self, PackageDirectoryError> {
let value = value.into();
Self::validate(&value)?;
Ok(Self(value))
}

/// Validate a package directory.
pub fn validate(directory: impl AsRef<str>) -> Result<(), PackageDirectoryError> {
super::validate(directory.as_ref(), &[b'-', b'_'], Self::MAX_LENGTH)
}
}

impl TryFrom<String> for PackageDirectory {
type Error = PackageDirectoryError;

fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}

impl FromStr for PackageDirectory {
type Err = miette::Report;

fn from_str(input: &str) -> Result<Self, Self::Err> {
Self::new(input).into_diagnostic()
}
}

impl From<PackageDirectory> for String {
fn from(s: PackageDirectory) -> Self {
s.to_string()
}
}

impl Deref for PackageDirectory {
type Target = str;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl fmt::Display for PackageDirectory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn ascii_lowercase() {
assert_eq!(
PackageDirectory::new("abc"),
Ok(PackageDirectory("abc".into()))
);
}

#[test]
fn short() {
assert_eq!(PackageDirectory::new("a"), Ok(PackageDirectory("a".into())));
assert_eq!(
PackageDirectory::new("ab"),
Ok(PackageDirectory("ab".into()))
);
}

#[test]
fn long() {
assert_eq!(
PackageDirectory::new("a".repeat(PackageDirectory::MAX_LENGTH)),
Ok(PackageDirectory("a".repeat(PackageDirectory::MAX_LENGTH)))
);

assert_eq!(
PackageDirectory::new("a".repeat(PackageDirectory::MAX_LENGTH + 1)),
Err(PackageDirectoryError::TooLong(
PackageDirectory::MAX_LENGTH + 1
))
);
}

#[test]
fn empty() {
assert_eq!(PackageDirectory::new(""), Err(PackageDirectoryError::Empty));
}

#[test]
fn numeric_start() {
assert_eq!(
PackageDirectory::new("4abc"),
Err(PackageDirectoryError::InvalidStart('4'))
);
}

#[test]
fn underscore_and_dash() {
assert_eq!(
PackageDirectory::new("with_underscore-and-dash"),
Ok(PackageDirectory("with_underscore-and-dash".into())),
);
}
}
Loading
Loading