Skip to content

Commit

Permalink
Update docs, add convenience implementations (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
jssblck authored Oct 10, 2024
1 parent 0eadd4a commit bb2bfb0
Show file tree
Hide file tree
Showing 4 changed files with 263 additions and 4 deletions.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "locator"
version = "2.0.2"
version = "2.1.0"
edition = "2021"

[dependencies]
Expand All @@ -20,5 +20,7 @@ semver = "1.0.23"

[dev-dependencies]
assert_matches = "1.5.0"
impls = "1.0.3"
itertools = "0.10.5"
proptest = "1.0.0"
static_assertions = "1.1.0"
105 changes: 104 additions & 1 deletion src/locator.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::fmt::Display;
use std::{fmt::Display, str::FromStr};

use documented::Documented;
use getset::{CopyGetters, Getters};
Expand All @@ -17,6 +17,54 @@ use crate::{
StrictLocator,
};

/// Convenience macro for creating a [`Locator`].
/// Required types and fields are checked at compile time.
///
/// ```
/// let loc = locator::locator!(Npm, "lodash");
/// assert_eq!("npm+lodash", &loc.to_string());
///
/// let loc = locator::locator!(Npm, "lodash", "1.0.0");
/// assert_eq!("npm+lodash$1.0.0", &loc.to_string());
///
/// let loc = locator::locator!(org 1234 => Npm, "lodash");
/// assert_eq!("npm+1234/lodash", &loc.to_string());
///
/// let loc = locator::locator!(org 1234 => Npm, "lodash", "1.0.0");
/// assert_eq!("npm+1234/lodash$1.0.0", &loc.to_string());
/// ```
#[macro_export]
macro_rules! locator {
(org $org:expr => $fetcher:ident, $package:expr, $version:expr) => {
$crate::Locator::builder()
.fetcher($crate::Fetcher::$fetcher)
.package($package)
.org_id($org)
.revision($version)
.build()
};
(org $org:expr => $fetcher:ident, $package:expr) => {
$crate::Locator::builder()
.fetcher($crate::Fetcher::$fetcher)
.package($package)
.org_id($org)
.build()
};
($fetcher:ident, $package:expr, $version:expr) => {
$crate::Locator::builder()
.fetcher($crate::Fetcher::$fetcher)
.package($package)
.revision($version)
.build()
};
($fetcher:ident, $package:expr) => {
$crate::Locator::builder()
.fetcher($crate::Fetcher::$fetcher)
.package($package)
.build()
};
}

/// Core, and most services that interact with Core,
/// refer to open source packages via the `Locator` type.
///
Expand Down Expand Up @@ -85,6 +133,23 @@ pub struct Locator {
fetcher: Fetcher,

/// Specifies the organization ID to which this package is namespaced.
///
/// Locators are namespaced to an organization when FOSSA needs to use the
/// private repositories or settings configured by the user to resolve the package.
///
/// Generally, users can treat this as an implementation detail:
/// Organization IDs namespacing a package means the package should concretely be considered different;
/// for example `npm+lodash$1.0.0` should be considered different from `npm+1234/lodash$1.0.0`.
/// The reasoning for this is that private packages may be totally different than
/// a similarly named public package- in the example above, both of them being `[email protected]`
/// doesn't really imply that they are both the popular project known as "lodash".
/// We know the public one is (`npm+lodash$1.0.0`), but the private one could be anything.
///
/// Examples:
/// - A public Maven package that is hosted on Maven Central is not namespaced.
/// - A private Maven package that is hosted on a private host is namespaced.
/// - A public NPM package that is hosted on NPM is not namespaced.
/// - A private NPM package that is hosted on NPM but requires credentials is namespaced.
#[builder(default, setter(transform = |id: usize| Some(OrgId(id))))]
#[getset(get_copy = "pub")]
org_id: Option<OrgId>,
Expand Down Expand Up @@ -325,19 +390,57 @@ impl From<&StrictLocator> for Locator {
}
}

impl AsRef<Locator> for Locator {
fn as_ref(&self) -> &Locator {
self
}
}

impl FromStr for Locator {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}

#[cfg(test)]
mod tests {
use std::borrow::Cow;

use assert_matches::assert_matches;
use impls::impls;
use itertools::{izip, Itertools};
use pretty_assertions::assert_eq;
use proptest::prelude::*;
use serde::Deserialize;
use static_assertions::const_assert;
use strum::IntoEnumIterator;

use super::*;

#[test]
fn trait_impls() {
const_assert!(impls!(Locator: AsRef<Locator>));
const_assert!(impls!(Locator: FromStr));
const_assert!(impls!(Locator: From<StrictLocator>));
}

#[test]
fn parse_using_fromstr() {
let input = "git+github.com/foo/bar";
let parsed = input.parse().expect("must parse locator");
let expected = locator!(Git, "github.com/foo/bar");
assert_eq!(expected, parsed);
assert_eq!(&parsed.to_string(), input);

let input = "git+github.com/foo/bar$1234";
let parsed = input.parse().expect("must parse locator");
let expected = locator!(Git, "github.com/foo/bar", "1234");
assert_eq!(expected, parsed);
assert_eq!(&parsed.to_string(), input);
}

#[test]
fn parse_render_successful() {
let input = "git+github.com/foo/bar";
Expand Down
79 changes: 78 additions & 1 deletion src/locator_package.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::fmt::Display;
use std::{fmt::Display, str::FromStr};

use documented::Documented;
use getset::{CopyGetters, Getters};
Expand All @@ -12,6 +12,33 @@ use utoipa::{

use crate::{Error, Fetcher, Locator, OrgId, Package, StrictLocator};

/// Convenience macro for creating a [`PackageLocator`].
/// Required types and fields are checked at compile time.
///
/// ```
/// let loc = locator::package!(Npm, "lodash");
/// assert_eq!("npm+lodash", &loc.to_string());
///
/// let loc = locator::package!(org 1234 => Npm, "lodash");
/// assert_eq!("npm+1234/lodash", &loc.to_string());
/// ```
#[macro_export]
macro_rules! package {
(org $org:expr => $fetcher:ident, $package:expr) => {
$crate::PackageLocator::builder()
.fetcher($crate::Fetcher::$fetcher)
.package($package)
.org_id($org)
.build()
};
($fetcher:ident, $package:expr) => {
$crate::PackageLocator::builder()
.fetcher($crate::Fetcher::$fetcher)
.package($package)
.build()
};
}

/// A [`Locator`] specialized to not include the `revision` component.
///
/// Any [`Locator`] may be converted to a `PackageLocator` by simply discarding the `revision` component.
Expand Down Expand Up @@ -63,6 +90,23 @@ pub struct PackageLocator {
fetcher: Fetcher,

/// Specifies the organization ID to which this package is namespaced.
///
/// Locators are namespaced to an organization when FOSSA needs to use the
/// private repositories or settings configured by the user to resolve the package.
///
/// Generally, users can treat this as an implementation detail:
/// Organization IDs namespacing a package means the package should concretely be considered different;
/// for example `npm+lodash$1.0.0` should be considered different from `npm+1234/lodash$1.0.0`.
/// The reasoning for this is that private packages may be totally different than
/// a similarly named public package- in the example above, both of them being `[email protected]`
/// doesn't really imply that they are both the popular project known as "lodash".
/// We know the public one is (`npm+lodash$1.0.0`), but the private one could be anything.
///
/// Examples:
/// - A public Maven package that is hosted on Maven Central is not namespaced.
/// - A private Maven package that is hosted on a private host is namespaced.
/// - A public NPM package that is hosted on NPM is not namespaced.
/// - A private NPM package that is hosted on NPM but requires credentials is namespaced.
#[builder(default, setter(transform = |id: usize| Some(OrgId(id))))]
#[getset(get_copy = "pub")]
org_id: Option<OrgId>,
Expand Down Expand Up @@ -204,18 +248,51 @@ impl From<&StrictLocator> for PackageLocator {
}
}

impl AsRef<PackageLocator> for PackageLocator {
fn as_ref(&self) -> &PackageLocator {
self
}
}

impl FromStr for PackageLocator {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}

#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use impls::impls;
use itertools::{izip, Itertools};
use pretty_assertions::assert_eq;
use serde::Deserialize;
use static_assertions::const_assert;
use strum::IntoEnumIterator;

use crate::ParseError;

use super::*;

#[test]
fn trait_impls() {
const_assert!(impls!(PackageLocator: AsRef<PackageLocator>));
const_assert!(impls!(PackageLocator: FromStr));
const_assert!(impls!(PackageLocator: From<StrictLocator>));
const_assert!(impls!(PackageLocator: From<Locator>));
}

#[test]
fn parse_using_fromstr() {
let input = "git+github.com/foo/bar";
let parsed = input.parse().expect("must parse locator");
let expected = package!(Git, "github.com/foo/bar");
assert_eq!(expected, parsed);
assert_eq!(&parsed.to_string(), input);
}

#[test]
fn parse_render_successful() {
let input = "git+github.com/foo/bar";
Expand Down
79 changes: 78 additions & 1 deletion src/locator_strict.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::fmt::Display;
use std::{fmt::Display, str::FromStr};

use documented::Documented;
use getset::{CopyGetters, Getters};
Expand All @@ -12,6 +12,35 @@ use utoipa::{

use crate::{Error, Fetcher, Locator, OrgId, Package, PackageLocator, ParseError, Revision};

/// Convenience macro for creating a [`StrictLocator`].
/// Required types and fields are checked at compile time.
///
/// ```
/// let loc = locator::strict!(Npm, "lodash", "1.0.0");
/// assert_eq!("npm+lodash$1.0.0", &loc.to_string());
///
/// let loc = locator::strict!(org 1234 => Npm, "lodash", "1.0.0");
/// assert_eq!("npm+1234/lodash$1.0.0", &loc.to_string());
/// ```
#[macro_export]
macro_rules! strict {
(org $org:expr => $fetcher:ident, $package:expr, $version:expr) => {
$crate::StrictLocator::builder()
.fetcher($crate::Fetcher::$fetcher)
.package($package)
.org_id($org)
.revision($version)
.build()
};
($fetcher:ident, $package:expr, $version:expr) => {
$crate::StrictLocator::builder()
.fetcher($crate::Fetcher::$fetcher)
.package($package)
.revision($version)
.build()
};
}

/// A [`Locator`] specialized to **require** the `revision` component.
///
/// ## Ordering
Expand Down Expand Up @@ -61,6 +90,23 @@ pub struct StrictLocator {
fetcher: Fetcher,

/// Specifies the organization ID to which this package is namespaced.
///
/// Locators are namespaced to an organization when FOSSA needs to use the
/// private repositories or settings configured by the user to resolve the package.
///
/// Generally, users can treat this as an implementation detail:
/// Organization IDs namespacing a package means the package should concretely be considered different;
/// for example `npm+lodash$1.0.0` should be considered different from `npm+1234/lodash$1.0.0`.
/// The reasoning for this is that private packages may be totally different than
/// a similarly named public package- in the example above, both of them being `[email protected]`
/// doesn't really imply that they are both the popular project known as "lodash".
/// We know the public one is (`npm+lodash$1.0.0`), but the private one could be anything.
///
/// Examples:
/// - A public Maven package that is hosted on Maven Central is not namespaced.
/// - A private Maven package that is hosted on a private host is namespaced.
/// - A public NPM package that is hosted on NPM is not namespaced.
/// - A private NPM package that is hosted on NPM but requires credentials is namespaced.
#[builder(default, setter(transform = |id: usize| Some(OrgId(id))))]
#[getset(get_copy = "pub")]
org_id: Option<OrgId>,
Expand Down Expand Up @@ -177,16 +223,47 @@ impl<'a> ToSchema<'a> for StrictLocator {
}
}

impl AsRef<StrictLocator> for StrictLocator {
fn as_ref(&self) -> &StrictLocator {
self
}
}

impl FromStr for StrictLocator {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}

#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use impls::impls;
use itertools::{izip, Itertools};
use pretty_assertions::assert_eq;
use serde::Deserialize;
use static_assertions::const_assert;
use strum::IntoEnumIterator;

use super::*;

#[test]
fn trait_impls() {
const_assert!(impls!(StrictLocator: AsRef<StrictLocator>));
const_assert!(impls!(StrictLocator: FromStr));
}

#[test]
fn parse_using_fromstr() {
let input = "git+github.com/foo/bar$abcd";
let parsed = input.parse().expect("must parse locator");
let expected = strict!(Git, "github.com/foo/bar", "abcd");
assert_eq!(expected, parsed);
assert_eq!(&parsed.to_string(), input);
}

#[test]
fn parse_render_successful() {
let input = "git+github.com/foo/bar$abcd";
Expand Down

0 comments on commit bb2bfb0

Please sign in to comment.