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