Skip to main content

deps_core/
lib.rs

1// rustc's default recursion limit can't always prove the `Send` bound on the boxed futures
2// `impl_dependency!`/`impl_version!` generate, turning a silent trait-solver retry into a hard
3// error under `-D warnings` (rust-lang/rust#159228). Same fix as deps-cargo #745, deps-nuget
4// #696, deps-swift #673.
5#![recursion_limit = "256"]
6
7//! Core abstractions for deps-lsp.
8//!
9//! This crate provides the foundational traits and utilities used across
10//! all ecosystem-specific implementations (Cargo, npm, PyPI, etc.).
11//!
12//! # Architecture
13//!
14//! deps-core defines:
15//! - **Traits**: `Registry`, `Version`, `Metadata`, `Ecosystem`, `ParseResult`
16//! - **HTTP Cache**: Shared caching layer with ETag/Last-Modified validation
17//! - **Error Types**: Unified error handling across all ecosystems
18//!
19//! # API stability (issue #769)
20//!
21//! Most public structs and enums here are `#[non_exhaustive]` so a new field or variant
22//! never breaks a downstream ecosystem crate's exhaustive match or struct literal. This is a
23//! per-kind decision, not one policy for both: on a **struct** with no `pub` fields,
24//! `#[non_exhaustive]` is a semantic no-op (an external crate can't literal-construct or
25//! destructure it either way), so such structs are deliberately left exhaustive instead. On
26//! an **enum**, `#[non_exhaustive]` always forces a downstream `match` to carry a wildcard
27//! arm, regardless of any variant's field visibility — each enum opts in when that forced
28//! wildcard is wanted and stays exhaustive when a future variant should instead be a
29//! compile error downstream; see the individual enum's own doc comment for which applies.
30//!
31//! A few pre-1.0 dependency types remain visible in public signatures as a deliberate
32//! semver commitment rather than an oversight:
33//! - [`error::SanitizedRegistryError`] implements `From<reqwest::Error>` — a `reqwest`
34//!   major bump that reshapes `Error` is a breaking change for this crate too.
35//! - [`parser::yaml_scalar_string`] takes `&yaml_rust2::Yaml`; `yaml_rust2` is re-exported
36//!   from the crate root so callers can name the type without their own direct dependency,
37//!   and a `yaml-rust2` major bump is likewise a breaking change here.
38//! - `deps-gitlab-ci`'s `gitlab_version_req` (`crates/deps-gitlab-ci/src/component.rs`)
39//!   returns `semver::VersionReq` directly — low risk since `semver` is unusually stable
40//!   pre-1.0, but the same commitment applies.
41//! - [`cache_policy`] takes/returns `&dashmap::DashMap<K, V>` in its public functions; this
42//!   is not new coupling — [`registry::register_capped_with_occupied`] already exposes
43//!   `&DashMap` publicly without a `pub use dashmap` — so a `dashmap` major bump is already
44//!   a breaking change here regardless of this module's visibility.
45//!
46//! ## LSP type stability (issue #832, narrowed by #1071 and #1083)
47//!
48//! `tower-lsp-server` is pinned pre-1.0, so a `tower-lsp-server` minor bump (e.g. 0.23 →
49//! 0.24) is not an implementation detail this crate can absorb silently — it forces a
50//! breaking release of `deps-core`: a minor version bump while `deps-core` itself remains
51//! pre-1.0, a major version bump once `deps-core` reaches 1.0. This section describes which
52//! parts of the public API still carry that coupling.
53//!
54//! As of issue #1071, a dependency's own *domain* data never carried it in the first place:
55//! [`ecosystem::Dependency`]'s range accessors return [`position::Range`], and
56//! [`ecosystem::ParseResult::uri`] / [`ecosystem::Ecosystem::parse_manifest`] use `url::Url`
57//! — neither names a `tower-lsp-server` type.
58//!
59//! As of issue #1083, the remaining *response*-shaped surface — `generate_hover`,
60//! `generate_diagnostics` (retyped to the protocol-agnostic [`diagnostic::Diagnostic`], not
61//! merely gated), `generate_code_actions`, `generate_code_lenses`, `generate_inlay_hints`,
62//! `generate_document_links`, `generate_completions`/`complete_version`/
63//! `complete_package_name`/`complete_feature`, and the [`completion`] module itself — is
64//! gated behind the `lsp-responses` Cargo feature, along with the `tower-lsp-server`
65//! dependency it requires. The feature is **not** part of this crate's `default` set:
66//! Cargo does not allow a `workspace = true` dependency edge to turn off a feature that a
67//! crate defaults on, so making it default here would make it inescapable for `deps-engine`
68//! (see `deps-engine/Cargo.toml`'s own `lsp-responses` feature doc) — every consumer that
69//! wants it, including each of the 14 `deps-<ecosystem>` crates via their own
70//! identically-named feature, must request `deps-core/lsp-responses` explicitly (`deps-lsp`
71//! does, directly and via `deps-engine`; `deps-cli` never does). Under `--no-default-features`
72//! (or with `lsp-responses` otherwise off), `deps-core` links no `tower-lsp-server` code at
73//! all, [`tower_lsp_server`] is not re-exported, and an ecosystem crate's `Ecosystem` impl
74//! simply doesn't declare the gated trait methods (the trait itself omits them when the
75//! feature is off, so there is nothing to implement) — this is FR-001/SC-001's mechanism for
76//! keeping `deps-cli`'s dependency tree free of `tower-lsp-server` entirely (verified by
77//! `cargo tree -p deps-cli -e features,no-dev` in CI).
78//!
79//! Downstream consumers implementing [`ecosystem::Ecosystem`] with `lsp-responses` enabled
80//! should depend on the exact matching `tower-lsp-server` version through the
81//! [`tower_lsp_server`] re-export rather than adding their own separate direct dependency,
82//! which could otherwise drift out of sync with the version `deps-core` was built against.
83//! `deps-core`'s own `lsp_helpers` converts a [`position::Position`]/[`position::Range`] into
84//! its `ls_types` equivalent internally via their `From` impls wherever one must be embedded
85//! in a gated response.
86//!
87//! Whether the *rest* of `deps-core`'s public API should stop naming third-party dependency
88//! types generally (`reqwest::Error`, `yaml_rust2::Yaml`, ...) is issue #851's broader, still-
89//! open question — out of scope for #1071/#1083.
90
91// #673: re-enables the cast-safety pedantic lints the workspace allows by default, since this
92// crate computes LSP offset/position math from attacker-influenceable input. Confirmed-safe
93// sites get an individual `#[allow]` with justification, not a blanket allow.
94#![warn(
95    clippy::cast_possible_truncation,
96    clippy::cast_precision_loss,
97    clippy::cast_sign_loss
98)]
99/// HTTP response cache with `ETag`/`Last-Modified` conditional-request validation.
100pub mod cache;
101/// Bounded-`DashMap` capacity policies shared by [`cache`], [`github`], [`deps_dev`], and
102/// [`osv`].
103///
104/// `pub`, so ecosystem crates (`deps-gitlab-ci`, `deps-github-actions`, `deps-npm`) reach it
105/// directly too.
106pub mod cache_policy;
107#[cfg(feature = "lsp-responses")]
108pub mod completion;
109/// Shared `#[macro_export]`ed conformance-test scaffolding (#758).
110///
111/// Ecosystem crates invoke these macros instead of hand-copying the same test family. Gated
112/// identically to [`test_util`].
113#[cfg(any(test, feature = "test-util"))]
114pub mod conformance;
115/// Per-document ceiling on tracked dependencies, enforced by
116/// [`ecosystem::parse_manifest_blocking`] for all 14 ecosystems (#796).
117pub mod dependency_cap;
118pub mod deps_dev;
119/// Protocol-agnostic [`diagnostic::Diagnostic`]/[`diagnostic::Severity`] types (issue #1083).
120///
121/// The domain-level replacement for `tower_lsp_server::ls_types::{Diagnostic,
122/// DiagnosticSeverity, DiagnosticRelatedInformation, CodeDescription}` returned by
123/// [`lsp_helpers::generate_diagnostics_from_cache`] and
124/// [`ecosystem::Ecosystem::generate_diagnostics`].
125pub mod diagnostic;
126/// The [`ecosystem::Ecosystem`] trait: the sealed extension point every package
127/// ecosystem crate implements to plug into the LSP server.
128pub mod ecosystem;
129/// Routes a manifest path to its owning [`ecosystem::Ecosystem`] implementation.
130pub mod ecosystem_registry;
131/// Unified error types (`DepsError`, `FetchFailure`) shared across ecosystems.
132pub mod error;
133pub mod fallback_completion;
134pub mod freshness;
135pub mod fs_probe;
136pub mod github;
137/// Shared bracket-interval version-range grammar (`[1.0,2.0)`-shaped), used by
138/// `deps-maven`, `deps-gradle`, and `deps-nuget` (#821).
139pub mod interval;
140pub mod json_ast;
141pub mod json_helpers;
142pub mod licenses;
143pub mod lockfile;
144pub mod lsp_helpers;
145pub mod macros;
146pub mod mtime_cache;
147pub mod net_policy;
148pub mod osv;
149pub mod package;
150pub mod pagination;
151/// Shared manifest-parsing helpers: bounded JSON/TOML/YAML nesting checks and
152/// depth-limited parsing used by every ecosystem's manifest parser.
153pub mod parser;
154/// The policy-relevant subset of `deps-lsp`'s configuration.
155///
156/// Diagnostics severities, cache, freshness, supply-chain, registries, network, license
157/// policy — shared with `deps-cli` so both compose the same type instead of each parsing its
158/// own copy.
159pub mod policy_config;
160/// Protocol-agnostic [`position::Position`]/[`position::Range`] types (issue #1071).
161///
162/// The domain-level replacement for `tower_lsp_server::ls_types::{Position, Range}` in
163/// [`ecosystem::Dependency`]'s range accessors.
164pub mod position;
165/// Shared escape-aware string-literal and comment scanning.
166///
167/// [`quote_scan::read_string_literal`], [`quote_scan::strip_line_comment`],
168/// [`quote_scan::blank_comments`], [`quote_scan::is_code_byte`],
169/// [`quote_scan::last_string_literal`], and [`quote_scan::find_closing_quote_before_comment`],
170/// parameterized over [`quote_scan::ScanSyntax`] — used by `deps-bundler`, `deps-swift`,
171/// `deps-pypi`, and `deps-gradle` instead of each hand-rolling its own scanner (#1022,
172/// #1174).
173pub mod quote_scan;
174/// Local, process-lifetime rate-limit short-circuiting.
175///
176/// [`rate_limit::RateLimitGate`] is shared by `deps-github-actions` and `deps-gitlab-ci`.
177pub mod rate_limit;
178/// The [`registry::Registry`] trait: version lookup and search that every
179/// ecosystem's registry client implements.
180pub mod registry;
181pub mod secret;
182#[cfg(any(test, feature = "test-util"))]
183pub mod test_util;
184pub mod version_matcher;
185pub mod xml_bounds;
186pub mod yaml_anchor;
187pub mod yaml_walk;
188
189/// Re-export of the LSP protocol types that appear in this crate's public trait
190/// signatures. `deps-core`'s version tracks `tower-lsp-server`'s: a
191/// `tower-lsp-server` bump is a breaking change here, by construction. See the
192/// "LSP type stability" section of this module's docs for detail.
193///
194/// Only present when the `lsp-responses` feature is enabled — see that feature's own
195/// doc comment in `Cargo.toml` for what it gates and why.
196#[cfg(feature = "lsp-responses")]
197pub use tower_lsp_server;
198
199pub use cache::{BodyLimit, CachedResponse, HttpCache};
200pub use dependency_cap::{DependencyBudget, MAX_DEPENDENCIES_PER_DOCUMENT};
201pub use deps_dev::{DepsDevClient, ProvenanceStatus, ScorecardSummary, SupplyChainTrustSignal};
202pub use ecosystem::{
203    BlockedRegistryOccurrence, BlockedSourceClass, Dependency, Ecosystem, EcosystemConfig,
204    EcosystemId, LicenseSource, ParseResult, parse_manifest_blocking,
205};
206pub use ecosystem_registry::EcosystemRegistry;
207pub use error::{DepsError, FetchFailure, Result};
208pub use freshness::{
209    DEFAULT_COOLDOWN_SECS, FreshnessSettings, PublishTime, format_relative_age, is_within_cooldown,
210};
211pub use json_ast::{JsonAst, JsonSection, find_last_prop};
212pub use json_helpers::string_valued_entries;
213pub use licenses::{
214    LicensePolicy, LicenseViolation, MAX_POM_LICENSE_NAME_RAW_CHARS, ViolationReason,
215    evaluate as evaluate_license_policy, resolve_license_entries,
216};
217pub use lockfile::{
218    LockFileProvider, ResolvedPackage, ResolvedPackages, ResolvedSource, read_and_parse_lockfile,
219    read_lockfile_content,
220};
221pub use lsp_helpers::{
222    DependencyOutcome, DependencyOutcomes, DiagnosticMessages, DiagnosticPolicy,
223    DiagnosticSeverities, EcosystemFormatter, HOVER_RECENT_VERSIONS,
224    LICENSE_POLICY_VIOLATION_DIAGNOSTIC_CODE, LineOffsetTable, OsvNaming, PackageNaming,
225    PackageRendering, PackageVersions, RequirementMatcher, RequirementResolution,
226    RequirementStatus, SourcePolicy, UNSATISFIABLE_DIAGNOSTIC_CODE, VersionData, is_dot_segment,
227    is_safe_maven_coordinate_segment, is_safe_package_name, is_safe_registry_url,
228    is_safe_version_string, is_same_major_minor, maven_coordinate_path, position_in_range,
229    requirement_is_unsatisfiable, warn_rejected_value,
230};
231/// LSP-response-shaped re-exports, present only when the `lsp-responses` feature is enabled.
232#[cfg(feature = "lsp-responses")]
233pub use lsp_helpers::{
234    collect_update_all_edits, generate_code_actions as lsp_generate_code_actions,
235    generate_code_lenses as lsp_generate_code_lenses, generate_hover as lsp_generate_hover,
236    generate_inlay_hints as lsp_generate_inlay_hints, single_file_edit, to_ls_uri,
237};
238pub use mtime_cache::{DEFAULT_MAX_CACHED_FILES, MAX_CACHED_FILE_BYTES, MtimeFileCache};
239pub use package::{ConcreteVersion, InvalidPackageName, PackageName, VersionReq};
240pub use parser::{
241    DependencySource, LoadingState, MAX_JSON_NESTING_DEPTH, MAX_TOML_NESTING_DEPTH,
242    MAX_YAML_EXPANDED_BYTES, MAX_YAML_NESTING_DEPTH, check_json_nesting_depth,
243    check_toml_nesting_depth, check_yaml_expansion, check_yaml_nesting_depth,
244    json_depth_error_message, parse_json_checked, yaml_scalar_string,
245};
246pub use position::{Position, Range};
247pub use registry::{
248    Deprecation, Metadata, Registry, RemovalStatus, Version, classify_default_registry_url,
249    existence_wildcard_req, find_latest_stable, has_default_prerelease_marker, hash_routing_key,
250    is_existence_wildcard, is_existence_wildcard_str, not_found_or, select_latest_for_existence,
251};
252pub use version_matcher::{
253    Pep440Matcher, SemverMatcher, VersionRequirementMatcher, extract_pypi_min_version,
254    normalize_and_parse_version, normalize_operator_spacing,
255};
256
257/// Re-exported so a caller of [`yaml_scalar_string`] can name `yaml_rust2::Yaml` without
258/// depending on `yaml-rust2` directly — see the "API stability" section above for the
259/// semver implication of this pre-1.0 dependency coupling.
260pub use yaml_rust2;
261
262/// Re-exported so a caller of [`error::SanitizedRegistryError`]'s `From<reqwest::Error>`
263/// impl can name `reqwest::Error` without depending on `reqwest` directly — see the "API
264/// stability" section above for the semver implication of this pre-1.0 dependency coupling.
265pub use reqwest;