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 draft of proposed new library API. #600

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
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
190 changes: 190 additions & 0 deletions src/proposed_new_lib_api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CheckGroup {
/// All the checks we should run.
checks: Vec<CrateCheck>,
}

#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CrateCheck {
/// The version we are interested in semver-checking.
current_version: CrateVersion,

/// The version we are comparing the current version against.
baseline_version: CrateVersion,

/// Optionally, override what kind of release this is and therefore which lints we check.
///
/// If `None`, we'll determine the appropriate lints based on the version numbers
/// of the current and baseline crate versions.
assume_release_type: Option<ReleaseType>
}

#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CrateVersion {
/// The name of the crate we're checking.
crate_name: String,

/// Where are we getting the source code for this crate version from?
source: Source,

/// Which group of features to enable: all features, default features, no features,
/// or use our own heuristic that aims to select all intended-to-be-public features.
///
/// TODO: move `FeaturesGroup` out of `rustdoc_gen.rs` and make it `pub` and `#[non_exhaustive]`
features_group: FeaturesGroup,

/// Additional features to enable, on top of the ones selected by `features_group`.
extra_features: Vec<String>,

/// What to do if the specified version has no library target, only binary targets.
///
/// In this situation, there's no API to semver-check since this version
/// cannot be used as a lib dependency, regardless of which APIs are declared `pub`.
if_package_has_no_library_target: OnUnexpectedOutcome,

/// What to do if a requested feature does not exist.
if_feature_does_not_exist: OnUnexpectedOutcome,

/// What to do if we failed to generate or parse rustdoc.
///
/// For example, if the package fails to compile with the current Rust version and rustflags.
if_failed_to_get_rustdoc: OnUnexpectedOutcome,
}

#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Source {
/// Load the crate's data from local source code.
ManifestPath(ManifestPath),

/// Load the crate's data from a specific git revision in a local repo.
ManifestGitRevision(ManifestGitRevision),

/// Load the crate's data from a specific version on crates.io.
ExactRegistryVersion(ExactRegistryVersion),

/// Load the crate's data by selecting a version from crates.io based on specific requirements.
PredicateRegistryVersion(PredicateRegistryVersion),
}

#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ManifestPath {
/// The `Cargo.toml` of the workspace containing the crate to check,
/// or the crate's own `Cargo.toml` if the crate isn't part of a workspace.
root_manifest: std::path::PathBuf,

/// If we failed to open, read, or parse the root manifest file at the specified path.
if_failed_to_read_root_manifest: OnUnexpectedOutcome,

/// If we opened the manifest but did not find the specified package inside it.
if_failed_to_find_package: OnUnexpectedOutcome,
}

#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ManifestGitRevision {
/// Path to the repository's root directory.
repo: std::path::PathBuf,

/// The git revision we should check out and use for semver-checking.
rev: String,

/// Path (relative to the repo root) to the `Cargo.toml` of the workspace
/// containing the crate to check, or to the crate's own `Cargo.toml`
/// if the crate isn't part of a workspace.
root_manifest: std::path::PathBuf,

/// If we failed to use the git repo and check out the revision.
/// For example, if the repo path is incorrect or the specified revision doesn't exist.
if_failed_to_check_out_rev: OnUnexpectedOutcome
obi1kenobi marked this conversation as resolved.
Show resolved Hide resolved

/// If we failed to open, read, or parse the root manifest file at the specified path.
if_failed_to_read_root_manifest: OnUnexpectedOutcome,

/// If we opened the manifest but did not find the specified package inside it.
/// For example, perhaps the package does not yet exist at the specified revision.
if_failed_to_find_package: OnUnexpectedOutcome,
}

#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExactRegistryVersion {
/// The crate version to check.
version: semver::Version,

/// If we aren't able to use the registry, for example due to a connection error.
if_failed_to_use_registry: OnUnexpectedOutcome,

/// If we connected to the registry but could not find the specified package.
if_failed_to_find_package: OnUnexpectedOutcome,

/// If the registry knows about the package, but does not have the specified version.
if_failed_to_find_package_version: OnUnexpectedOutcome,

/// If the registry has the specified version but it is not usable for semver-checking:
/// for example, if it has been yanked (we won't be able to generate rustdoc for it).
if_version_is_unusable: OnUnexpectedOutcome,
}

#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PredicateRegistryVersion {
/// The way to select the version.
predicate: VersionPredicate,

/// Whether to consider pre-release versions or not.
///
/// Note that breaking changes are always allowed between pre-releases,
/// and between a pre-release and a corresponding normal release.
allow_prereleases: bool,

/// Whether we should automatically ignore yanked releases.
ignore_yanked: bool,

/// If we aren't able to use the registry, for example due to a connection error.
if_failed_to_use_registry: OnUnexpectedOutcome,

/// If we connected to the registry but could not find the specified package.
if_failed_to_find_package: OnUnexpectedOutcome,

/// If the registry knows about the package, but none of its versions match our requirements.
if_no_matching_version: OnUnexpectedOutcome,

/// If a version matched our requirements, but it is not usable for semver-checking:
/// for example, if it has been yanked (we won't be able to generate rustdoc for it).
if_version_is_unusable: OnUnexpectedOutcome,
}

#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VersionPredicate {
/// Strictly less than the specified version.
LessThan(semver::Version),

/// Less than or equal than the specified version.
LessThanOrEqual(semver::Version),

/// The largest version number available.
HighestAvailable,

/// The last release ordered by release date.
MostRecentlyPublished.
}

#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OnUnexpectedOutcome {
/// Stop running checks and return an error.
FailStop,

/// Log an error but continue with other checks.
LogAndContinue,

/// Continue with other checks as if nothing happened.
/// Do not print anything to stderr.
ContinueSilently,
}
Comment on lines +178 to +190
Copy link
Sponsor Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative to this design would be to model the internals of cargo semver-checks as a state machine that a binary or other consumer needs to manually advance. That is a bit more work to integrate but gives you greater flexibility. In other words, the library part would only offer building blocks that you can compose together with your own policy.

For example, if said state machine yields an event like:

struct WorkspaceCrateDiscovered {
	pub publish: bool,
	pub name: String
	// ...
}

We would also need APIs like:

pub fn load_crate_from_registry(name: &str) -> Result<...> { }

pub fn check_semver(base: &CrateData, new: &CrateData) -> Result<impl Iterator<Item = SemverViolation>, ...> {}

Upon encountering a WorkspaceCrateDiscovered event a user could then e.g. decide whether or not to continue checking the crate for semver-violations based on the publish field.

Loading