Skip to content

Commit

Permalink
Fix up docs
Browse files Browse the repository at this point in the history
  • Loading branch information
jssblck committed Oct 21, 2024
1 parent b54b403 commit 620308b
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 90 deletions.
20 changes: 13 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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 {
Expand Down
112 changes: 53 additions & 59 deletions src/locator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)]
Expand Down Expand Up @@ -189,55 +195,43 @@ impl Locator {

/// Parse a `Locator`.
/// For details, see the parsing section on [`Locator`].
pub fn parse(locator: &str) -> Result<Self, Error> {
pub fn parse(input: &str) -> Result<Self, Error> {
/// 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 {
Expand Down
49 changes: 33 additions & 16 deletions src/locator_package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)]
Expand Down
38 changes: 30 additions & 8 deletions src/locator_strict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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,
)]
Expand Down

0 comments on commit 620308b

Please sign in to comment.