Repository: rust-lang/annotate-snippets-rs
Pull request #94 (2024-03-11 21:30:27)

fix!: Clarify Snippet API
This is a batch API change from talking to @Muscraft about what would make the clearer, what will make it more easily adapt to rustc, and what will make it better encoded the design in the type system.

Renames:
- `Snippet` -> `Message`
- `Slice` -> `Snippet`
  - `Slice::new` to `Snippet::source`
- `SourceAnnotation` -> `Annotation`
- `AnnotationType` -> `Level`

Other breaking changes:
- `Snippet::line_start` is now defaulted to `1`  instead of required
- Create `Message` from `Level`
- Create `Annotation` from `Level`
  - `Annotation::label` is now optional

New:
- Bulk-add functions to make it simpler to use builder API for automated conversion

What wasn't done:
- Deferred: Switch `Annotation` from using `Level` to a `Style { Level, Neutral, Added, Removed }`
- Deferred: Remove `footer` or convert it from `Vec<Label>` to `Vec<Message>`
- Deferred: Switching fields that are `Option` to have constructor methods that are `Option`
  - A little messy for people directly making a `Message`
  - Simplifies use with automated conversion
  - Alts
    - Leave as-is
    - Switch to `&mut` builder which gets messy with the nesting builder API
- `Message::id` to `Message::code`.  I feel like the former is more semantically meaningful


Patch:
diff --git a/benches/simple.rs b/benches/simple.rs
index 4eacfda..fccb70b 100644
--- a/benches/simple.rs
+++ b/benches/simple.rs
@@ -4,7 +4,7 @@ extern crate criterion;
 
 use criterion::{black_box, Criterion};
 
-use annotate_snippets::{Label, Renderer, Slice, Snippet};
+use annotate_snippets::{Level, Renderer, Snippet};
 
 fn create_snippet(renderer: Renderer) {
     let source = r#") -> Option<String> {
@@ -29,16 +29,23 @@ fn create_snippet(renderer: Renderer) {
             _ => continue,
         }
     }"#;
-    let snippet = Snippet::error("mismatched types").id("E0308").slice(
-        Slice::new(source, 51)
+    let message = Level::Error.title("mismatched types").id("E0308").snippet(
+        Snippet::source(source)
+            .line_start(51)
             .origin("src/format.rs")
             .annotation(
-                Label::warning("expected `Option<String>` because of return type").span(5..19),
+                Level::Warning
+                    .span(5..19)
+                    .label("expected `Option<String>` because of return type"),
             )
-            .annotation(Label::error("expected enum `std::option::Option`").span(26..724)),
+            .annotation(
+                Level::Error
+                    .span(26..724)
+                    .label("expected enum `std::option::Option`"),
+            ),
     );
 
-    let _result = renderer.render(snippet).to_string();
+    let _result = renderer.render(message).to_string();
 }
 
 pub fn criterion_benchmark(c: &mut Criterion) {
diff --git a/examples/expected_type.rs b/examples/expected_type.rs
index adcbeff..0184dee 100644
--- a/examples/expected_type.rs
+++ b/examples/expected_type.rs
@@ -1,23 +1,23 @@
-use annotate_snippets::{Label, Renderer, Slice, Snippet};
+use annotate_snippets::{Level, Renderer, Snippet};
 
 fn main() {
     let source = r#"                annotations: vec![SourceAnnotation {
                 label: "expected struct `annotate_snippets::snippet::Slice`, found reference"
                     ,
                 range: <22, 25>,"#;
-    let snippet = Snippet::error("expected type, found `22`").slice(
-        Slice::new(source, 26)
+    let message = Level::Error.title("expected type, found `22`").snippet(
+        Snippet::source(source)
+            .line_start(26)
             .origin("examples/footer.rs")
             .fold(true)
             .annotation(
-                Label::error(
-                    "expected struct `annotate_snippets::snippet::Slice`, found reference",
-                )
-                .span(193..195),
+                Level::Error
+                    .span(193..195)
+                    .label("expected struct `annotate_snippets::snippet::Slice`, found reference"),
             )
-            .annotation(Label::info("while parsing this struct").span(34..50)),
+            .annotation(Level::Info.span(34..50).label("while parsing this struct")),
     );
 
     let renderer = Renderer::styled();
-    anstream::println!("{}", renderer.render(snippet));
+    anstream::println!("{}", renderer.render(message));
 }
diff --git a/examples/footer.rs b/examples/footer.rs
index 6c45328..8b4d078 100644
--- a/examples/footer.rs
+++ b/examples/footer.rs
@@ -1,22 +1,22 @@
-use annotate_snippets::{Label, Renderer, Slice, Snippet};
+use annotate_snippets::{Label, Level, Renderer, Snippet};
 
 fn main() {
-    let snippet = Snippet::error("mismatched types")
-        .id("E0308")
-        .slice(
-            Slice::new("        slices: vec![\"A\",", 13)
-                .origin("src/multislice.rs")
-                .annotation(
-                    Label::error(
+    let message =
+        Level::Error
+            .title("mismatched types")
+            .id("E0308")
+            .snippet(
+                Snippet::source("        slices: vec![\"A\",")
+                    .line_start(13)
+                    .origin("src/multislice.rs")
+                    .annotation(Level::Error.span(21..24).label(
                         "expected struct `annotate_snippets::snippet::Slice`, found reference",
-                    )
-                    .span(21..24),
-                ),
-        )
-        .footer(Label::note(
-            "expected type: `snippet::Annotation`\n   found type: `__&__snippet::Annotation`",
-        ));
+                    )),
+            )
+            .footer(Label::note(
+                "expected type: `snippet::Annotation`\n   found type: `__&__snippet::Annotation`",
+            ));
 
     let renderer = Renderer::styled();
-    anstream::println!("{}", renderer.render(snippet));
+    anstream::println!("{}", renderer.render(message));
 }
diff --git a/examples/format.rs b/examples/format.rs
index 1337812..1606777 100644
--- a/examples/format.rs
+++ b/examples/format.rs
@@ -1,4 +1,4 @@
-use annotate_snippets::{Label, Renderer, Slice, Snippet};
+use annotate_snippets::{Level, Renderer, Snippet};
 
 fn main() {
     let source = r#") -> Option<String> {
@@ -23,15 +23,22 @@ fn main() {
             _ => continue,
         }
     }"#;
-    let snippet = Snippet::error("mismatched types").id("E0308").slice(
-        Slice::new(source, 51)
+    let message = Level::Error.title("mismatched types").id("E0308").snippet(
+        Snippet::source(source)
+            .line_start(51)
             .origin("src/format.rs")
             .annotation(
-                Label::warning("expected `Option<String>` because of return type").span(5..19),
+                Level::Warning
+                    .span(5..19)
+                    .label("expected `Option<String>` because of return type"),
             )
-            .annotation(Label::error("expected enum `std::option::Option`").span(26..724)),
+            .annotation(
+                Level::Error
+                    .span(26..724)
+                    .label("expected enum `std::option::Option`"),
+            ),
     );
 
     let renderer = Renderer::styled();
-    anstream::println!("{}", renderer.render(snippet));
+    anstream::println!("{}", renderer.render(message));
 }
diff --git a/examples/multislice.rs b/examples/multislice.rs
index b6fcb3f..ea31bbd 100644
--- a/examples/multislice.rs
+++ b/examples/multislice.rs
@@ -1,10 +1,19 @@
-use annotate_snippets::{Renderer, Slice, Snippet};
+use annotate_snippets::{Level, Renderer, Snippet};
 
 fn main() {
-    let snippet = Snippet::error("mismatched types")
-        .slice(Slice::new("Foo", 51).origin("src/format.rs"))
-        .slice(Slice::new("Faa", 129).origin("src/display.rs"));
+    let message = Level::Error
+        .title("mismatched types")
+        .snippet(
+            Snippet::source("Foo")
+                .line_start(51)
+                .origin("src/format.rs"),
+        )
+        .snippet(
+            Snippet::source("Faa")
+                .line_start(129)
+                .origin("src/display.rs"),
+        );
 
     let renderer = Renderer::styled();
-    anstream::println!("{}", renderer.render(snippet));
+    anstream::println!("{}", renderer.render(message));
 }
diff --git a/src/lib.rs b/src/lib.rs
index 8602c0a..a2e4231 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16,10 +16,10 @@
 //! The crate uses a three stage process with two conversions between states:
 //!
 //! ```text
-//! Snippet --> Renderer --> impl Display
+//! Message --> Renderer --> impl Display
 //! ```
 //!
-//! The input type - [Snippet] is a structure designed
+//! The input type - [Message] is a structure designed
 //! to align with likely output from any parser whose code snippet is to be
 //! annotated.
 //!
diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs
index 01484d4..d54282f 100644
--- a/src/renderer/display_list.rs
+++ b/src/renderer/display_list.rs
@@ -106,23 +106,30 @@ impl<'a> DisplayList<'a> {
     const WARNING_TXT: &'static str = "warning";
 
     pub(crate) fn new(
-        snippet::Snippet {
-            title,
+        snippet::Message {
+            level,
             id,
+            title,
             footer,
-            slices,
-        }: snippet::Snippet<'a>,
+            snippets,
+        }: snippet::Message<'a>,
         stylesheet: &'a Stylesheet,
         anonymized_line_numbers: bool,
         margin: Option<Margin>,
     ) -> DisplayList<'a> {
         let mut body = vec![];
 
-        body.push(format_title(title, id));
+        body.push(format_title(
+            snippet::Label {
+                level,
+                label: title,
+            },
+            id,
+        ));
 
-        for (idx, slice) in slices.into_iter().enumerate() {
+        for (idx, snippet) in snippets.into_iter().enumerate() {
             body.append(&mut format_slice(
-                slice,
+                snippet,
                 idx == 0,
                 !footer.is_empty(),
                 margin,
@@ -542,7 +549,7 @@ pub enum DisplayLine<'a> {
 /// A source line.
 #[derive(Debug, PartialEq)]
 pub enum DisplaySourceLine<'a> {
-    /// A line with the content of the Slice.
+    /// A line with the content of the Snippet.
     Content {
         text: &'a str,
         range: (usize, usize), // meta information for annotation placement.
@@ -649,14 +656,14 @@ pub enum DisplayAnnotationType {
     Help,
 }
 
-impl From<snippet::AnnotationType> for DisplayAnnotationType {
-    fn from(at: snippet::AnnotationType) -> Self {
+impl From<snippet::Level> for DisplayAnnotationType {
+    fn from(at: snippet::Level) -> Self {
         match at {
-            snippet::AnnotationType::Error => DisplayAnnotationType::Error,
-            snippet::AnnotationType::Warning => DisplayAnnotationType::Warning,
-            snippet::AnnotationType::Info => DisplayAnnotationType::Info,
-            snippet::AnnotationType::Note => DisplayAnnotationType::Note,
-            snippet::AnnotationType::Help => DisplayAnnotationType::Help,
+            snippet::Level::Error => DisplayAnnotationType::Error,
+            snippet::Level::Warning => DisplayAnnotationType::Warning,
+            snippet::Level::Info => DisplayAnnotationType::Info,
+            snippet::Level::Note => DisplayAnnotationType::Note,
+            snippet::Level::Help => DisplayAnnotationType::Help,
         }
     }
 }
@@ -736,7 +743,7 @@ fn format_label(
 fn format_title<'a>(title: snippet::Label<'a>, id: Option<&'a str>) -> DisplayLine<'a> {
     DisplayLine::Raw(DisplayRawLine::Annotation {
         annotation: Annotation {
-            annotation_type: DisplayAnnotationType::from(title.annotation_type),
+            annotation_type: DisplayAnnotationType::from(title.level),
             id,
             label: format_label(Some(title.label), Some(DisplayTextStyle::Emphasis)),
         },
@@ -750,7 +757,7 @@ fn format_footer(footer: snippet::Label<'_>) -> Vec<DisplayLine<'_>> {
     for (i, line) in footer.label.lines().enumerate() {
         result.push(DisplayLine::Raw(DisplayRawLine::Annotation {
             annotation: Annotation {
-                annotation_type: DisplayAnnotationType::from(footer.annotation_type),
+                annotation_type: DisplayAnnotationType::from(footer.level),
                 id: None,
                 label: format_label(Some(line), None),
             },
@@ -762,15 +769,15 @@ fn format_footer(footer: snippet::Label<'_>) -> Vec<DisplayLine<'_>> {
 }
 
 fn format_slice(
-    slice: snippet::Slice<'_>,
+    snippet: snippet::Snippet<'_>,
     is_first: bool,
     has_footer: bool,
     margin: Option<Margin>,
 ) -> Vec<DisplayLine<'_>> {
-    let main_range = slice.annotations.first().map(|x| x.range.start);
-    let origin = slice.origin;
+    let main_range = snippet.annotations.first().map(|x| x.range.start);
+    let origin = snippet.origin;
     let need_empty_header = origin.is_some() || is_first;
-    let mut body = format_body(slice, need_empty_header, has_footer, margin);
+    let mut body = format_body(snippet, need_empty_header, has_footer, margin);
     let header = format_header(origin, main_range, &body, is_first);
     let mut result = vec![];
 
@@ -942,13 +949,13 @@ fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
 }
 
 fn format_body(
-    slice: snippet::Slice<'_>,
+    snippet: snippet::Snippet<'_>,
     need_empty_header: bool,
     has_footer: bool,
     margin: Option<Margin>,
 ) -> Vec<DisplayLine<'_>> {
-    let source_len = slice.source.len();
-    if let Some(bigger) = slice.annotations.iter().find_map(|x| {
+    let source_len = snippet.source.len();
+    if let Some(bigger) = snippet.annotations.iter().find_map(|x| {
         // Allow highlighting one past the last character in the source.
         if source_len + 1 < x.range.end {
             Some(&x.range)
@@ -963,7 +970,7 @@ fn format_body(
     }
 
     let mut body = vec![];
-    let mut current_line = slice.line_start;
+    let mut current_line = snippet.line_start;
     let mut current_index = 0;
     let mut line_info = vec![];
 
@@ -972,7 +979,7 @@ fn format_body(
         line_end_index: usize,
     }
 
-    for (line, end_line) in CursorLines::new(slice.source) {
+    for (line, end_line) in CursorLines::new(snippet.source) {
         let line_length: usize = line
             .chars()
             .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
@@ -995,7 +1002,7 @@ fn format_body(
     }
 
     let mut annotation_line_count = 0;
-    let mut annotations = slice.annotations;
+    let mut annotations = snippet.annotations;
     for (
         idx,
         LineInfo {
@@ -1010,10 +1017,10 @@ fn format_body(
         // It would be nice to use filter_drain here once it's stable.
         annotations.retain(|annotation| {
             let body_idx = idx + annotation_line_count;
-            let annotation_type = match annotation.annotation_type {
-                snippet::AnnotationType::Error => DisplayAnnotationType::None,
-                snippet::AnnotationType::Warning => DisplayAnnotationType::None,
-                _ => DisplayAnnotationType::from(annotation.annotation_type),
+            let annotation_type = match annotation.level {
+                snippet::Level::Error => DisplayAnnotationType::None,
+                snippet::Level::Warning => DisplayAnnotationType::None,
+                _ => DisplayAnnotationType::from(annotation.level),
             };
             match annotation.range {
                 Range { start, .. } if start > line_end_index => true,
@@ -1033,12 +1040,10 @@ fn format_body(
                                 annotation: Annotation {
                                     annotation_type,
                                     id: None,
-                                    label: format_label(Some(annotation.label), None),
+                                    label: format_label(annotation.label, None),
                                 },
                                 range,
-                                annotation_type: DisplayAnnotationType::from(
-                                    annotation.annotation_type,
-                                ),
+                                annotation_type: DisplayAnnotationType::from(annotation.level),
                                 annotation_part: DisplayAnnotationPart::Standalone,
                             },
                         },
@@ -1059,9 +1064,7 @@ fn format_body(
                         {
                             inline_marks.push(DisplayMark {
                                 mark_type: DisplayMarkType::AnnotationStart,
-                                annotation_type: DisplayAnnotationType::from(
-                                    annotation.annotation_type,
-                                ),
+                                annotation_type: DisplayAnnotationType::from(annotation.level),
                             });
                         }
                     } else {
@@ -1079,9 +1082,7 @@ fn format_body(
                                         label: vec![],
                                     },
                                     range,
-                                    annotation_type: DisplayAnnotationType::from(
-                                        annotation.annotation_type,
-                                    ),
+                                    annotation_type: DisplayAnnotationType::from(annotation.level),
                                     annotation_part: DisplayAnnotationPart::MultilineStart,
                                 },
                             },
@@ -1098,9 +1099,7 @@ fn format_body(
                     {
                         inline_marks.push(DisplayMark {
                             mark_type: DisplayMarkType::AnnotationThrough,
-                            annotation_type: DisplayAnnotationType::from(
-                                annotation.annotation_type,
-                            ),
+                            annotation_type: DisplayAnnotationType::from(annotation.level),
                         });
                     }
                     true
@@ -1117,9 +1116,7 @@ fn format_body(
                     {
                         inline_marks.push(DisplayMark {
                             mark_type: DisplayMarkType::AnnotationThrough,
-                            annotation_type: DisplayAnnotationType::from(
-                                annotation.annotation_type,
-                            ),
+                            annotation_type: DisplayAnnotationType::from(annotation.level),
                         });
                     }
 
@@ -1131,20 +1128,16 @@ fn format_body(
                             lineno: None,
                             inline_marks: vec![DisplayMark {
                                 mark_type: DisplayMarkType::AnnotationThrough,
-                                annotation_type: DisplayAnnotationType::from(
-                                    annotation.annotation_type,
-                                ),
+                                annotation_type: DisplayAnnotationType::from(annotation.level),
                             }],
                             line: DisplaySourceLine::Annotation {
                                 annotation: Annotation {
                                     annotation_type,
                                     id: None,
-                                    label: format_label(Some(annotation.label), None),
+                                    label: format_label(annotation.label, None),
                                 },
                                 range,
-                                annotation_type: DisplayAnnotationType::from(
-                                    annotation.annotation_type,
-                                ),
+                                annotation_type: DisplayAnnotationType::from(annotation.level),
                                 annotation_part: DisplayAnnotationPart::MultilineEnd,
                             },
                         },
@@ -1157,7 +1150,7 @@ fn format_body(
         });
     }
 
-    if slice.fold {
+    if snippet.fold {
         body = fold_body(body);
     }
 
@@ -1220,7 +1213,7 @@ mod tests {
 
     #[test]
     fn test_format_title() {
-        let input = snippet::Snippet::error("This is a title").id("E0001");
+        let input = snippet::Level::Error.title("This is a title").id("E0001");
         let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation {
             annotation: Annotation {
                 annotation_type: DisplayAnnotationType::Error,
@@ -1241,7 +1234,9 @@ mod tests {
         let line_1 = "This is line 1";
         let line_2 = "This is line 2";
         let source = [line_1, line_2].join("\n");
-        let input = snippet::Snippet::error("").slice(snippet::Slice::new(&source, 5402));
+        let input = snippet::Level::Error
+            .title("")
+            .snippet(snippet::Snippet::source(&source).line_start(5402));
         let output = from_display_lines(vec![
             DisplayLine::Raw(DisplayRawLine::Annotation {
                 annotation: Annotation {
@@ -1291,9 +1286,18 @@ mod tests {
         let src_0_len = src_0.len();
         let src_1 = "This is slice 2";
         let src_1_len = src_1.len();
-        let input = snippet::Snippet::error("")
-            .slice(snippet::Slice::new(src_0, 5402).origin("file1.rs"))
-            .slice(snippet::Slice::new(src_1, 2).origin("file2.rs"));
+        let input = snippet::Level::Error
+            .title("")
+            .snippet(
+                snippet::Snippet::source(src_0)
+                    .line_start(5402)
+                    .origin("file1.rs"),
+            )
+            .snippet(
+                snippet::Snippet::source(src_1)
+                    .line_start(2)
+                    .origin("file2.rs"),
+            );
         let output = from_display_lines(vec![
             DisplayLine::Raw(DisplayRawLine::Annotation {
                 annotation: Annotation {
@@ -1364,9 +1368,14 @@ mod tests {
         let source = [line_1, line_2].join("\n");
         // In line 2
         let range = 22..24;
-        let input = snippet::Snippet::error("").slice(
-            snippet::Slice::new(&source, 5402)
-                .annotation(snippet::Label::info("Test annotation").span(range.clone())),
+        let input = snippet::Level::Error.title("").snippet(
+            snippet::Snippet::source(&source)
+                .line_start(5402)
+                .annotation(
+                    snippet::Level::Info
+                        .span(range.clone())
+                        .label("Test annotation"),
+                ),
         );
         let output = from_display_lines(vec![
             DisplayLine::Raw(DisplayRawLine::Annotation {
@@ -1433,8 +1442,9 @@ mod tests {
 
     #[test]
     fn test_format_label() {
-        let input =
-            snippet::Snippet::error("").footer(snippet::Label::error("This __is__ a title"));
+        let input = snippet::Level::Error
+            .title("")
+            .footer(snippet::Label::error("This __is__ a title"));
         let output = from_display_lines(vec![
             DisplayLine::Raw(DisplayRawLine::Annotation {
                 annotation: Annotation {
@@ -1469,20 +1479,22 @@ mod tests {
     fn test_i26() {
         let source = "short";
         let label = "label";
-        let input = snippet::Snippet::error("").slice(
-            snippet::Slice::new(source, 0)
-                .annotation(snippet::Label::error(label).span(0..source.len() + 2)),
+        let input = snippet::Level::Error.title("").snippet(
+            snippet::Snippet::source(source)
+                .line_start(0)
+                .annotation(snippet::Level::Error.span(0..source.len() + 2).label(label)),
         );
         let _ = DisplayList::new(input, &STYLESHEET, false, None);
     }
 
     #[test]
     fn test_i_29() {
-        let snippets = snippet::Snippet::error("oops").slice(
-            snippet::Slice::new("First line\r\nSecond oops line", 1)
+        let snippets = snippet::Level::Error.title("oops").snippet(
+            snippet::Snippet::source("First line\r\nSecond oops line")
+                .line_start(1)
                 .origin("<current file>")
                 .fold(true)
-                .annotation(snippet::Label::error("oops").span(19..23)),
+                .annotation(snippet::Level::Error.span(19..23).label("oops")),
         );
 
         let expected = from_display_lines(vec![
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 7046407..5f9394d 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -1,11 +1,11 @@
-//! The renderer for [`Snippet`]s
+//! The renderer for [`Message`]s
 //!
 //! # Example
 //! ```
-//! use annotate_snippets::{Renderer, Slice, Snippet};
-//! let snippet = Snippet::error("mismatched types")
-//!     .slice(Slice::new("Foo", 51).origin("src/format.rs"))
-//!     .slice(Slice::new("Faa", 129).origin("src/display.rs"));
+//! use annotate_snippets::{Renderer, Snippet, Level};
+//! let snippet = Level::Error.title("mismatched types")
+//!     .snippet(Snippet::source("Foo").line_start(51).origin("src/format.rs"))
+//!     .snippet(Snippet::source("Faa").line_start(129).origin("src/display.rs"));
 //!
 //!  let renderer = Renderer::styled();
 //!  println!("{}", renderer.render(snippet));
@@ -14,14 +14,14 @@ mod display_list;
 mod margin;
 pub(crate) mod stylesheet;
 
-use crate::snippet::Snippet;
+use crate::snippet::Message;
 pub use anstyle::*;
 use display_list::DisplayList;
 pub use margin::Margin;
 use std::fmt::Display;
 use stylesheet::Stylesheet;
 
-/// A renderer for [`Snippet`]s
+/// A renderer for [`Message`]s
 #[derive(Clone)]
 pub struct Renderer {
     anonymized_line_numbers: bool,
@@ -165,9 +165,9 @@ impl Renderer {
     }
 
     /// Render a snippet into a `Display`able object
-    pub fn render<'a>(&'a self, snippet: Snippet<'a>) -> impl Display + 'a {
+    pub fn render<'a>(&'a self, msg: Message<'a>) -> impl Display + 'a {
         DisplayList::new(
-            snippet,
+            msg,
             &self.stylesheet,
             self.anonymized_line_numbers,
             self.margin,
diff --git a/src/snippet.rs b/src/snippet.rs
index 37f431a..6ba2d2d 100644
--- a/src/snippet.rs
+++ b/src/snippet.rs
@@ -5,58 +5,37 @@
 //! ```
 //! use annotate_snippets::*;
 //!
-//! Snippet::error("mismatched types")
-//!     .slice(Slice::new("Foo", 51).origin("src/format.rs"))
-//!     .slice(Slice::new("Faa", 129).origin("src/display.rs"));
+//! Level::Error.title("mismatched types")
+//!     .snippet(Snippet::source("Foo").line_start(51).origin("src/format.rs"))
+//!     .snippet(Snippet::source("Faa").line_start(129).origin("src/display.rs"));
 //! ```
 
 use std::ops::Range;
 
 /// Primary structure provided for formatting
-pub struct Snippet<'a> {
-    pub(crate) title: Label<'a>,
+///
+/// See [`Level::title`] to create a [`Message`]
+pub struct Message<'a> {
+    pub(crate) level: Level,
     pub(crate) id: Option<&'a str>,
-    pub(crate) slices: Vec<Slice<'a>>,
+    pub(crate) title: &'a str,
+    pub(crate) snippets: Vec<Snippet<'a>>,
     pub(crate) footer: Vec<Label<'a>>,
 }
 
-impl<'a> Snippet<'a> {
-    pub fn title(title: Label<'a>) -> Self {
-        Self {
-            title,
-            id: None,
-            slices: vec![],
-            footer: vec![],
-        }
-    }
-
-    pub fn error(title: &'a str) -> Self {
-        Self::title(Label::error(title))
-    }
-
-    pub fn warning(title: &'a str) -> Self {
-        Self::title(Label::warning(title))
-    }
-
-    pub fn info(title: &'a str) -> Self {
-        Self::title(Label::info(title))
-    }
-
-    pub fn note(title: &'a str) -> Self {
-        Self::title(Label::note(title))
-    }
-
-    pub fn help(title: &'a str) -> Self {
-        Self::title(Label::help(title))
-    }
-
+impl<'a> Message<'a> {
     pub fn id(mut self, id: &'a str) -> Self {
         self.id = Some(id);
         self
     }
 
-    pub fn slice(mut self, slice: Slice<'a>) -> Self {
-        self.slices.push(slice);
+    pub fn snippet(mut self, slice: Snippet<'a>) -> Self {
+        self.snippets.push(slice);
+        self
+    }
+
+    pub fn snippets(mut self, slice: impl IntoIterator<Item = Snippet<'a>>) -> Self {
+        self.snippets.extend(slice);
         self
     }
 
@@ -64,107 +43,122 @@ impl<'a> Snippet<'a> {
         self.footer.push(footer);
         self
     }
+
+    pub fn footers(mut self, footer: impl IntoIterator<Item = Label<'a>>) -> Self {
+        self.footer.extend(footer);
+        self
+    }
 }
 
 pub struct Label<'a> {
-    pub(crate) annotation_type: AnnotationType,
+    pub(crate) level: Level,
     pub(crate) label: &'a str,
 }
 
 impl<'a> Label<'a> {
-    pub fn new(annotation_type: AnnotationType, label: &'a str) -> Self {
-        Self {
-            annotation_type,
-            label,
-        }
+    pub fn new(level: Level, label: &'a str) -> Self {
+        Self { level, label }
     }
     pub fn error(label: &'a str) -> Self {
-        Self::new(AnnotationType::Error, label)
+        Self::new(Level::Error, label)
     }
 
     pub fn warning(label: &'a str) -> Self {
-        Self::new(AnnotationType::Warning, label)
+        Self::new(Level::Warning, label)
     }
 
     pub fn info(label: &'a str) -> Self {
-        Self::new(AnnotationType::Info, label)
+        Self::new(Level::Info, label)
     }
 
     pub fn note(label: &'a str) -> Self {
-        Self::new(AnnotationType::Note, label)
+        Self::new(Level::Note, label)
     }
 
     pub fn help(label: &'a str) -> Self {
-        Self::new(AnnotationType::Help, label)
+        Self::new(Level::Help, label)
     }
 
     pub fn label(mut self, label: &'a str) -> Self {
         self.label = label;
         self
     }
-
-    /// Create a [`SourceAnnotation`] with the given span for a [`Slice`]
-    pub fn span(&self, span: Range<usize>) -> SourceAnnotation<'a> {
-        SourceAnnotation {
-            range: span,
-            label: self.label,
-            annotation_type: self.annotation_type,
-        }
-    }
-}
-
-impl From<AnnotationType> for Label<'_> {
-    fn from(annotation_type: AnnotationType) -> Self {
-        Label {
-            annotation_type,
-            label: "",
-        }
-    }
 }
 
 /// Structure containing the slice of text to be annotated and
 /// basic information about the location of the slice.
 ///
-/// One `Slice` is meant to represent a single, continuous,
+/// One `Snippet` is meant to represent a single, continuous,
 /// slice of source code that you want to annotate.
-pub struct Slice<'a> {
-    pub(crate) source: &'a str,
-    pub(crate) line_start: usize,
+pub struct Snippet<'a> {
     pub(crate) origin: Option<&'a str>,
-    pub(crate) annotations: Vec<SourceAnnotation<'a>>,
+    pub(crate) line_start: usize,
+
+    pub(crate) source: &'a str,
+    pub(crate) annotations: Vec<Annotation<'a>>,
+
     pub(crate) fold: bool,
 }
 
-impl<'a> Slice<'a> {
-    pub fn new(source: &'a str, line_start: usize) -> Self {
+impl<'a> Snippet<'a> {
+    pub fn source(source: &'a str) -> Self {
         Self {
-            source,
-            line_start,
             origin: None,
+            line_start: 1,
+            source,
             annotations: vec![],
             fold: false,
         }
     }
 
+    pub fn line_start(mut self, line_start: usize) -> Self {
+        self.line_start = line_start;
+        self
+    }
+
     pub fn origin(mut self, origin: &'a str) -> Self {
         self.origin = Some(origin);
         self
     }
 
-    pub fn annotation(mut self, annotation: SourceAnnotation<'a>) -> Self {
+    pub fn annotation(mut self, annotation: Annotation<'a>) -> Self {
         self.annotations.push(annotation);
         self
     }
 
+    pub fn annotations(mut self, annotation: impl IntoIterator<Item = Annotation<'a>>) -> Self {
+        self.annotations.extend(annotation);
+        self
+    }
+
+    /// Hide lines without [`Annotation`]s
     pub fn fold(mut self, fold: bool) -> Self {
         self.fold = fold;
         self
     }
 }
 
+/// An annotation for a [`Snippet`].
+///
+/// See [`Level::span`] to create a [`Annotation`]
+#[derive(Debug)]
+pub struct Annotation<'a> {
+    /// The byte range of the annotation in the `source` string
+    pub(crate) range: Range<usize>,
+    pub(crate) label: Option<&'a str>,
+    pub(crate) level: Level,
+}
+
+impl<'a> Annotation<'a> {
+    pub fn label(mut self, label: &'a str) -> Self {
+        self.label = Some(label);
+        self
+    }
+}
+
 /// Types of annotations.
 #[derive(Debug, Clone, Copy, PartialEq)]
-pub enum AnnotationType {
+pub enum Level {
     /// Error annotations are displayed using red color and "^" character.
     Error,
     /// Warning annotations are displayed using blue color and "-" character.
@@ -174,13 +168,23 @@ pub enum AnnotationType {
     Help,
 }
 
-/// An annotation for a [`Slice`].
-///
-/// This gets created by [`Label::span`].
-#[derive(Debug)]
-pub struct SourceAnnotation<'a> {
-    /// The byte range of the annotation in the `source` string
-    pub(crate) range: Range<usize>,
-    pub(crate) label: &'a str,
-    pub(crate) annotation_type: AnnotationType,
+impl Level {
+    pub fn title(self, title: &str) -> Message<'_> {
+        Message {
+            level: self,
+            id: None,
+            title,
+            snippets: vec![],
+            footer: vec![],
+        }
+    }
+
+    /// Create a [`Annotation`] with the given span for a [`Snippet`]
+    pub fn span<'a>(self, span: Range<usize>) -> Annotation<'a> {
+        Annotation {
+            range: span,
+            label: None,
+            level: self,
+        }
+    }
 }


Test patch:
diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs
index a01c343..6bfe76f 100644
--- a/tests/fixtures/deserialize.rs
+++ b/tests/fixtures/deserialize.rs
@@ -1,23 +1,22 @@
 use serde::{Deserialize, Deserializer, Serialize};
 use std::ops::Range;
 
-use annotate_snippets::{
-    renderer::Margin, AnnotationType, Label, Renderer, Slice, Snippet, SourceAnnotation,
-};
+use annotate_snippets::{renderer::Margin, Annotation, Label, Level, Message, Renderer, Snippet};
 
 #[derive(Deserialize)]
 pub struct Fixture<'a> {
     #[serde(default)]
     pub renderer: RendererDef,
     #[serde(borrow)]
-    pub snippet: SnippetDef<'a>,
+    pub message: MessageDef<'a>,
 }
 
 #[derive(Deserialize)]
-pub struct SnippetDef<'a> {
-    #[serde(deserialize_with = "deserialize_label")]
+pub struct MessageDef<'a> {
+    #[serde(with = "LevelDef")]
+    pub level: Level,
     #[serde(borrow)]
-    pub title: Label<'a>,
+    pub title: &'a str,
     #[serde(default)]
     #[serde(borrow)]
     pub id: Option<&'a str>,
@@ -25,48 +24,30 @@ pub struct SnippetDef<'a> {
     #[serde(default)]
     #[serde(borrow)]
     pub footer: Vec<Label<'a>>,
-    #[serde(deserialize_with = "deserialize_slices")]
+    #[serde(deserialize_with = "deserialize_snippets")]
     #[serde(borrow)]
-    pub slices: Vec<Slice<'a>>,
+    pub snippets: Vec<Snippet<'a>>,
 }
 
-impl<'a> From<SnippetDef<'a>> for Snippet<'a> {
-    fn from(val: SnippetDef<'a>) -> Self {
-        let SnippetDef {
+impl<'a> From<MessageDef<'a>> for Message<'a> {
+    fn from(val: MessageDef<'a>) -> Self {
+        let MessageDef {
+            level,
             title,
             id,
             footer,
-            slices,
+            snippets,
         } = val;
-        let mut snippet = Snippet::title(title);
+        let mut message = level.title(title);
         if let Some(id) = id {
-            snippet = snippet.id(id);
+            message = message.id(id);
         }
-        snippet = slices
-            .into_iter()
-            .fold(snippet, |snippet, slice| snippet.slice(slice));
-        snippet = footer
-            .into_iter()
-            .fold(snippet, |snippet, label| snippet.footer(label));
-        snippet
+        message = message.snippets(snippets);
+        message = message.footers(footer);
+        message
     }
 }
 
-fn deserialize_label<'de, D>(deserializer: D) -> Result<Label<'de>, D::Error>
-where
-    D: Deserializer<'de>,
-{
-    #[derive(Deserialize)]
-    struct Wrapper<'a>(
-        #[serde(with = "LabelDef")]
-        #[serde(borrow)]
-        LabelDef<'a>,
-    );
-
-    Wrapper::deserialize(deserializer)
-        .map(|Wrapper(label)| Label::new(label.annotation_type, label.label))
-}
-
 fn deserialize_labels<'de, D>(deserializer: D) -> Result<Vec<Label<'de>>, D::Error>
 where
     D: Deserializer<'de>,
@@ -80,19 +61,19 @@ where
 
     let v = Vec::deserialize(deserializer)?;
     Ok(v.into_iter()
-        .map(|Wrapper(a)| Label::new(a.annotation_type, a.label))
+        .map(|Wrapper(a)| Label::new(a.level, a.label))
         .collect())
 }
 
-fn deserialize_slices<'de, D>(deserializer: D) -> Result<Vec<Slice<'de>>, D::Error>
+fn deserialize_snippets<'de, D>(deserializer: D) -> Result<Vec<Snippet<'de>>, D::Error>
 where
     D: Deserializer<'de>,
 {
     #[derive(Deserialize)]
     struct Wrapper<'a>(
-        #[serde(with = "SliceDef")]
+        #[serde(with = "SnippetDef")]
         #[serde(borrow)]
-        SliceDef<'a>,
+        SnippetDef<'a>,
     );
 
     let v = Vec::deserialize(deserializer)?;
@@ -100,84 +81,80 @@ where
 }
 
 #[derive(Deserialize)]
-pub struct SliceDef<'a> {
+pub struct SnippetDef<'a> {
     #[serde(borrow)]
     pub source: &'a str,
     pub line_start: usize,
     #[serde(borrow)]
     pub origin: Option<&'a str>,
-    #[serde(deserialize_with = "deserialize_source_annotations")]
+    #[serde(deserialize_with = "deserialize_annotations")]
     #[serde(borrow)]
-    pub annotations: Vec<SourceAnnotation<'a>>,
+    pub annotations: Vec<Annotation<'a>>,
     #[serde(default)]
     pub fold: bool,
 }
 
-impl<'a> From<SliceDef<'a>> for Slice<'a> {
-    fn from(val: SliceDef<'a>) -> Self {
-        let SliceDef {
+impl<'a> From<SnippetDef<'a>> for Snippet<'a> {
+    fn from(val: SnippetDef<'a>) -> Self {
+        let SnippetDef {
             source,
             line_start,
             origin,
             annotations,
             fold,
         } = val;
-        let mut slice = Slice::new(source, line_start).fold(fold);
+        let mut snippet = Snippet::source(source).line_start(line_start).fold(fold);
         if let Some(origin) = origin {
-            slice = slice.origin(origin)
+            snippet = snippet.origin(origin)
         }
-        slice = annotations
-            .into_iter()
-            .fold(slice, |slice, annotation| slice.annotation(annotation));
-        slice
+        snippet = snippet.annotations(annotations);
+        snippet
     }
 }
 
-fn deserialize_source_annotations<'de, D>(
-    deserializer: D,
-) -> Result<Vec<SourceAnnotation<'de>>, D::Error>
+fn deserialize_annotations<'de, D>(deserializer: D) -> Result<Vec<Annotation<'de>>, D::Error>
 where
     D: Deserializer<'de>,
 {
     #[derive(Deserialize)]
-    struct Wrapper<'a>(#[serde(borrow)] SourceAnnotationDef<'a>);
+    struct Wrapper<'a>(#[serde(borrow)] AnnotationDef<'a>);
 
     let v = Vec::deserialize(deserializer)?;
     Ok(v.into_iter().map(|Wrapper(a)| a.into()).collect())
 }
 
 #[derive(Serialize, Deserialize)]
-pub struct SourceAnnotationDef<'a> {
+pub struct AnnotationDef<'a> {
     pub range: Range<usize>,
     #[serde(borrow)]
     pub label: &'a str,
-    #[serde(with = "AnnotationTypeDef")]
-    pub annotation_type: AnnotationType,
+    #[serde(with = "LevelDef")]
+    pub level: Level,
 }
 
-impl<'a> From<SourceAnnotationDef<'a>> for SourceAnnotation<'a> {
-    fn from(val: SourceAnnotationDef<'a>) -> Self {
-        let SourceAnnotationDef {
+impl<'a> From<AnnotationDef<'a>> for Annotation<'a> {
+    fn from(val: AnnotationDef<'a>) -> Self {
+        let AnnotationDef {
             range,
             label,
-            annotation_type,
+            level,
         } = val;
-        Label::new(annotation_type, label).span(range)
+        level.span(range).label(label)
     }
 }
 
 #[derive(Serialize, Deserialize)]
 pub struct LabelDef<'a> {
-    #[serde(with = "AnnotationTypeDef")]
-    pub annotation_type: AnnotationType,
+    #[serde(with = "LevelDef")]
+    pub level: Level,
     #[serde(borrow)]
     pub label: &'a str,
 }
 
 #[allow(dead_code)]
 #[derive(Serialize, Deserialize)]
-#[serde(remote = "AnnotationType")]
-enum AnnotationTypeDef {
+#[serde(remote = "Level")]
+enum LevelDef {
     Error,
     Warning,
     Info,
diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs
index c320407..841b363 100644
--- a/tests/fixtures/main.rs
+++ b/tests/fixtures/main.rs
@@ -1,7 +1,7 @@
 mod deserialize;
 
 use crate::deserialize::Fixture;
-use annotate_snippets::{Renderer, Snippet};
+use annotate_snippets::{Message, Renderer};
 use snapbox::data::DataFormat;
 use snapbox::Data;
 use std::error::Error;
@@ -26,8 +26,8 @@ fn setup(input_path: std::path::PathBuf) -> snapbox::harness::Case {
 
 fn test(input_path: &std::path::Path) -> Result<Data, Box<dyn Error>> {
     let src = std::fs::read_to_string(input_path)?;
-    let (renderer, snippet): (Renderer, Snippet<'_>) =
-        toml::from_str(&src).map(|a: Fixture| (a.renderer.into(), a.snippet.into()))?;
-    let actual = renderer.render(snippet).to_string();
+    let (renderer, message): (Renderer, Message<'_>) =
+        toml::from_str(&src).map(|a: Fixture| (a.renderer.into(), a.message.into()))?;
+    let actual = renderer.render(message).to_string();
     Ok(Data::from(actual).coerce_to(DataFormat::TermSvg))
 }
diff --git a/tests/fixtures/no-color/issue_52.toml b/tests/fixtures/no-color/issue_52.toml
index ea239dd..1e81a71 100644
--- a/tests/fixtures/no-color/issue_52.toml
+++ b/tests/fixtures/no-color/issue_52.toml
@@ -1,8 +1,8 @@
-[snippet.title]
-annotation_type = "Error"
-label = ""
+[message]
+level = "Error"
+title = ""
 
-[[snippet.slices]]
+[[message.snippets]]
 source = """
 
 
@@ -11,7 +11,7 @@ invalid syntax
 line_start = 1
 origin = "path/to/error.rs"
 fold = true
-[[snippet.slices.annotations]]
+[[message.snippets.annotations]]
 label = "error here"
-annotation_type = "Warning"
+level = "Warning"
 range = [2,16]
diff --git a/tests/fixtures/no-color/issue_9.toml b/tests/fixtures/no-color/issue_9.toml
index ee1fe27..1f35243 100644
--- a/tests/fixtures/no-color/issue_9.toml
+++ b/tests/fixtures/no-color/issue_9.toml
@@ -1,28 +1,28 @@
-[snippet.title]
-label = "expected one of `.`, `;`, `?`, or an operator, found `for`"
-annotation_type = "Error"
+[message]
+level = "Error"
+title = "expected one of `.`, `;`, `?`, or an operator, found `for`"
 
-[[snippet.slices]]
+[[message.snippets]]
 source = "let x = vec![1];"
 line_start = 4
 origin = "/code/rust/src/test/ui/annotate-snippet/suggestion.rs"
-[[snippet.slices.annotations]]
+[[message.snippets.annotations]]
 label = "move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait"
-annotation_type = "Warning"
+level = "Warning"
 range = [4, 5]
 
-[[snippet.slices]]
+[[message.snippets]]
 source = "let y = x;"
 line_start = 7
-[[snippet.slices.annotations]]
+[[message.snippets.annotations]]
 label = "value moved here"
-annotation_type = "Warning"
+level = "Warning"
 range = [8, 9]
 
-[[snippet.slices]]
+[[message.snippets]]
 source = "x;"
 line_start = 9
-[[snippet.slices.annotations]]
+[[message.snippets.annotations]]
 label = "value used here after move"
-annotation_type = "Error"
+level = "Error"
 range = [0, 1]
diff --git a/tests/fixtures/no-color/multiline_annotation.toml b/tests/fixtures/no-color/multiline_annotation.toml
index 48c0725..09fc7d4 100644
--- a/tests/fixtures/no-color/multiline_annotation.toml
+++ b/tests/fixtures/no-color/multiline_annotation.toml
@@ -1,4 +1,9 @@
-[[snippet.slices]]
+[message]
+level = "Error"
+id = "E0308"
+title = "mismatched types"
+
+[[message.snippets]]
 source = """
 ) -> Option<String> {
     for ann in annotations {
@@ -26,15 +31,11 @@ source = """
 line_start = 51
 origin = "src/format.rs"
 fold = true
-[[snippet.slices.annotations]]
+[[message.snippets.annotations]]
 label = "expected `std::option::Option<std::string::String>` because of return type"
-annotation_type = "Warning"
+level = "Warning"
 range = [5, 19]
-[[snippet.slices.annotations]]
+[[message.snippets.annotations]]
 label = "expected enum `std::option::Option`, found ()"
-annotation_type = "Error"
+level = "Error"
 range = [22, 766]
-
-[snippet]
-title = { annotation_type = "Error", label = "mismatched types" }
-id = "E0308"
diff --git a/tests/fixtures/no-color/multiline_annotation2.toml b/tests/fixtures/no-color/multiline_annotation2.toml
index 89294bc..671b534 100644
--- a/tests/fixtures/no-color/multiline_annotation2.toml
+++ b/tests/fixtures/no-color/multiline_annotation2.toml
@@ -1,4 +1,9 @@
-[[snippet.slices]]
+[message]
+level = "Error"
+id = "E0027"
+title = "pattern does not mention fields `lineno`, `content`"
+
+[[message.snippets]]
 source = """
                         if let DisplayLine::Source {
                             ref mut inline_marks,
@@ -7,11 +12,7 @@ source = """
 line_start = 139
 origin = "src/display_list.rs"
 fold = false
-[[snippet.slices.annotations]]
+[[message.snippets.annotations]]
 label = "missing fields `lineno`, `content`"
-annotation_type = "Error"
+level = "Error"
 range = [31, 128]
-
-[snippet]
-title = { annotation_type = "Error", label = "pattern does not mention fields `lineno`, `content`" }
-id = "E0027"
diff --git a/tests/fixtures/no-color/multiline_annotation3.toml b/tests/fixtures/no-color/multiline_annotation3.toml
index efc1f5f..dd85332 100644
--- a/tests/fixtures/no-color/multiline_annotation3.toml
+++ b/tests/fixtures/no-color/multiline_annotation3.toml
@@ -1,4 +1,9 @@
-[[snippet.slices]]
+[message]
+level = "Error"
+id = "E####"
+title = "spacing error found"
+
+[[message.snippets]]
 source = """
 This is an exampl
 e of an edge case of an annotation overflowing
@@ -7,11 +12,7 @@ to exactly one character on next line.
 line_start = 26
 origin = "foo.txt"
 fold = false
-[[snippet.slices.annotations]]
+[[message.snippets.annotations]]
 label = "this should not be on separate lines"
-annotation_type = "Error"
+level = "Error"
 range = [11, 18]
-
-[snippet]
-title = { annotation_type = "Error", label = "spacing error found" }
-id = "E####"
diff --git a/tests/fixtures/no-color/multiple_annotations.toml b/tests/fixtures/no-color/multiple_annotations.toml
index ae35cef..842b137 100644
--- a/tests/fixtures/no-color/multiple_annotations.toml
+++ b/tests/fixtures/no-color/multiple_annotations.toml
@@ -1,8 +1,8 @@
-[snippet.title]
-annotation_type = "Error"
-label = ""
+[message]
+level = "Error"
+title = ""
 
-[[snippet.slices]]
+[[message.snippets]]
 source = """
 fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {
     if let Some(annotation) = main_annotation {
@@ -15,15 +15,15 @@ fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>
 }
 """
 line_start = 96
-[[snippet.slices.annotations]]
+[[message.snippets.annotations]]
 label = "Variable defined here"
-annotation_type = "Error"
+level = "Error"
 range = [100, 110]
-[[snippet.slices.annotations]]
+[[message.snippets.annotations]]
 label = "Referenced here"
-annotation_type = "Error"
+level = "Error"
 range = [184, 194]
-[[snippet.slices.annotations]]
+[[message.snippets.annotations]]
 label = "Referenced again here"
-annotation_type = "Error"
+level = "Error"
 range = [243, 253]
diff --git a/tests/fixtures/no-color/one_past.toml b/tests/fixtures/no-color/one_past.toml
index 62d1d42..b681c29 100644
--- a/tests/fixtures/no-color/one_past.toml
+++ b/tests/fixtures/no-color/one_past.toml
@@ -1,12 +1,12 @@
-[snippet.title]
-label = "expected `.`, `=`"
-annotation_type = "Error"
+[message]
+level = "Error"
+title = "expected `.`, `=`"
 
-[[snippet.slices]]
+[[message.snippets]]
 source = "asdf"
 line_start = 1
 origin = "Cargo.toml"
-[[snippet.slices.annotations]]
+[[message.snippets.annotations]]
 label = ""
-annotation_type = "Error"
+level = "Error"
 range = [4, 5]
diff --git a/tests/fixtures/no-color/simple.toml b/tests/fixtures/no-color/simple.toml
index 2e9b6d8..76b5bac 100644
--- a/tests/fixtures/no-color/simple.toml
+++ b/tests/fixtures/no-color/simple.toml
@@ -1,18 +1,19 @@
-[[snippet.slices]]
+[message]
+level = "Error"
+title = "expected one of `.`, `;`, `?`, or an operator, found `for`"
+
+[[message.snippets]]
 source = """
         })
 
         for line in &self.body {"""
 line_start = 169
 origin = "src/format_color.rs"
-[[snippet.slices.annotations]]
+[[message.snippets.annotations]]
 label = "unexpected token"
-annotation_type = "Error"
+level = "Error"
 range = [20, 23]
-[[snippet.slices.annotations]]
+[[message.snippets.annotations]]
 label = "expected one of `.`, `;`, `?`, or an operator here"
-annotation_type = "Warning"
+level = "Warning"
 range = [10, 11]
-[snippet.title]
-label = "expected one of `.`, `;`, `?`, or an operator, found `for`"
-annotation_type = "Error"
\ No newline at end of file
diff --git a/tests/fixtures/no-color/strip_line.toml b/tests/fixtures/no-color/strip_line.toml
index 9de50aa..d44024b 100644
--- a/tests/fixtures/no-color/strip_line.toml
+++ b/tests/fixtures/no-color/strip_line.toml
@@ -1,15 +1,16 @@
-[snippet]
-title = { annotation_type = "Error", label = "mismatched types" }
+[message]
+level = "Error"
 id = "E0308"
+title = "mismatched types"
 
-[[snippet.slices]]
+[[message.snippets]]
 source = "                                                                                                                                                                                    let _: () = 42;"
 line_start = 4
 origin = "$DIR/whitespace-trimming.rs"
 
-[[snippet.slices.annotations]]
+[[message.snippets.annotations]]
 label = "expected (), found integer"
-annotation_type = "Error"
+level = "Error"
 range = [192, 194]
 
 [renderer]
diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/no-color/strip_line_char.toml
index 72df2ab..e3f7482 100644
--- a/tests/fixtures/no-color/strip_line_char.toml
+++ b/tests/fixtures/no-color/strip_line_char.toml
@@ -1,15 +1,16 @@
-[snippet]
-title = { annotation_type = "Error", label = "mismatched types" }
+[message]
+level = "Error"
 id = "E0308"
+title = "mismatched types" 
 
-[[snippet.slices]]
+[[message.snippets]]
 source = "                                                                                                                                                                                    let _: () = 42ñ"
 line_start = 4
 origin = "$DIR/whitespace-trimming.rs"
 
-[[snippet.slices.annotations]]
+[[message.snippets.annotations]]
 label = "expected (), found integer"
-annotation_type = "Error"
+level = "Error"
 range = [192, 194]
 
 [renderer]
diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/no-color/strip_line_non_ws.toml
index 236d8f1..2985177 100644
--- a/tests/fixtures/no-color/strip_line_non_ws.toml
+++ b/tests/fixtures/no-color/strip_line_non_ws.toml
@@ -1,15 +1,16 @@
-[snippet]
-title = { annotation_type = "Error", label = "mismatched types" }
+[message]
+level = "Error"
 id = "E0308"
+title = "mismatched types"
 
-[[snippet.slices]]
+[[message.snippets]]
 source = "    let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();"
 line_start = 4
 origin = "$DIR/non-whitespace-trimming.rs"
 
-[[snippet.slices.annotations]]
+[[message.snippets.annotations]]
 label = "expected (), found integer"
-annotation_type = "Error"
+level = "Error"
 range = [240, 242]
 
 [renderer]
diff --git a/tests/formatter.rs b/tests/formatter.rs
index 8f5c09f..76dcb27 100644
--- a/tests/formatter.rs
+++ b/tests/formatter.rs
@@ -1,11 +1,11 @@
-use annotate_snippets::{Label, Renderer, Slice, Snippet};
+use annotate_snippets::{Level, Renderer, Snippet};
 
 #[test]
 fn test_i_29() {
-    let snippets = Snippet::error("oops").slice(
-        Slice::new("First line\r\nSecond oops line", 1)
+    let snippets = Level::Error.title("oops").snippet(
+        Snippet::source("First line\r\nSecond oops line")
             .origin("<current file>")
-            .annotation(Label::error("oops").span(19..23))
+            .annotation(Level::Error.span(19..23).label("oops"))
             .fold(true),
     );
     let expected = r#"error: oops
@@ -22,10 +22,10 @@ fn test_i_29() {
 
 #[test]
 fn test_point_to_double_width_characters() {
-    let snippets = Snippet::error("").slice(
-        Slice::new("こんにちは、世界", 1)
+    let snippets = Level::Error.title("").snippet(
+        Snippet::source("こんにちは、世界")
             .origin("<current file>")
-            .annotation(Label::error("world").span(12..16)),
+            .annotation(Level::Error.span(12..16).label("world")),
     );
 
     let expected = r#"error
@@ -41,10 +41,10 @@ fn test_point_to_double_width_characters() {
 
 #[test]
 fn test_point_to_double_width_characters_across_lines() {
-    let snippets = Snippet::error("").slice(
-        Slice::new("おはよう\nございます", 1)
+    let snippets = Level::Error.title("").snippet(
+        Snippet::source("おはよう\nございます")
             .origin("<current file>")
-            .annotation(Label::error("Good morning").span(4..15)),
+            .annotation(Level::Error.span(4..15).label("Good morning")),
     );
 
     let expected = r#"error
@@ -62,11 +62,11 @@ fn test_point_to_double_width_characters_across_lines() {
 
 #[test]
 fn test_point_to_double_width_characters_multiple() {
-    let snippets = Snippet::error("").slice(
-        Slice::new("お寿司\n食べたい🍣", 1)
+    let snippets = Level::Error.title("").snippet(
+        Snippet::source("お寿司\n食べたい🍣")
             .origin("<current file>")
-            .annotation(Label::error("Sushi1").span(0..6))
-            .annotation(Label::note("Sushi2").span(11..15)),
+            .annotation(Level::Error.span(0..6).label("Sushi1"))
+            .annotation(Level::Note.span(11..15).label("Sushi2")),
     );
 
     let expected = r#"error
@@ -84,10 +84,10 @@ fn test_point_to_double_width_characters_multiple() {
 
 #[test]
 fn test_point_to_double_width_characters_mixed() {
-    let snippets = Snippet::error("").slice(
-        Slice::new("こんにちは、新しいWorld！", 1)
+    let snippets = Level::Error.title("").snippet(
+        Snippet::source("こんにちは、新しいWorld！")
             .origin("<current file>")
-            .annotation(Label::error("New world").span(12..23)),
+            .annotation(Level::Error.span(12..23).label("New world")),
     );
 
     let expected = r#"error