Skip to main content

public_api/
lib.rs

1//! This library gives you the public API of a library crate, in the form of a
2//! list of public items in the crate. Public items are items that other crates
3//! can use. Diffing is also supported.
4//!
5//! If you want a convenient CLI for this library, you should use [cargo
6//! public-api](https://github.com/cargo-public-api/cargo-public-api).
7//!
8//! As input to the library, a special output format from `rustdoc +nightly` is
9//! used, which goes by the name **rustdoc JSON**. Currently, only the nightly
10//! toolchain can build **rustdoc JSON**.
11//!
12//! You use the [`rustdoc-json`](https://crates.io/crates/rustdoc_json) library
13//! to programmatically build rustdoc JSON. See below for example code. To
14//! manually build rustdoc JSON you would typically do something like this:
15//! ```sh
16//! cargo +nightly rustdoc -- -Z unstable-options --output-format json
17//! ```
18//!
19//! # Examples
20//!
21//! The two main use cases are listing the public API and diffing different
22//! versions of the same public APIs.
23//!
24//! ## List all public items of a crate (the public API)
25//! ```no_run
26#![doc = include_str!("../examples/list_public_api.rs")]
27//! ```
28//!
29//! ## Diff two versions of a public API
30//! ```no_run
31#![doc = include_str!("../examples/diff_public_api.rs")]
32//! ```
33
34// deny in CI, only warn here
35#![warn(missing_docs)]
36
37mod crate_wrapper;
38mod error;
39mod intermediate_public_item;
40mod item_processor;
41mod nameable_item;
42mod path_component;
43mod public_item;
44mod render;
45pub mod tokens;
46
47pub mod diff;
48
49use std::path::PathBuf;
50
51// Documented at the definition site so cargo doc picks it up
52pub use error::{Error, Result};
53
54// Documented at the definition site so cargo doc picks it up
55pub use public_item::PublicItem;
56
57/// This constant defines the minimum version of nightly that is required in
58/// order for the rustdoc JSON output to be parsable by this library. Note that
59/// this library is implemented with stable Rust. But the rustdoc JSON that this
60/// library parses can currently only be produced by nightly.
61///
62/// The rustdoc JSON format is still changing, so every now and then we update
63/// this library to support the latest format. If you use this version of
64/// nightly or later, you should be fine.
65pub const MINIMUM_NIGHTLY_RUST_VERSION: &str = "nightly-2025-08-02";
66// End-marker for scripts/release-helper/src/bin/update-version-info/main.rs
67
68/// See [`Builder`] method docs for what each field means.
69#[derive(Copy, Clone, Debug)]
70struct BuilderOptions {
71    sorted: bool,
72    debug_sorting: bool,
73    omit_blanket_impls: bool,
74    omit_auto_trait_impls: bool,
75    omit_auto_derived_impls: bool,
76    include_function_parameter_names: bool,
77}
78
79/// Builds [`PublicApi`]s. See the [top level][`crate`] module docs for example
80/// code.
81#[derive(Debug, Clone)]
82pub struct Builder {
83    rustdoc_json: PathBuf,
84    options: BuilderOptions,
85}
86
87impl Builder {
88    /// Create a new [`PublicApi`] builder from a rustdoc JSON file. See the
89    /// [top level][`crate`] module docs for example code.
90    #[must_use]
91    pub fn from_rustdoc_json(path: impl Into<PathBuf>) -> Self {
92        let options = BuilderOptions {
93            sorted: true,
94            debug_sorting: false,
95            omit_blanket_impls: false,
96            omit_auto_trait_impls: false,
97            omit_auto_derived_impls: false,
98            include_function_parameter_names: false,
99        };
100        Self {
101            rustdoc_json: path.into(),
102            options,
103        }
104    }
105
106    /// If `true`, items will be sorted before being returned. If you will pass
107    /// on the return value to [`diff::PublicApiDiff::between`], it is
108    /// currently unnecessary to sort first, because the sorting will be
109    /// performed/ensured inside of that function.
110    ///
111    /// The default value is `true`, because usually the performance impact is
112    /// negligible, and is is generally more practical to work with sorted data.
113    #[must_use]
114    pub fn sorted(mut self, sorted: bool) -> Self {
115        self.options.sorted = sorted;
116        self
117    }
118
119    /// If `true`, item paths include the so called "sorting prefix" that makes
120    /// them grouped in a nice way. Only intended for debugging this library.
121    ///
122    /// The default value is `false`
123    #[must_use]
124    pub fn debug_sorting(mut self, debug_sorting: bool) -> Self {
125        self.options.debug_sorting = debug_sorting;
126        self
127    }
128
129    /// If `true`, items that belongs to Blanket Implementations are omitted
130    /// from the output. This makes the output less noisy, at the cost of not
131    /// fully describing the public API.
132    ///
133    /// Examples of Blanket Implementations: `impl<T> Any for T`, `impl<T>
134    /// Borrow<T> for T`, and `impl<T, U> Into<U> for T where U: From<T>`
135    ///
136    /// The default value is `false` so that the listed public API is complete
137    /// by default.
138    #[must_use]
139    pub fn omit_blanket_impls(mut self, omit_blanket_impls: bool) -> Self {
140        self.options.omit_blanket_impls = omit_blanket_impls;
141        self
142    }
143
144    /// If `true`, items that belongs to Auto Trait Implementations are omitted
145    /// from the output. This makes the output less noisy, at the cost of not
146    /// fully describing the public API.
147    ///
148    /// Examples of Auto Trait Implementations: `impl Send for Foo`, `impl Sync
149    /// for Foo`, and `impl Unpin for Foo`
150    ///
151    /// The default value is `false` so that the listed public API is complete
152    /// by default.
153    #[must_use]
154    pub fn omit_auto_trait_impls(mut self, omit_auto_trait_impls: bool) -> Self {
155        self.options.omit_auto_trait_impls = omit_auto_trait_impls;
156        self
157    }
158
159    /// If `true`, items that belongs to automatically derived implementations
160    /// (`Clone`, `Debug`, `Eq`, etc) are omitted from the output. This makes
161    /// the output less noisy, at the cost of not fully describing the public
162    /// API.
163    ///
164    /// The default value is `false` so that the listed public API is complete
165    /// by default.
166    #[must_use]
167    pub fn omit_auto_derived_impls(mut self, omit_auto_derived_impls: bool) -> Self {
168        self.options.omit_auto_derived_impls = omit_auto_derived_impls;
169        self
170    }
171
172    /// If `true`, function parameter names are included in the API output. They
173    /// are omitted by default to avoid spurious diffs when parameter names
174    /// change, but they can sometimes be helpful to include in the output.
175    #[must_use]
176    pub fn include_function_parameter_names(
177        mut self,
178        include_function_parameter_names: bool,
179    ) -> Self {
180        self.options.include_function_parameter_names = include_function_parameter_names;
181        self
182    }
183
184    /// Builds [`PublicApi`]. See the [top level][`crate`] module docs for
185    /// example code.
186    ///
187    /// # Errors
188    ///
189    /// E.g. if the [JSON](Builder::from_rustdoc_json) is invalid or if the file
190    /// can't be read.
191    pub fn build(self) -> Result<PublicApi> {
192        from_rustdoc_json_str(std::fs::read_to_string(self.rustdoc_json)?, self.options)
193    }
194}
195
196/// The public API of a crate
197///
198/// Create an instance with [`Builder`].
199///
200/// ## Rendering the items
201///
202/// To render the items in the public API you can iterate over the [items](PublicItem).
203///
204/// You get the `rustdoc_json_str` in the example below as explained in the [crate] documentation, either via
205/// [`rustdoc_json`](https://crates.io/crates/rustdoc_json) or by calling `cargo rustdoc` yourself.
206///
207/// ```no_run
208/// use public_api::PublicApi;
209/// use std::path::PathBuf;
210///
211/// # let rustdoc_json: PathBuf = todo!();
212/// // Gather the rustdoc content as described in this crates top-level documentation.
213/// let public_api = public_api::Builder::from_rustdoc_json(&rustdoc_json).build()?;
214///
215/// for public_item in public_api.items() {
216///     // here we print the items to stdout, we could also write to a string or a file.
217///     println!("{}", public_item);
218/// }
219///
220/// // If you want all items of the public API in a single big multi-line String then
221/// // you can do like this:
222/// let public_api_string = public_api.to_string();
223/// # Ok::<(), Box<dyn std::error::Error>>(())
224/// ```
225#[derive(Debug)]
226#[non_exhaustive] // More fields might be added in the future
227pub struct PublicApi {
228    /// The items that constitutes the public API. An "item" is for example a
229    /// function, a struct, a struct field, an enum, an enum variant, a module,
230    /// etc...
231    pub(crate) items: Vec<PublicItem>,
232
233    /// See [`Self::missing_item_ids()`]
234    pub(crate) missing_item_ids: Vec<u32>,
235}
236
237impl PublicApi {
238    /// Returns an iterator over all public items in the public API
239    pub fn items(&self) -> impl Iterator<Item = &'_ PublicItem> {
240        self.items.iter()
241    }
242
243    /// Like [`Self::items()`], but ownership of all `PublicItem`s are
244    /// transferred to the caller.
245    pub fn into_items(self) -> impl Iterator<Item = PublicItem> {
246        self.items.into_iter()
247    }
248
249    /// The rustdoc JSON IDs of missing but referenced items. Intended for use
250    /// with `--verbose` flags or similar.
251    ///
252    /// In some cases, a public item might be referenced from another public
253    /// item (e.g. a `mod`), but is missing from the rustdoc JSON file. This
254    /// occurs for example in the case of re-exports of external modules (see
255    /// <https://github.com/cargo-public-api/cargo-public-api/issues/103>). The entries
256    /// in this Vec are what IDs that could not be found.
257    ///
258    /// The exact format of IDs are to be considered an implementation detail
259    /// and must not be be relied on.
260    pub fn missing_item_ids(&self) -> impl Iterator<Item = &u32> {
261        self.missing_item_ids.iter()
262    }
263
264    /// Assert that the public API matches the snapshot at `snapshot_path`. The
265    /// function will panic with a helpful diff if the public API does not
266    /// match.
267    ///
268    /// If the env var `UPDATE_SNAPSHOTS` is set to `1`, `yes` or `true` then
269    /// the public API will be written to `snapshot_file` instead of being
270    /// asserted to match.
271    #[cfg(feature = "snapshot-testing")]
272    #[track_caller]
273    pub fn assert_eq_or_update(&self, snapshot_path: impl AsRef<std::path::Path>) {
274        snapshot_testing::assert_eq_or_update(self.to_string(), snapshot_path);
275    }
276}
277
278impl std::fmt::Display for PublicApi {
279    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280        for item in self.items() {
281            writeln!(f, "{item}")?;
282        }
283        Ok(())
284    }
285}
286
287fn from_rustdoc_json_str(
288    rustdoc_json_str: impl AsRef<str>,
289    options: BuilderOptions,
290) -> Result<PublicApi> {
291    let crate_ = deserialize_without_recursion_limit(rustdoc_json_str.as_ref())?;
292
293    let mut public_api = item_processor::public_api_in_crate(&crate_, options);
294
295    if options.sorted {
296        public_api.items.sort_by(PublicItem::grouping_cmp);
297    }
298
299    Ok(public_api)
300}
301
302/// Helper to deserialize the JSON with `serde_json`, but with the recursion
303/// limit disabled. Otherwise we hit the recursion limit on crates such as
304/// `diesel`.
305fn deserialize_without_recursion_limit(rustdoc_json_str: &str) -> Result<rustdoc_types::Crate> {
306    let mut deserializer = serde_json::Deserializer::from_str(rustdoc_json_str);
307    deserializer.disable_recursion_limit();
308    Ok(serde::de::Deserialize::deserialize(&mut deserializer)?)
309}