diff --git a/src/lib.rs b/src/lib.rs index 69b68eb..6712296 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,13 +22,7 @@ pub use locator::*; pub use locator_package::*; pub use locator_strict::*; -/// [`Locator`](crate::Locator) is closely tied with the concept of Core's "fetchers", -/// which are asynchronous jobs tasked with downloading the code -/// referred to by a [`Locator`](crate::Locator) so that Core or some other service -/// may analyze it. -/// -/// For more information on the background of `Locator` and fetchers generally, -/// refer to [Fetchers and Locators](https://go/fetchers-doc). +/// `Fetcher` identifies a supported code host protocol. #[derive( Copy, Clone, @@ -251,6 +245,14 @@ impl std::fmt::Debug for OrgId { } /// The package section of the locator. +/// +/// A "package" is generally the name of a project or dependency in a code host. +/// However some fetcher protocols (such as `git`) embed additional information +/// inside the `Package` of a locator, such as the URL of the `git` instance +/// from which the project can be fetched. +/// +/// Additionally, some fetcher protocols (such as `apk`, `rpm-generic`, and `deb`) +/// further encode additional standardized information in the `Package` of the locator. #[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Documented, ToSchema)] pub struct Package(String); @@ -304,6 +306,10 @@ impl std::cmp::PartialOrd for Package { } /// The revision section of the locator. +/// +/// A "revision" is the version of the project in the code host. +/// Some fetcher protocols (such as `apk`, `rpm-generic`, and `deb`) +/// encode additional standardized information in the `Revision` of the locator. #[derive(Clone, Eq, PartialEq, Hash, Documented, ToSchema)] #[schema(examples(json!("1.0.0"), json!("2.0.0-alpha.1"), json!("abcd1234")))] pub enum Revision { diff --git a/src/locator.rs b/src/locator.rs index 1d72828..b2d0590 100644 --- a/src/locator.rs +++ b/src/locator.rs @@ -86,55 +86,61 @@ macro_rules! locator_regex { }; } -/// Core, and most services that interact with Core, -/// refer to open source packages via the `Locator` type. +/// `Locator` identifies a package, optionally at a specific revision, in a code host. /// -/// This type is nearly universally rendered to a string -/// before being serialized to the database or sent over the network. +/// If the `revision` component is not specified, FOSSA services interpret this to mean +/// that the "latest" version of the package should be used if the requested operation +/// requires a concrete version of the package. /// -/// This type represents a _validly-constructed_ `Locator`, but does not -/// validate whether a `Locator` is actually valid. This means that a -/// given `Locator` is guaranteed to be correctly formatted data, -/// but that the actual repository or revision to which the `Locator` -/// refers is _not_ guaranteed to exist or be accessible. -/// Currently the canonical method for validating whether a given `Locator` is -/// accessible is to run it through the Core fetcher system. +/// ## Guarantees /// -/// For more information on the background of `Locator` and fetchers generally, -/// FOSSA employees may refer to -/// [Fetchers and Locators](https://go/fetchers-doc). +/// This type represents a _validly-constructed_ `Locator`, but does not +/// guarantee whether a package or revision actually exists or is accessible +/// in the code host. /// /// ## Ordering /// -/// Locators order by: +/// `Locator` orders by: /// 1. Fetcher, alphanumerically. /// 2. Organization ID, alphanumerically; missing organizations are sorted higher. /// 3. The package field, alphanumerically. /// 4. The revision field: -/// If both comparing locators use semver, these are compared using semver rules; -/// otherwise these are compared alphanumerically. -/// Missing revisions are sorted higher. +/// - If both comparing locators use semver, these are compared using semver rules. +/// - Otherwise these are compared alphanumerically. +/// - Missing revisions are sorted higher. /// -/// Importantly, there may be other metrics for ordering using the actual code host -/// which contains the package (for example, ordering by release date). -/// This library does not perform such ordering. +/// **Important:** there may be other metrics for ordering using the actual code host +/// which contains the package- for example ordering by release date, or code hosts +/// such as `git` which have non-linear history (making flat ordering a lossy operation). +/// `Locator` does not take such edge cases into account in any way. /// /// ## Parsing /// -/// The input string must be in one of the following forms: -/// - `{fetcher}+{package}` -/// - `{fetcher}+{package}$` -/// - `{fetcher}+{package}${revision}` +/// This type is canonically rendered to a string before being serialized +/// to the database or sent over the network according to the rules in this section. +/// +/// The input string must be in one of the following formats: +/// ```ignore +/// {fetcher}+{package}${revision} +/// {fetcher}+{package} +/// ``` /// /// Packages may also be namespaced to a specific organization; /// in such cases the organization ID is at the start of the `{package}` field /// separated by a slash. The ID can be any non-negative integer. -/// This yields the following formats: -/// - `{fetcher}+{org_id}/{package}` -/// - `{fetcher}+{org_id}/{package}$` -/// - `{fetcher}+{org_id}/{package}${revision}` +/// This yields the following optional formats: +/// ```ignore +/// {fetcher}+{org_id}/{package}${revision} +/// {fetcher}+{org_id}/{package} +/// ``` /// -/// This parse function is based on the function used in FOSSA Core for maximal compatibility. +/// Note that locators do not feature escaping: instead the _first_ instance +/// of each delimiter (`+`, `/`, `$`) is used to split the fields. However, +/// as a special case organization IDs are only extracted if the field content +/// fully consists of a non-negative integer. +// +// For more information on the background of `Locator` and fetchers generally, +// FOSSA employees may refer to the "fetchers and locators" doc: https://go/fetchers-doc. #[derive( Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Builder, Getters, CopyGetters, Documented, )] @@ -189,55 +195,43 @@ impl Locator { /// Parse a `Locator`. /// For details, see the parsing section on [`Locator`]. - pub fn parse(locator: &str) -> Result { + pub fn parse(input: &str) -> Result { + /// Convenience macro for fatal errors without needing to type out all the `.into()`s. macro_rules! fatal { - (syntax; $inner:expr) => { - ParseError::Syntax { - input: $inner.into(), - } - }; - (field => $name:expr; $inner:expr) => { - ParseError::Field { - input: $inner.into(), - field: $name.into(), + ($type:ident => $input:expr) => { + ParseError::$type { + input: $input.into(), } }; - (fetcher => $fetcher:expr, error => $err:expr; $inner:expr) => { - ParseError::Fetcher { - input: $inner.into(), - fetcher: $fetcher.into(), - error: $err.into(), - } - }; - (package => $package:expr, error => $err:expr; $inner:expr) => { - ParseError::Package { - input: $inner.into(), - package: $package.into(), - error: $err.into(), + ($type:ident => $input:expr, $($key:ident: $value:expr),+) => { + ParseError::$type { + input: $input.into(), + $($key: $value.into()),*, } }; } + /// Convenience macro for early returns. macro_rules! bail { ($($tt:tt)*) => { return Err(Error::from(fatal!($($tt)*))) }; } - let Some((_, fetcher, package, revision)) = locator_regex!(parse => locator) else { - bail!(syntax; locator); + let Some((_, fetcher, package, revision)) = locator_regex!(parse => input) else { + bail!(Syntax => input); }; if fetcher.is_empty() { - bail!(field => "fetcher"; locator); + bail!(Field => input, field: "fetcher"); } - let fetcher = Fetcher::try_from(fetcher) - .map_err(|err| fatal!(fetcher => fetcher, error => err; locator))?; - if package.is_empty() { - bail!(field => "package"; locator); + bail!(Field => input, field: "package"); } + let fetcher = Fetcher::try_from(fetcher) + .map_err(|err| fatal!(Fetcher => input, fetcher: fetcher, error: err))?; + let revision = if revision.is_empty() { None } else { diff --git a/src/locator_package.rs b/src/locator_package.rs index 1ae615e..4d243cf 100644 --- a/src/locator_package.rs +++ b/src/locator_package.rs @@ -39,38 +39,55 @@ macro_rules! package { }; } -/// A [`Locator`] specialized to not include the `revision` component. +/// `PackageLocator` identifies a package in a code host. /// -/// Any [`Locator`] may be converted to a `PackageLocator` by simply discarding the `revision` component. -/// To create a [`Locator`] from a `PackageLocator`, the value for `revision` must be provided; see [`Locator`] for details. +/// "Package" locators are similar to standard locators, except that they +/// _never specify_ the `revision` field. If the `revision` field +/// is provided in the input string, `PackageLocator` ignores it. +/// +/// ## Guarantees +/// +/// This type represents a _validly-constructed_ `PackageLocator`, but does not +/// guarantee whether a package actually exists or is accessible in the code host. /// /// ## Ordering /// -/// Locators order by: +/// `PackageLocator` orders by: /// 1. Fetcher, alphanumerically. /// 2. Organization ID, alphanumerically; missing organizations are sorted higher. /// 3. The package field, alphanumerically. /// -/// Importantly, there may be other metrics for ordering using the actual code host -/// which contains the package (for example, ordering by release date). -/// This library does not perform such ordering. +/// **Important:** there may be other metrics for ordering using the actual code host +/// which contains the package- for example ordering by release date. +/// `PackageLocator` does not take such edge cases into account in any way. /// /// ## Parsing /// -/// The input string must be in one of the following forms: -/// - `{fetcher}+{package}` -/// - `{fetcher}+{package}$` -/// - `{fetcher}+{package}${revision}` +/// This type is canonically rendered to a string before being serialized +/// to the database or sent over the network according to the rules in this section. +/// +/// The input string must be in one of the following formats: +/// ```ignore +/// {fetcher}+{package}${revision} +/// {fetcher}+{package} +/// ``` /// /// Packages may also be namespaced to a specific organization; /// in such cases the organization ID is at the start of the `{package}` field /// separated by a slash. The ID can be any non-negative integer. -/// This yields the following formats: -/// - `{fetcher}+{org_id}/{package}` -/// - `{fetcher}+{org_id}/{package}$` -/// - `{fetcher}+{org_id}/{package}${revision}` +/// This yields the following optional formats: +/// ```ignore +/// {fetcher}+{org_id}/{package}${revision} +/// {fetcher}+{org_id}/{package} +/// ``` /// -/// This implementation ignores the `revision` segment if it exists. If this is not preferred, use [`Locator`] instead. +/// Note that locators do not feature escaping: instead the _first_ instance +/// of each delimiter (`+`, `/`, `$`) is used to split the fields. However, +/// as a special case organization IDs are only extracted if the field content +/// fully consists of a non-negative integer. +// +// For more information on the background of `Locator` and fetchers generally, +// FOSSA employees may refer to the "fetchers and locators" doc: https://go/fetchers-doc. #[derive( Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Builder, Getters, CopyGetters, Documented, )] diff --git a/src/locator_strict.rs b/src/locator_strict.rs index e223bb1..9046ec9 100644 --- a/src/locator_strict.rs +++ b/src/locator_strict.rs @@ -41,24 +41,38 @@ macro_rules! strict { }; } -/// A [`Locator`] specialized to **require** the `revision` component. +/// `StrictLocator` identifies a package at a specific revision in a code host. +/// +/// "Strict" locators are similar to standard locators, except that they +/// _require_ the `revision` field to be specified. If the `revision` field +/// is not specified, `StrictLocator` fails to parse. +/// +/// ## Guarantees +/// +/// This type represents a _validly-constructed_ `StrictLocator`, but does not +/// guarantee whether a package or revision actually exists or is accessible +/// in the code host. /// /// ## Ordering /// -/// Locators order by: +/// `StrictLocator` orders by: /// 1. Fetcher, alphanumerically. /// 2. Organization ID, alphanumerically; missing organizations are sorted higher. /// 3. The package field, alphanumerically. /// 4. The revision field: -/// If both comparing locators use semver, these are compared using semver rules; -/// otherwise these are compared alphanumerically. +/// - If both comparing locators use semver, these are compared using semver rules. +/// - Otherwise these are compared alphanumerically. /// -/// Importantly, there may be other metrics for ordering using the actual code host -/// which contains the package (for example, ordering by release date). -/// This library does not perform such ordering. +/// **Important:** there may be other metrics for ordering using the actual code host +/// which contains the package- for example ordering by release date, or code hosts +/// such as `git` which have non-linear history (making flat ordering a lossy operation). +/// `StrictLocator` does not take such edge cases into account in any way. /// /// ## Parsing /// +/// This type is canonically rendered to a string before being serialized +/// to the database or sent over the network according to the rules in this section. +/// /// The input string must be in the following format: /// ```ignore /// {fetcher}+{package}${revision} @@ -67,10 +81,18 @@ macro_rules! strict { /// Packages may also be namespaced to a specific organization; /// in such cases the organization ID is at the start of the `{package}` field /// separated by a slash. The ID can be any non-negative integer. -/// This yields the following format: +/// This yields the following optional format: /// ```ignore /// {fetcher}+{org_id}/{package}${revision} /// ``` +/// +/// Note that locators do not feature escaping: instead the _first_ instance +/// of each delimiter (`+`, `/`, `$`) is used to split the fields. However, +/// as a special case organization IDs are only extracted if the field content +/// fully consists of a non-negative integer. +// +// For more information on the background of `Locator` and fetchers generally, +// FOSSA employees may refer to the "fetchers and locators" doc: https://go/fetchers-doc. #[derive( Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Builder, Getters, CopyGetters, Documented, )]