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
100// #1238: lets `redact_debug::RedactingDebug`'s generated code say `deps_core::redact_debug::…`
101// unconditionally, whether the derive is used by an external consumer or by a type defined
102// inside this crate itself (e.g. a future `lsp_helpers::git_ref::ResolvedShaPin` migration) —
103// a crate has no external name for itself without this alias.
104extern crate self as deps_core;
105
106/// HTTP response cache with `ETag`/`Last-Modified` conditional-request validation.
107pub mod cache;
108/// Bounded-`DashMap` capacity policies shared by [`cache`], [`github`], [`deps_dev`], and
109/// [`osv`].
110///
111/// `pub`, so ecosystem crates (`deps-gitlab-ci`, `deps-github-actions`, `deps-npm`) reach it
112/// directly too.
113pub mod cache_policy;
114#[cfg(feature = "lsp-responses")]
115pub mod completion;
116/// Config-value trust tiers and `${VAR}`/`%VAR%` env-var interpolation (issue #1434).
117///
118/// Shared by every ecosystem whose own config format supports it.
119pub mod config_trust;
120/// Shared `#[macro_export]`ed conformance-test scaffolding (#758).
121///
122/// Ecosystem crates invoke these macros instead of hand-copying the same test family. Gated
123/// identically to [`test_util`].
124#[cfg(any(test, feature = "test-util"))]
125pub mod conformance;
126/// Per-document ceiling on tracked dependencies, enforced by
127/// [`ecosystem::parse_manifest_blocking`] for all 14 ecosystems (#796).
128pub mod dependency_cap;
129pub mod deps_dev;
130/// Protocol-agnostic [`diagnostic::Diagnostic`]/[`diagnostic::Severity`] types (issue #1083).
131///
132/// The domain-level replacement for `tower_lsp_server::ls_types::{Diagnostic,
133/// DiagnosticSeverity, DiagnosticRelatedInformation, CodeDescription}` returned by
134/// [`lsp_helpers::generate_diagnostics_from_cache`] and
135/// [`ecosystem::Ecosystem::generate_diagnostics`].
136pub mod diagnostic;
137/// The [`ecosystem::Ecosystem`] trait: the sealed extension point every package
138/// ecosystem crate implements to plug into the LSP server.
139pub mod ecosystem;
140/// Routes a manifest path to its owning [`ecosystem::Ecosystem`] implementation.
141pub mod ecosystem_registry;
142pub mod edit;
143/// Unified error types (`DepsError`, `FetchFailure`) shared across ecosystems.
144pub mod error;
145pub mod fallback_completion;
146pub mod freshness;
147pub mod fs_probe;
148pub mod github;
149/// Protocol-agnostic [`hover::Hover`] type (issue #1277).
150///
151/// The domain-level replacement for `tower_lsp_server::ls_types::Hover` returned by
152/// [`lsp_helpers::generate_hover`] and [`ecosystem::Ecosystem::generate_hover`]. Gated
153/// behind the `lsp-responses` feature, unlike [`diagnostic`] — see this module's own doc
154/// for why.
155#[cfg(feature = "lsp-responses")]
156#[cfg_attr(docsrs, doc(cfg(feature = "lsp-responses")))]
157pub mod hover;
158/// Bounded property/variable values for manifest interpolation (issue #1481).
159///
160/// Shared by `deps-maven` (`${property}`) and `deps-gradle` (`version.ref`/`$name`/
161/// `${name}`) to bound retained resolution memory.
162pub mod interpolation;
163/// Shared bracket-interval version-range grammar (`[1.0,2.0)`-shaped), used by
164/// `deps-maven`, `deps-gradle`, and `deps-nuget` (#821).
165pub mod interval;
166pub mod json_ast;
167pub mod json_helpers;
168pub mod licenses;
169pub mod lockfile;
170pub mod lsp_helpers;
171pub mod macros;
172pub mod matched_spans;
173pub mod mtime_cache;
174pub mod net_policy;
175pub mod osv;
176pub mod package;
177pub mod pagination;
178/// Shared manifest-parsing helpers: bounded JSON/TOML/YAML nesting checks and
179/// depth-limited parsing used by every ecosystem's manifest parser.
180pub mod parser;
181pub mod path_segment;
182/// The policy-relevant subset of `deps-lsp`'s configuration.
183///
184/// Diagnostics severities, cache, freshness, supply-chain, registries, network, license
185/// policy — shared with `deps-cli` so both compose the same type instead of each parsing its
186/// own copy.
187pub mod policy_config;
188/// Protocol-agnostic [`position::Position`]/[`position::Range`] types (issue #1071).
189///
190/// The domain-level replacement for `tower_lsp_server::ls_types::{Position, Range}` in
191/// [`ecosystem::Dependency`]'s range accessors.
192pub mod position;
193/// Shared escape-aware string-literal and comment scanning.
194///
195/// [`quote_scan::read_string_literal`], [`quote_scan::strip_line_comment`],
196/// [`quote_scan::blank_comments`], [`quote_scan::is_code_byte`],
197/// [`quote_scan::last_string_literal`], and [`quote_scan::find_closing_quote_before_comment`],
198/// parameterized over [`quote_scan::ScanSyntax`] — used by `deps-bundler`, `deps-swift`,
199/// `deps-pypi`, and `deps-gradle` instead of each hand-rolling its own scanner (#1022,
200/// #1174).
201pub mod quote_scan;
202/// Local, process-lifetime rate-limit short-circuiting.
203///
204/// [`rate_limit::RateLimitGate`] is shared by `deps-github-actions` and `deps-gitlab-ci`.
205pub mod rate_limit;
206/// Redaction: URL/declaration-key/parse-error text and in-memory secret values (issue #1247).
207///
208/// Extracted from [`net_policy`], which kept the old paths as re-exports.
209pub mod redact;
210/// Compile-time-enforced `Debug` redaction: the [`redact_debug::RedactingDebug`] derive
211/// (issue #1238).
212pub mod redact_debug;
213/// The [`registry::Registry`] trait: version lookup and search that every
214/// ecosystem's registry client implements.
215pub mod registry;
216/// Re-export of [`redact::secret`] at its pre-#1247 path.
217pub use redact::secret;
218/// [`selection::SelectionContext`]/[`selection::StabilityFloor`]: manifest-scoped
219/// selection state (#1444).
220pub mod selection;
221#[cfg(any(test, feature = "test-util"))]
222pub mod test_util;
223pub mod version_matcher;
224pub mod xml_bounds;
225pub mod yaml_anchor;
226pub mod yaml_walk;
227
228/// Re-export of the LSP protocol types that appear in this crate's public trait
229/// signatures. `deps-core`'s version tracks `tower-lsp-server`'s: a
230/// `tower-lsp-server` bump is a breaking change here, by construction. See the
231/// "LSP type stability" section of this module's docs for detail.
232///
233/// Only present when the `lsp-responses` feature is enabled — see that feature's own
234/// doc comment in `Cargo.toml` for what it gates and why.
235#[cfg(feature = "lsp-responses")]
236pub use tower_lsp_server;
237
238pub use cache::{BodyLimit, CacheMode, CachedResponse, HttpCache, NetworkMode};
239pub use dependency_cap::{DependencyBudget, MAX_DEPENDENCIES_PER_DOCUMENT};
240pub use deps_dev::{
241    DepsDevClient, FetchCompleteness, GossipCooldown, GossipFindings, GossipLowUsage,
242    GossipRiskLevel, ProvenanceStatus, ScorecardSummary, SupplyChainTrustSignal, TyposquatSignal,
243};
244pub use ecosystem::{
245    BlockedRegistryOccurrence, BlockedSourceClass, Dependency, Ecosystem, EcosystemConfig,
246    EcosystemId, LicenseSource, ParseResult, RegistryOccurrence, RejectedRegistryOccurrence,
247    RejectedSourceClass, parse_manifest_blocking, parse_manifest_now,
248};
249pub use ecosystem_registry::EcosystemRegistry;
250pub use edit::{
251    EcosystemReparse, EditSpan, ManifestEdit, ManifestReparse, PlannedUpdate, UnplannableReason,
252    UpdateCandidate, UpdateKind, VulnFixSkip, apply_edits, classify_update,
253    collect_update_candidates, collect_update_edits, dedup_overlapping_edits, plan_verified_fix,
254    plan_vulnerability_fix, resolve_recommended_fix, resolve_verified_fix,
255};
256pub use error::{DepsError, FetchFailure, RateLimitEvidence, Result};
257pub use freshness::{
258    CooldownWindow, DEFAULT_COOLDOWN_SECS, FreshnessSettings, PublishTime, format_relative_age,
259};
260pub use interpolation::{
261    MAX_INTERPOLATED_VALUE_BYTES, OversizedPropertyValue, PropertyValue, insert_bounded,
262};
263pub use json_ast::{JsonAst, JsonSection, find_last_prop};
264pub use json_helpers::string_valued_entries;
265pub use licenses::{
266    LicensePolicy, LicenseViolation, MAX_POM_LICENSE_NAME_RAW_CHARS, ViolationReason,
267    evaluate as evaluate_license_policy, resolve_license_entries,
268};
269pub use lockfile::{
270    LockFileProvider, ResolvedPackage, ResolvedPackages, ResolvedSource, read_and_parse_lockfile,
271    read_lockfile_content,
272};
273pub use lsp_helpers::{
274    DependencyOutcome, DependencyOutcomes, DiagnosticMessages, DiagnosticPolicy,
275    DiagnosticSeverities, EcosystemFormatter, HOVER_RECENT_VERSIONS,
276    LICENSE_POLICY_VIOLATION_DIAGNOSTIC_CODE, LineOffsetTable, OsvNaming, PackageNaming,
277    PackageRendering, PackageVersions, RequirementMatcher, RequirementResolution,
278    RequirementStatus, SourcePolicy, UNSATISFIABLE_DIAGNOSTIC_CODE, VersionData, is_dot_segment,
279    is_safe_feature_name, is_safe_maven_coordinate_segment, is_safe_package_name,
280    is_safe_registry_url, is_safe_version_string, is_same_major_minor, maven_coordinate_path,
281    position_in_range, requirement_is_unsatisfiable, warn_rejected_value,
282};
283/// LSP-response-shaped re-exports, present only when the `lsp-responses` feature is enabled.
284#[cfg(feature = "lsp-responses")]
285pub use lsp_helpers::{
286    collect_update_all_edits, generate_code_actions as lsp_generate_code_actions,
287    generate_code_lenses as lsp_generate_code_lenses, generate_hover as lsp_generate_hover,
288    generate_inlay_hints as lsp_generate_inlay_hints, single_file_edit, to_ls_uri,
289};
290pub use matched_spans::MatchedSpans;
291pub use mtime_cache::{DEFAULT_MAX_CACHED_FILES, MAX_CACHED_FILE_BYTES, MtimeFileCache};
292pub use package::{ConcreteVersion, InvalidPackageName, PackageName, VersionReq};
293pub use parser::{
294    CheckedTomlError, DependencySource, LoadingState, MAX_JSON_NESTING_DEPTH,
295    MAX_TOML_NESTING_DEPTH, MAX_YAML_EXPANDED_BYTES, MAX_YAML_NESTING_DEPTH,
296    check_json_nesting_depth, check_toml_nesting_depth, check_yaml_bounds, check_yaml_expansion,
297    check_yaml_nesting_depth, json_depth_error_message, parse_json_checked, parse_toml_checked,
298    yaml_scalar_string,
299};
300pub use path_segment::{DotSegmentError, SafePathSegment, SegmentedPathName};
301pub use position::{Position, Range};
302pub use registry::{
303    Deprecation, Metadata, Registry, RemovalStatus, Version, classify_default_registry_url,
304    existence_wildcard_req, find_latest_stable, has_default_prerelease_marker, hash_routing_key,
305    is_existence_wildcard, is_existence_wildcard_str, not_found_or, select_latest_for_existence,
306};
307pub use selection::{
308    InvalidStabilityOccurrence, SelectionContext, StabilityFloor, UnknownStability,
309};
310pub use version_matcher::{
311    Pep440Matcher, SemverMatcher, VersionRequirementMatcher, extract_pypi_min_version,
312    normalize_and_parse_version, normalize_operator_spacing,
313};
314
315/// Re-exported so a caller of [`yaml_scalar_string`] can name `yaml_rust2::Yaml` without
316/// depending on `yaml-rust2` directly — see the "API stability" section above for the
317/// semver implication of this pre-1.0 dependency coupling.
318pub use yaml_rust2;
319
320/// Re-exported so a caller of [`error::SanitizedRegistryError`]'s `From<reqwest::Error>`
321/// impl can name `reqwest::Error` without depending on `reqwest` directly — see the "API
322/// stability" section above for the semver implication of this pre-1.0 dependency coupling.
323pub use reqwest;