Switch all public types to non_exhaustive

This switches all public types to use non_exhaustive to make it easier
to make additions without a semver-breaking change.

Some of the ergonomics are hampered due to the lack of exhaustiveness
checking. Hopefully some day in the future,
non_exhaustive_omitted_patterns_lint or something like it will get
stabilized.

Closes https://github.com/rust-lang/mdBook/issues/1835
This commit is contained in:
Eric Huss 2025-08-09 16:38:22 -07:00
parent c25e866796
commit 5956092b4b
18 changed files with 90 additions and 122 deletions

View file

@ -9,6 +9,9 @@ members = [
all = { level = "allow", priority = -2 } all = { level = "allow", priority = -2 }
correctness = { level = "warn", priority = -1 } correctness = { level = "warn", priority = -1 }
complexity = { level = "warn", priority = -1 } complexity = { level = "warn", priority = -1 }
exhaustive_enums = "warn"
exhaustive_structs = "warn"
manual_non_exhaustive = "warn"
[workspace.lints.rust] [workspace.lints.rust]
missing_docs = "warn" missing_docs = "warn"

View file

@ -16,10 +16,10 @@ use std::path::PathBuf;
/// [`iter()`]: #method.iter /// [`iter()`]: #method.iter
/// [`for_each_mut()`]: #method.for_each_mut /// [`for_each_mut()`]: #method.for_each_mut
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Book { pub struct Book {
/// The sections in this book. /// The sections in this book.
pub sections: Vec<BookItem>, pub sections: Vec<BookItem>,
__non_exhaustive: (),
} }
impl Book { impl Book {
@ -30,10 +30,7 @@ impl Book {
/// Creates a new book with the given items. /// Creates a new book with the given items.
pub fn new_with_items(items: Vec<BookItem>) -> Book { pub fn new_with_items(items: Vec<BookItem>) -> Book {
Book { Book { sections: items }
sections: items,
__non_exhaustive: (),
}
} }
/// Get a depth-first iterator over the items in the book. /// Get a depth-first iterator over the items in the book.
@ -81,6 +78,7 @@ where
/// Enum representing any type of item which can be added to a book. /// Enum representing any type of item which can be added to a book.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum BookItem { pub enum BookItem {
/// A nested chapter. /// A nested chapter.
Chapter(Chapter), Chapter(Chapter),
@ -99,6 +97,7 @@ impl From<Chapter> for BookItem {
/// The representation of a "chapter", usually mapping to a single file on /// The representation of a "chapter", usually mapping to a single file on
/// disk however it may contain multiple sub-chapters. /// disk however it may contain multiple sub-chapters.
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Chapter { pub struct Chapter {
/// The chapter's name. /// The chapter's name.
pub name: String, pub name: String,

View file

@ -64,6 +64,7 @@ use toml::value::Table;
/// The overall configuration object for MDBook, essentially an in-memory /// The overall configuration object for MDBook, essentially an in-memory
/// representation of `book.toml`. /// representation of `book.toml`.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct Config { pub struct Config {
/// Metadata about the book. /// Metadata about the book.
pub book: BookConfig, pub book: BookConfig,
@ -386,6 +387,7 @@ fn is_legacy_format(table: &Value) -> bool {
/// loading it from disk. /// loading it from disk.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")] #[serde(default, rename_all = "kebab-case")]
#[non_exhaustive]
pub struct BookConfig { pub struct BookConfig {
/// The book's title. /// The book's title.
pub title: Option<String>, pub title: Option<String>,
@ -429,6 +431,7 @@ impl BookConfig {
/// Text direction to use for HTML output /// Text direction to use for HTML output
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum TextDirection { pub enum TextDirection {
/// Left to right. /// Left to right.
#[serde(rename = "ltr")] #[serde(rename = "ltr")]
@ -454,6 +457,7 @@ impl TextDirection {
/// Configuration for the build procedure. /// Configuration for the build procedure.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")] #[serde(default, rename_all = "kebab-case")]
#[non_exhaustive]
pub struct BuildConfig { pub struct BuildConfig {
/// Where to put built artefacts relative to the book's root directory. /// Where to put built artefacts relative to the book's root directory.
pub build_dir: PathBuf, pub build_dir: PathBuf,
@ -481,13 +485,15 @@ impl Default for BuildConfig {
/// Configuration for the Rust compiler(e.g., for playground) /// Configuration for the Rust compiler(e.g., for playground)
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")] #[serde(default, rename_all = "kebab-case")]
#[non_exhaustive]
pub struct RustConfig { pub struct RustConfig {
/// Rust edition used in playground /// Rust edition used in playground
pub edition: Option<RustEdition>, pub edition: Option<RustEdition>,
} }
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
/// Rust edition to use for the code. /// Rust edition to use for the code.
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum RustEdition { pub enum RustEdition {
/// The 2024 edition of Rust /// The 2024 edition of Rust
#[serde(rename = "2024")] #[serde(rename = "2024")]
@ -506,6 +512,7 @@ pub enum RustEdition {
/// Configuration for the HTML renderer. /// Configuration for the HTML renderer.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")] #[serde(default, rename_all = "kebab-case")]
#[non_exhaustive]
pub struct HtmlConfig { pub struct HtmlConfig {
/// The theme directory, if specified. /// The theme directory, if specified.
pub theme: Option<PathBuf>, pub theme: Option<PathBuf>,
@ -625,6 +632,7 @@ impl HtmlConfig {
/// Configuration for how to render the print icon, print.html, and print.css. /// Configuration for how to render the print icon, print.html, and print.css.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")] #[serde(default, rename_all = "kebab-case")]
#[non_exhaustive]
pub struct Print { pub struct Print {
/// Whether print support is enabled. /// Whether print support is enabled.
pub enable: bool, pub enable: bool,
@ -644,6 +652,7 @@ impl Default for Print {
/// Configuration for how to fold chapters of sidebar. /// Configuration for how to fold chapters of sidebar.
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")] #[serde(default, rename_all = "kebab-case")]
#[non_exhaustive]
pub struct Fold { pub struct Fold {
/// When off, all folds are open. Default: `false`. /// When off, all folds are open. Default: `false`.
pub enable: bool, pub enable: bool,
@ -656,6 +665,7 @@ pub struct Fold {
/// Configuration for tweaking how the HTML renderer handles the playground. /// Configuration for tweaking how the HTML renderer handles the playground.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")] #[serde(default, rename_all = "kebab-case")]
#[non_exhaustive]
pub struct Playground { pub struct Playground {
/// Should playground snippets be editable? Default: `false`. /// Should playground snippets be editable? Default: `false`.
pub editable: bool, pub editable: bool,
@ -685,6 +695,7 @@ impl Default for Playground {
/// Configuration for tweaking how the HTML renderer handles code blocks. /// Configuration for tweaking how the HTML renderer handles code blocks.
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")] #[serde(default, rename_all = "kebab-case")]
#[non_exhaustive]
pub struct Code { pub struct Code {
/// A prefix string to hide lines per language (one or more chars). /// A prefix string to hide lines per language (one or more chars).
pub hidelines: HashMap<String, String>, pub hidelines: HashMap<String, String>,
@ -693,6 +704,7 @@ pub struct Code {
/// Configuration of the search functionality of the HTML renderer. /// Configuration of the search functionality of the HTML renderer.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")] #[serde(default, rename_all = "kebab-case")]
#[non_exhaustive]
pub struct Search { pub struct Search {
/// Enable the search feature. Default: `true`. /// Enable the search feature. Default: `true`.
pub enable: bool, pub enable: bool,
@ -750,6 +762,7 @@ impl Default for Search {
/// Search options for chapters (or paths). /// Search options for chapters (or paths).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(default, rename_all = "kebab-case")] #[serde(default, rename_all = "kebab-case")]
#[non_exhaustive]
pub struct SearchChapterSettings { pub struct SearchChapterSettings {
/// Whether or not indexing is enabled, default `true`. /// Whether or not indexing is enabled, default `true`.
pub enable: Option<bool>, pub enable: Option<bool>,

View file

@ -8,6 +8,7 @@ use std::{path::Path, sync::LazyLock};
/// A preprocessor for converting file name `README.md` to `index.md` since /// A preprocessor for converting file name `README.md` to `index.md` since
/// `README.md` is the de facto index file in markdown-based documentation. /// `README.md` is the de facto index file in markdown-based documentation.
#[derive(Default)] #[derive(Default)]
#[non_exhaustive]
pub struct IndexPreprocessor; pub struct IndexPreprocessor;
impl IndexPreprocessor { impl IndexPreprocessor {

View file

@ -26,6 +26,7 @@ const MAX_LINK_NESTED_DEPTH: usize = 10;
/// - `{{# playground}}` - Insert runnable Rust files /// - `{{# playground}}` - Insert runnable Rust files
/// - `{{# title}}` - Override \<title\> of a webpage. /// - `{{# title}}` - Override \<title\> of a webpage.
#[derive(Default)] #[derive(Default)]
#[non_exhaustive]
pub struct LinkPreprocessor; pub struct LinkPreprocessor;
impl LinkPreprocessor { impl LinkPreprocessor {

View file

@ -5,9 +5,10 @@ use mdbook_core::utils;
use mdbook_renderer::{RenderContext, Renderer}; use mdbook_renderer::{RenderContext, Renderer};
use std::fs; use std::fs;
#[derive(Default)]
/// A renderer to output the Markdown after the preprocessors have run. Mostly useful /// A renderer to output the Markdown after the preprocessors have run. Mostly useful
/// when debugging preprocessors. /// when debugging preprocessors.
#[derive(Default)]
#[non_exhaustive]
pub struct MarkdownRenderer; pub struct MarkdownRenderer;
impl MarkdownRenderer { impl MarkdownRenderer {

View file

@ -95,6 +95,7 @@ fn load_summary_item<P: AsRef<Path> + Clone>(
SummaryItem::Separator => Ok(BookItem::Separator), SummaryItem::Separator => Ok(BookItem::Separator),
SummaryItem::Link(link) => load_chapter(link, src_dir, parent_names).map(BookItem::Chapter), SummaryItem::Link(link) => load_chapter(link, src_dir, parent_names).map(BookItem::Chapter),
SummaryItem::PartTitle(title) => Ok(BookItem::PartTitle(title.clone())), SummaryItem::PartTitle(title) => Ok(BookItem::PartTitle(title.clone())),
_ => panic!("SummaryItem {item:?} not covered"),
} }
} }
@ -252,28 +253,21 @@ And here is some \
fn load_recursive_link_with_separators() { fn load_recursive_link_with_separators() {
let (root, temp) = nested_links(); let (root, temp) = nested_links();
let nested = Chapter { let mut nested = Chapter::new(
name: String::from("Nested Chapter 1"), "Nested Chapter 1",
content: String::from("Hello World!"), String::from("Hello World!"),
number: Some(SectionNumber::new([1, 2])), "second.md",
path: Some(PathBuf::from("second.md")), vec![String::from("Chapter 1")],
source_path: Some(PathBuf::from("second.md")), );
parent_names: vec![String::from("Chapter 1")], nested.number = Some(SectionNumber::new([1, 2]));
sub_items: Vec::new(), let mut chapter =
}; Chapter::new("Chapter 1", String::from(DUMMY_SRC), "chapter_1.md", vec![]);
let should_be = BookItem::Chapter(Chapter { chapter.sub_items = vec![
name: String::from("Chapter 1"),
content: String::from(DUMMY_SRC),
number: None,
path: Some(PathBuf::from("chapter_1.md")),
source_path: Some(PathBuf::from("chapter_1.md")),
parent_names: Vec::new(),
sub_items: vec![
BookItem::Chapter(nested.clone()), BookItem::Chapter(nested.clone()),
BookItem::Separator, BookItem::Separator,
BookItem::Chapter(nested), BookItem::Chapter(nested),
], ];
}); let should_be = BookItem::Chapter(chapter);
let got = load_summary_item(&SummaryItem::Link(root), temp.path(), Vec::new()).unwrap(); let got = load_summary_item(&SummaryItem::Link(root), temp.path(), Vec::new()).unwrap();
assert_eq!(got, should_be); assert_eq!(got, should_be);
@ -282,17 +276,15 @@ And here is some \
#[test] #[test]
fn load_a_book_with_a_single_chapter() { fn load_a_book_with_a_single_chapter() {
let (link, temp) = dummy_link(); let (link, temp) = dummy_link();
let summary = Summary { let mut summary = Summary::default();
numbered_chapters: vec![SummaryItem::Link(link)], summary.numbered_chapters = vec![SummaryItem::Link(link)];
..Default::default() let chapter = Chapter::new(
}; "Chapter 1",
let sections = vec![BookItem::Chapter(Chapter { String::from(DUMMY_SRC),
name: String::from("Chapter 1"), PathBuf::from("chapter_1.md"),
content: String::from(DUMMY_SRC), vec![],
path: Some(PathBuf::from("chapter_1.md")), );
source_path: Some(PathBuf::from("chapter_1.md")), let sections = vec![BookItem::Chapter(chapter)];
..Default::default()
})];
let should_be = Book::new_with_items(sections); let should_be = Book::new_with_items(sections);
let got = load_book_from_disk(&summary, temp.path()).unwrap(); let got = load_book_from_disk(&summary, temp.path()).unwrap();
@ -303,16 +295,9 @@ And here is some \
#[test] #[test]
fn cant_load_chapters_with_an_empty_path() { fn cant_load_chapters_with_an_empty_path() {
let (_, temp) = dummy_link(); let (_, temp) = dummy_link();
let summary = Summary { let mut summary = Summary::default();
numbered_chapters: vec![SummaryItem::Link(Link { let link = Link::new("Empty", "");
name: String::from("Empty"), summary.numbered_chapters = vec![SummaryItem::Link(link)];
location: Some(PathBuf::from("")),
..Default::default()
})],
..Default::default()
};
let got = load_book_from_disk(&summary, temp.path()); let got = load_book_from_disk(&summary, temp.path());
assert!(got.is_err()); assert!(got.is_err());
} }
@ -323,14 +308,9 @@ And here is some \
let dir = temp.path().join("nested"); let dir = temp.path().join("nested");
fs::create_dir(&dir).unwrap(); fs::create_dir(&dir).unwrap();
let summary = Summary { let mut summary = Summary::default();
numbered_chapters: vec![SummaryItem::Link(Link { let link = Link::new("nested", dir);
name: String::from("nested"), summary.numbered_chapters = vec![SummaryItem::Link(link)];
location: Some(dir),
..Default::default()
})],
..Default::default()
};
let got = load_book_from_disk(&summary, temp.path()); let got = load_book_from_disk(&summary, temp.path());
assert!(got.is_err()); assert!(got.is_err());

View file

@ -136,6 +136,7 @@ impl MDBook {
/// BookItem::Chapter(ref chapter) => {}, /// BookItem::Chapter(ref chapter) => {},
/// BookItem::Separator => {}, /// BookItem::Separator => {},
/// BookItem::PartTitle(ref title) => {} /// BookItem::PartTitle(ref title) => {}
/// _ => {}
/// } /// }
/// } /// }
/// ///
@ -329,6 +330,7 @@ impl MDBook {
RustEdition::E2024 => { RustEdition::E2024 => {
cmd.args(["--edition", "2024"]); cmd.args(["--edition", "2024"]);
} }
_ => panic!("RustEdition {edition:?} not covered"),
} }
} }

View file

@ -230,7 +230,7 @@ fn config_respects_preprocessor_selection() {
let cfg = Config::from_str(cfg_str).unwrap(); let cfg = Config::from_str(cfg_str).unwrap();
let html_renderer = HtmlHandlebars; let html_renderer = HtmlHandlebars::default();
let pre = LinkPreprocessor::new(); let pre = LinkPreprocessor::new();
let should_run = preprocessor_should_run(&pre, &html_renderer, &cfg).unwrap(); let should_run = preprocessor_should_run(&pre, &html_renderer, &cfg).unwrap();

View file

@ -21,6 +21,7 @@ use std::sync::LazyLock;
/// The HTML renderer for mdBook. /// The HTML renderer for mdBook.
#[derive(Default)] #[derive(Default)]
#[non_exhaustive]
pub struct HtmlHandlebars; pub struct HtmlHandlebars;
impl HtmlHandlebars { impl HtmlHandlebars {
@ -656,6 +657,7 @@ fn make_data(
BookItem::Separator => { BookItem::Separator => {
chapter.insert("spacer".to_owned(), json!("_spacer_")); chapter.insert("spacer".to_owned(), json!("_spacer_"));
} }
_ => panic!("BookItem {item:?} not covered"),
} }
chapters.push(chapter); chapters.push(chapter);
@ -778,6 +780,7 @@ fn add_playground_pre(
Some(RustEdition::E2018) => " edition2018", Some(RustEdition::E2018) => " edition2018",
Some(RustEdition::E2021) => " edition2021", Some(RustEdition::E2021) => " edition2021",
Some(RustEdition::E2024) => " edition2024", Some(RustEdition::E2024) => " edition2024",
Some(_) => panic!("edition {edition:?} not covered"),
None => "", None => "",
} }
}; };
@ -1085,14 +1088,9 @@ mod tests {
), ),
]; ];
for (src, should_be) in &inputs { for (src, should_be) in &inputs {
let got = add_playground_pre( let mut p = Playground::default();
src, p.editable = true;
&Playground { let got = add_playground_pre(src, &p, None);
editable: true,
..Playground::default()
},
None,
);
assert_eq!(&*got, *should_be); assert_eq!(&*got, *should_be);
} }
} }
@ -1117,14 +1115,9 @@ mod tests {
), ),
]; ];
for (src, should_be) in &inputs { for (src, should_be) in &inputs {
let got = add_playground_pre( let mut p = Playground::default();
src, p.editable = true;
&Playground { let got = add_playground_pre(src, &p, Some(RustEdition::E2015));
editable: true,
..Playground::default()
},
Some(RustEdition::E2015),
);
assert_eq!(&*got, *should_be); assert_eq!(&*got, *should_be);
} }
} }
@ -1149,14 +1142,9 @@ mod tests {
), ),
]; ];
for (src, should_be) in &inputs { for (src, should_be) in &inputs {
let got = add_playground_pre( let mut p = Playground::default();
src, p.editable = true;
&Playground { let got = add_playground_pre(src, &p, Some(RustEdition::E2018));
editable: true,
..Playground::default()
},
Some(RustEdition::E2018),
);
assert_eq!(&*got, *should_be); assert_eq!(&*got, *should_be);
} }
} }
@ -1181,14 +1169,9 @@ mod tests {
), ),
]; ];
for (src, should_be) in &inputs { for (src, should_be) in &inputs {
let got = add_playground_pre( let mut p = Playground::default();
src, p.editable = true;
&Playground { let got = add_playground_pre(src, &p, Some(RustEdition::E2021));
editable: true,
..Playground::default()
},
Some(RustEdition::E2021),
);
assert_eq!(&*got, *should_be); assert_eq!(&*got, *should_be);
} }
} }
@ -1248,17 +1231,10 @@ mod tests {
"<code class=\"language-python hidelines=!!!\"><span class=\"boring\">hidden()\n</span>nothidden():\n<span class=\"boring\"> hidden()\n</span><span class=\"boring\"> hidden()\n</span> nothidden()\n</code>", "<code class=\"language-python hidelines=!!!\"><span class=\"boring\">hidden()\n</span>nothidden():\n<span class=\"boring\"> hidden()\n</span><span class=\"boring\"> hidden()\n</span> nothidden()\n</code>",
), ),
]; ];
let mut code = Code::default();
code.hidelines.insert("python".to_string(), "~".to_string());
for (src, should_be) in &inputs { for (src, should_be) in &inputs {
let got = hide_lines( let got = hide_lines(src, &code);
src,
&Code {
hidelines: {
let mut map = HashMap::new();
map.insert("python".to_string(), "~".to_string());
map
},
},
);
assert_eq!(&*got, *should_be); assert_eq!(&*got, *should_be);
} }
} }

View file

@ -409,9 +409,11 @@ fn chapter_settings_priority() {
("cli/inner/index.md", Some(true)), ("cli/inner/index.md", Some(true)),
("cli/inner/foo.md", Some(false)), ("cli/inner/foo.md", Some(false)),
] { ] {
let mut settings = SearchChapterSettings::default();
settings.enable = enable;
assert_eq!( assert_eq!(
get_chapter_settings(&chapter_configs, Path::new(path)), get_chapter_settings(&chapter_configs, Path::new(path)),
SearchChapterSettings { enable } settings
); );
} }
} }

View file

@ -49,6 +49,7 @@ pub static FONT_AWESOME_OTF: &[u8] = include_bytes!("../../front-end/fonts/FontA
/// You should only ever use the static variables directly if you want to /// You should only ever use the static variables directly if you want to
/// override the user's theme with the defaults. /// override the user's theme with the defaults.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
#[non_exhaustive]
pub struct Theme { pub struct Theme {
pub index: Vec<u8>, pub index: Vec<u8>,
pub head: Vec<u8>, pub head: Vec<u8>,

View file

@ -47,6 +47,7 @@ pub trait Preprocessor {
/// Extra information for a `Preprocessor` to give them more context when /// Extra information for a `Preprocessor` to give them more context when
/// processing a book. /// processing a book.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct PreprocessorContext { pub struct PreprocessorContext {
/// The location of the book directory on disk. /// The location of the book directory on disk.
pub root: PathBuf, pub root: PathBuf,
@ -62,8 +63,6 @@ pub struct PreprocessorContext {
/// This should not be used outside of mdbook's internals. /// This should not be used outside of mdbook's internals.
#[serde(skip)] #[serde(skip)]
pub chapter_titles: RefCell<HashMap<PathBuf, String>>, pub chapter_titles: RefCell<HashMap<PathBuf, String>>,
#[serde(skip)]
__non_exhaustive: (),
} }
impl PreprocessorContext { impl PreprocessorContext {
@ -75,7 +74,6 @@ impl PreprocessorContext {
renderer, renderer,
mdbook_version: crate::MDBOOK_VERSION.to_string(), mdbook_version: crate::MDBOOK_VERSION.to_string(),
chapter_titles: RefCell::new(HashMap::new()), chapter_titles: RefCell::new(HashMap::new()),
__non_exhaustive: (),
} }
} }
} }

View file

@ -36,6 +36,7 @@ pub trait Renderer {
/// The context provided to all renderers. /// The context provided to all renderers.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct RenderContext { pub struct RenderContext {
/// Which version of `mdbook` did this come from (as written in `mdbook`'s /// Which version of `mdbook` did this come from (as written in `mdbook`'s
/// `Cargo.toml`). Useful if you know the renderer is only compatible with /// `Cargo.toml`). Useful if you know the renderer is only compatible with
@ -57,8 +58,6 @@ pub struct RenderContext {
/// This should not be used outside of mdbook's internals. /// This should not be used outside of mdbook's internals.
#[serde(skip)] #[serde(skip)]
pub chapter_titles: HashMap<PathBuf, String>, pub chapter_titles: HashMap<PathBuf, String>,
#[serde(skip)]
__non_exhaustive: (),
} }
impl RenderContext { impl RenderContext {
@ -75,7 +74,6 @@ impl RenderContext {
root: root.into(), root: root.into(),
destination: destination.into(), destination: destination.into(),
chapter_titles: HashMap::new(), chapter_titles: HashMap::new(),
__non_exhaustive: (),
} }
} }

View file

@ -63,6 +63,7 @@ pub fn parse_summary(summary: &str) -> Result<Summary> {
/// The parsed `SUMMARY.md`, specifying how the book should be laid out. /// The parsed `SUMMARY.md`, specifying how the book should be laid out.
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Summary { pub struct Summary {
/// An optional title for the `SUMMARY.md`, currently just ignored. /// An optional title for the `SUMMARY.md`, currently just ignored.
pub title: Option<String>, pub title: Option<String>,
@ -79,6 +80,7 @@ pub struct Summary {
/// ///
/// This is roughly the equivalent of `[Some section](./path/to/file.md)`. /// This is roughly the equivalent of `[Some section](./path/to/file.md)`.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Link { pub struct Link {
/// The name of the chapter. /// The name of the chapter.
pub name: String, pub name: String,
@ -114,8 +116,9 @@ impl Default for Link {
} }
} }
/// An item in `SUMMARY.md` which could be either a separator or a `Link`. /// An item in `SUMMARY.md`.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum SummaryItem { pub enum SummaryItem {
/// A link to a chapter. /// A link to a chapter.
Link(Link), Link(Link),

View file

@ -146,8 +146,7 @@ mod nop_lib {
"parent_names": [] "parent_names": []
} }
} }
], ]
"__non_exhaustive": null
} }
]"##; ]"##;
let input_json = input_json.as_bytes(); let input_json = input_json.as_bytes();

View file

@ -167,7 +167,6 @@ fn backends_receive_render_context_via_stdin() {
str![[r##" str![[r##"
{ {
"book": { "book": {
"__non_exhaustive": null,
"sections": [ "sections": [
{ {
"Chapter": { "Chapter": {

View file

@ -3,7 +3,7 @@
use crate::prelude::*; use crate::prelude::*;
use mdbook_core::book::{BookItem, Chapter}; use mdbook_core::book::{BookItem, Chapter};
use snapbox::file; use snapbox::file;
use std::path::{Path, PathBuf}; use std::path::Path;
fn read_book_index(root: &Path) -> serde_json::Value { fn read_book_index(root: &Path) -> serde_json::Value {
let index = root.join("book/searchindex.js"); let index = root.join("book/searchindex.js");
@ -116,15 +116,7 @@ fn can_disable_individual_chapters() {
fn with_no_source_path() { fn with_no_source_path() {
let test = BookTest::from_dir("search/reasonable_search_index"); let test = BookTest::from_dir("search/reasonable_search_index");
let mut book = test.load_book(); let mut book = test.load_book();
let chapter = Chapter { let chapter = Chapter::new("Sample chapter", String::new(), "sample.html", vec![]);
name: "Sample chapter".to_string(),
content: "".to_string(),
number: None,
sub_items: Vec::new(),
path: Some(PathBuf::from("sample.html")),
source_path: None,
parent_names: Vec::new(),
};
book.book.sections.push(BookItem::Chapter(chapter)); book.book.sections.push(BookItem::Chapter(chapter));
book.build().unwrap(); book.build().unwrap();
} }