From bae26158e0d6fbbebf9c7802636599de53e8fe70 Mon Sep 17 00:00:00 2001 From: Yosh Date: Fri, 29 Nov 2024 19:58:13 +0100 Subject: [PATCH 1/5] init OCI author parsing --- crates/wasm-metadata/src/lib.rs | 2 + .../src/oci_annotations/author.rs | 79 +++++++++++++++++++ .../wasm-metadata/src/oci_annotations/mod.rs | 20 +++++ 3 files changed, 101 insertions(+) create mode 100644 crates/wasm-metadata/src/oci_annotations/author.rs create mode 100644 crates/wasm-metadata/src/oci_annotations/mod.rs diff --git a/crates/wasm-metadata/src/lib.rs b/crates/wasm-metadata/src/lib.rs index 5d76b34f29..497617fdb3 100644 --- a/crates/wasm-metadata/src/lib.rs +++ b/crates/wasm-metadata/src/lib.rs @@ -5,6 +5,7 @@ pub use add_metadata::AddMetadata; pub use metadata::Metadata; pub use names::{ComponentNames, ModuleNames}; +pub use oci_annotations::Author; pub use producers::{Producers, ProducersField}; pub use registry::{CustomLicense, Link, LinkType, RegistryMetadata}; @@ -13,6 +14,7 @@ pub(crate) use rewrite::rewrite_wasm; mod add_metadata; mod metadata; mod names; +mod oci_annotations; mod producers; mod registry; mod rewrite; diff --git a/crates/wasm-metadata/src/oci_annotations/author.rs b/crates/wasm-metadata/src/oci_annotations/author.rs new file mode 100644 index 0000000000..ede14265d1 --- /dev/null +++ b/crates/wasm-metadata/src/oci_annotations/author.rs @@ -0,0 +1,79 @@ +use std::borrow::Cow; +use std::fmt::{self, Display}; + +use anyhow::{ensure, Result}; +use wasm_encoder::{ComponentSection, CustomSection, Encode}; +use wasmparser::CustomSectionReader; + +/// Contact details of the people or organization responsible for the image +/// encoded as a freeform string. +#[derive(Debug)] +pub struct Author(CustomSection<'static>); + +impl Display for Author { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // NOTE: this will never panic since we always guarantee the data is + // encoded as utf8, even if we internally store it as [u8]. + let data = String::from_utf8(self.0.data.to_vec()).unwrap(); + write!(f, "{data}") + } +} + +impl Author { + /// Create a new instance of `Author`. + pub fn new>>(s: S) -> Self { + Self(CustomSection { + name: "author".into(), + data: match s.into() { + Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()), + Cow::Owned(s) => Cow::Owned(s.into()), + }, + }) + } + + /// Parse an `author` custom section from a wasm binary. + pub fn parse_wasm(reader: CustomSectionReader<'_>) -> Result { + ensure!( + dbg!(reader.name()) == "author", + "The `author` custom section should have a name of 'author'" + ); + let data = String::from_utf8(reader.data().to_owned())?; + Ok(Self::new(data)) + } +} + +impl ComponentSection for Author { + fn id(&self) -> u8 { + self.0.id() + } +} + +impl Encode for Author { + fn encode(&self, sink: &mut Vec) { + self.0.encode(sink); + } +} + +#[cfg(test)] +mod test { + use super::*; + use wasm_encoder::Component; + use wasmparser::Payload; + + #[test] + fn roundtrip() { + let mut component = Component::new(); + component.section(&Author::new("Nori Cat")); + let component = component.finish(); + + let mut parsed = false; + for section in wasmparser::Parser::new(0).parse_all(&component) { + if let Payload::CustomSection(reader) = section.unwrap() { + let author = Author::parse_wasm(reader).unwrap(); + assert_eq!(author.to_string(), "Nori Cat"); + parsed = true; + } + } + assert!(parsed); + } +} diff --git a/crates/wasm-metadata/src/oci_annotations/mod.rs b/crates/wasm-metadata/src/oci_annotations/mod.rs new file mode 100644 index 0000000000..18aa04799b --- /dev/null +++ b/crates/wasm-metadata/src/oci_annotations/mod.rs @@ -0,0 +1,20 @@ +//! Annotations following the [OCI Annotations Spec]. +//! +//! The fields of these annotations are encoded into custom sections of +//! component binaries, and are explicitly compatible with the OCI Annotations +//! Spec. That enables Compontents to be encoded to OCI and back without needing +//! to perform any additional parsing. This greatly simplifies adding metadata to +//! component registries, since language-native component toolchains can encode them +//! directly into components. Which in turn can be picked up by Component-to-OCI +//! tooling to take those annotations and display them in a way that registries can +//! understand. +//! +//! For the files in this submodule that means we want to be explicitly +//! compatible with the OCI Annotations specification. Any deviation in our +//! parsing rules from the spec should be considered a bug we have to fix. +//! +//! [OCI Annotations Spec]: https://specs.opencontainers.org/image-spec/annotations/ + +pub use author::Author; + +mod author; From c4ace542e6b495c7f28816d22c32bae1e082c65e Mon Sep 17 00:00:00 2001 From: Yosh Date: Tue, 3 Dec 2024 10:10:17 +0100 Subject: [PATCH 2/5] add serialization --- crates/wasm-metadata/src/metadata.rs | 6 +++++- .../src/oci_annotations/author.rs | 19 ++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/crates/wasm-metadata/src/metadata.rs b/crates/wasm-metadata/src/metadata.rs index 17845ac170..3697c6ef9c 100644 --- a/crates/wasm-metadata/src/metadata.rs +++ b/crates/wasm-metadata/src/metadata.rs @@ -4,7 +4,7 @@ use std::fmt; use std::ops::Range; use wasmparser::{KnownCustom, Parser, Payload::*}; -use crate::{ComponentNames, ModuleNames, Producers, RegistryMetadata}; +use crate::{Author, ComponentNames, ModuleNames, Producers, RegistryMetadata}; /// A tree of the metadata found in a WebAssembly binary. #[derive(Debug, Serialize)] @@ -18,6 +18,8 @@ pub enum Metadata { producers: Option, /// The component's registry metadata section, if any. registry_metadata: Option, + /// The component's author section, if any. + author: Option, /// All child modules and components inside the component. children: Vec>, /// Byte range of the module in the parent binary @@ -31,6 +33,8 @@ pub enum Metadata { producers: Option, /// The module's registry metadata section, if any. registry_metadata: Option, + /// The component's author section, if any. + author: Option, /// Byte range of the module in the parent binary range: Range, }, diff --git a/crates/wasm-metadata/src/oci_annotations/author.rs b/crates/wasm-metadata/src/oci_annotations/author.rs index ede14265d1..50dc8ffae8 100644 --- a/crates/wasm-metadata/src/oci_annotations/author.rs +++ b/crates/wasm-metadata/src/oci_annotations/author.rs @@ -2,14 +2,24 @@ use std::borrow::Cow; use std::fmt::{self, Display}; use anyhow::{ensure, Result}; +use serde::Serialize; use wasm_encoder::{ComponentSection, CustomSection, Encode}; use wasmparser::CustomSectionReader; /// Contact details of the people or organization responsible for the image /// encoded as a freeform string. -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct Author(CustomSection<'static>); +impl Serialize for Author { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + impl Display for Author { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // NOTE: this will never panic since we always guarantee the data is @@ -76,4 +86,11 @@ mod test { } assert!(parsed); } + + #[test] + fn serialize() { + let author = Author::new("Chashu Cat"); + let json = serde_json::to_string(&author).unwrap(); + assert_eq!("Chashu Cat", json); + } } From 0c185393c8e9a9f76235e964175ae7d61fc7b604 Mon Sep 17 00:00:00 2001 From: Yosh Date: Tue, 3 Dec 2024 15:30:21 +0100 Subject: [PATCH 3/5] fix tests --- crates/wasm-metadata/src/metadata.rs | 2 ++ crates/wasm-metadata/src/oci_annotations/author.rs | 2 +- crates/wasm-metadata/tests/component.rs | 6 ++++++ crates/wasm-metadata/tests/module.rs | 3 +++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/wasm-metadata/src/metadata.rs b/crates/wasm-metadata/src/metadata.rs index 3697c6ef9c..beba7f5fce 100644 --- a/crates/wasm-metadata/src/metadata.rs +++ b/crates/wasm-metadata/src/metadata.rs @@ -124,6 +124,7 @@ impl Metadata { Metadata::Component { name: None, producers: None, + author: None, registry_metadata: None, children: Vec::new(), range, @@ -134,6 +135,7 @@ impl Metadata { Metadata::Module { name: None, producers: None, + author: None, registry_metadata: None, range, } diff --git a/crates/wasm-metadata/src/oci_annotations/author.rs b/crates/wasm-metadata/src/oci_annotations/author.rs index 50dc8ffae8..49e64189d4 100644 --- a/crates/wasm-metadata/src/oci_annotations/author.rs +++ b/crates/wasm-metadata/src/oci_annotations/author.rs @@ -8,7 +8,7 @@ use wasmparser::CustomSectionReader; /// Contact details of the people or organization responsible for the image /// encoded as a freeform string. -#[derive(Debug, PartialEq)] +#[derive(Debug)] pub struct Author(CustomSection<'static>); impl Serialize for Author { diff --git a/crates/wasm-metadata/tests/component.rs b/crates/wasm-metadata/tests/component.rs index beda10a3d3..88be96f7dd 100644 --- a/crates/wasm-metadata/tests/component.rs +++ b/crates/wasm-metadata/tests/component.rs @@ -42,6 +42,7 @@ fn add_to_empty_component() { name, producers, registry_metadata, + author, children, range, } => { @@ -98,6 +99,8 @@ fn add_to_empty_component() { vec!["Tools".to_owned()] ); + assert!(author.is_none()); + assert_eq!(range.start, 0); assert_eq!(range.end, 435); } @@ -159,6 +162,7 @@ fn add_to_nested_component() { Metadata::Module { name, producers, + author, registry_metadata, range, } => { @@ -179,6 +183,8 @@ fn add_to_nested_component() { &["Foo".to_owned()] ); + assert!(author.is_none()); + assert_eq!(range.start, 10); assert_eq!(range.end, 123); } diff --git a/crates/wasm-metadata/tests/module.rs b/crates/wasm-metadata/tests/module.rs index a1b95bc974..e177c2c0f6 100644 --- a/crates/wasm-metadata/tests/module.rs +++ b/crates/wasm-metadata/tests/module.rs @@ -41,6 +41,7 @@ fn add_to_empty_module() { Metadata::Module { name, producers, + author, registry_metadata, range, } => { @@ -96,6 +97,8 @@ fn add_to_empty_module() { vec!["Tools".to_owned()] ); + assert!(author.is_none()); + assert_eq!(range.start, 0); assert_eq!(range.end, 425); } From 7f932c58d904bb91471b27eaad43661a7d9916a8 Mon Sep 17 00:00:00 2001 From: Yosh Date: Tue, 3 Dec 2024 15:33:31 +0100 Subject: [PATCH 4/5] fix json formatting test --- crates/wasm-metadata/src/oci_annotations/author.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wasm-metadata/src/oci_annotations/author.rs b/crates/wasm-metadata/src/oci_annotations/author.rs index 49e64189d4..675956842c 100644 --- a/crates/wasm-metadata/src/oci_annotations/author.rs +++ b/crates/wasm-metadata/src/oci_annotations/author.rs @@ -91,6 +91,6 @@ mod test { fn serialize() { let author = Author::new("Chashu Cat"); let json = serde_json::to_string(&author).unwrap(); - assert_eq!("Chashu Cat", json); + assert_eq!(r#""Chashu Cat""#, json); } } From 06f9aa09302ef2d7789b2fb655571d596305ea0a Mon Sep 17 00:00:00 2001 From: Yosh Date: Wed, 4 Dec 2024 17:35:23 +0100 Subject: [PATCH 5/5] add author parsing --- crates/wasm-encoder/src/core/custom.rs | 2 +- crates/wasm-metadata/src/add_metadata.rs | 8 ++- crates/wasm-metadata/src/metadata.rs | 7 ++ .../src/oci_annotations/author.rs | 67 ++++++++++++------- crates/wasm-metadata/src/producers.rs | 2 +- crates/wasm-metadata/src/registry.rs | 2 +- crates/wasm-metadata/src/rewrite.rs | 13 +++- crates/wasm-metadata/tests/component.rs | 16 +++-- crates/wasm-metadata/tests/module.rs | 7 +- 9 files changed, 83 insertions(+), 41 deletions(-) diff --git a/crates/wasm-encoder/src/core/custom.rs b/crates/wasm-encoder/src/core/custom.rs index 870ee62617..1fbeb10223 100644 --- a/crates/wasm-encoder/src/core/custom.rs +++ b/crates/wasm-encoder/src/core/custom.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use crate::{encoding_size, Encode, Section, SectionId}; /// A custom section holding arbitrary data. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct CustomSection<'a> { /// The name of this custom section. pub name: Cow<'a, str>, diff --git a/crates/wasm-metadata/src/add_metadata.rs b/crates/wasm-metadata/src/add_metadata.rs index c2b997e44a..cb18569476 100644 --- a/crates/wasm-metadata/src/add_metadata.rs +++ b/crates/wasm-metadata/src/add_metadata.rs @@ -1,4 +1,4 @@ -use crate::{rewrite_wasm, Producers, RegistryMetadata}; +use crate::{rewrite_wasm, Author, Producers, RegistryMetadata}; use anyhow::Result; @@ -25,6 +25,11 @@ pub struct AddMetadata { #[cfg_attr(feature="clap", clap(long, value_parser = parse_key_value, value_name="NAME=VERSION"))] pub sdk: Vec<(String, String)>, + /// Contact details of the people or organization responsible, + /// encoded as a freeform string. + #[cfg_attr(feature = "clap", clap(long, value_name = "NAME"))] + pub author: Option, + /// Add an registry metadata to the registry-metadata section #[cfg_attr(feature="clap", clap(long, value_parser = parse_registry_metadata_value, value_name="PATH"))] pub registry_metadata: Option, @@ -54,6 +59,7 @@ impl AddMetadata { rewrite_wasm( &self.name, &Producers::from_meta(self), + &self.author, self.registry_metadata.as_ref(), input, ) diff --git a/crates/wasm-metadata/src/metadata.rs b/crates/wasm-metadata/src/metadata.rs index beba7f5fce..2277455a36 100644 --- a/crates/wasm-metadata/src/metadata.rs +++ b/crates/wasm-metadata/src/metadata.rs @@ -110,6 +110,13 @@ impl Metadata { .expect("non-empty metadata stack") .set_registry_metadata(registry); } + KnownCustom::Unknown if c.name() == "author" => { + let a = Author::parse_custom_section(&c)?; + match metadata.last_mut().expect("non-empty metadata stack") { + Metadata::Module { author, .. } => *author = Some(a), + Metadata::Component { author, .. } => *author = Some(a), + } + } _ => {} }, _ => {} diff --git a/crates/wasm-metadata/src/oci_annotations/author.rs b/crates/wasm-metadata/src/oci_annotations/author.rs index 675956842c..ae7948292c 100644 --- a/crates/wasm-metadata/src/oci_annotations/author.rs +++ b/crates/wasm-metadata/src/oci_annotations/author.rs @@ -1,34 +1,17 @@ use std::borrow::Cow; use std::fmt::{self, Display}; +use std::str::FromStr; -use anyhow::{ensure, Result}; +use anyhow::{ensure, Error, Result}; use serde::Serialize; -use wasm_encoder::{ComponentSection, CustomSection, Encode}; +use wasm_encoder::{ComponentSection, CustomSection, Encode, Section}; use wasmparser::CustomSectionReader; -/// Contact details of the people or organization responsible for the image +/// Contact details of the people or organization responsible, /// encoded as a freeform string. -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq)] pub struct Author(CustomSection<'static>); -impl Serialize for Author { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -impl Display for Author { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // NOTE: this will never panic since we always guarantee the data is - // encoded as utf8, even if we internally store it as [u8]. - let data = String::from_utf8(self.0.data.to_vec()).unwrap(); - write!(f, "{data}") - } -} - impl Author { /// Create a new instance of `Author`. pub fn new>>(s: S) -> Self { @@ -42,9 +25,9 @@ impl Author { } /// Parse an `author` custom section from a wasm binary. - pub fn parse_wasm(reader: CustomSectionReader<'_>) -> Result { + pub(crate) fn parse_custom_section(reader: &CustomSectionReader<'_>) -> Result { ensure!( - dbg!(reader.name()) == "author", + reader.name() == "author", "The `author` custom section should have a name of 'author'" ); let data = String::from_utf8(reader.data().to_owned())?; @@ -52,9 +35,41 @@ impl Author { } } +impl FromStr for Author { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(Self::new(s.to_owned())) + } +} + +impl Serialize for Author { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl Display for Author { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // NOTE: this will never panic since we always guarantee the data is + // encoded as utf8, even if we internally store it as [u8]. + let data = String::from_utf8(self.0.data.to_vec()).unwrap(); + write!(f, "{data}") + } +} + impl ComponentSection for Author { fn id(&self) -> u8 { - self.0.id() + ComponentSection::id(&self.0) + } +} + +impl Section for Author { + fn id(&self) -> u8 { + Section::id(&self.0) } } @@ -79,7 +94,7 @@ mod test { let mut parsed = false; for section in wasmparser::Parser::new(0).parse_all(&component) { if let Payload::CustomSection(reader) = section.unwrap() { - let author = Author::parse_wasm(reader).unwrap(); + let author = Author::parse_custom_section(&reader).unwrap(); assert_eq!(author.to_string(), "Nori Cat"); parsed = true; } diff --git a/crates/wasm-metadata/src/producers.rs b/crates/wasm-metadata/src/producers.rs index ccec57e4d6..4ddac5e312 100644 --- a/crates/wasm-metadata/src/producers.rs +++ b/crates/wasm-metadata/src/producers.rs @@ -148,7 +148,7 @@ impl Producers { /// Merge into an existing wasm module. Rewrites the module with this producers section /// merged into its existing one, or adds this producers section if none is present. pub fn add_to_wasm(&self, input: &[u8]) -> Result> { - rewrite_wasm(&None, self, None, input) + rewrite_wasm(&None, self, &None, None, input) } pub(crate) fn display(&self, f: &mut fmt::Formatter, indent: usize) -> fmt::Result { diff --git a/crates/wasm-metadata/src/registry.rs b/crates/wasm-metadata/src/registry.rs index fbdc30937e..e52a349bef 100644 --- a/crates/wasm-metadata/src/registry.rs +++ b/crates/wasm-metadata/src/registry.rs @@ -44,7 +44,7 @@ impl RegistryMetadata { /// Merge into an existing wasm module. Rewrites the module with this registry-metadata section /// overwriting its existing one, or adds this registry-metadata section if none is present. pub fn add_to_wasm(&self, input: &[u8]) -> Result> { - rewrite_wasm(&None, &Producers::empty(), Some(&self), input) + rewrite_wasm(&None, &Producers::empty(), &None, Some(&self), input) } /// Parse a Wasm binary and extract the `Registry` section, if there is any. diff --git a/crates/wasm-metadata/src/rewrite.rs b/crates/wasm-metadata/src/rewrite.rs index 77d351729e..b1e8aa1424 100644 --- a/crates/wasm-metadata/src/rewrite.rs +++ b/crates/wasm-metadata/src/rewrite.rs @@ -1,4 +1,4 @@ -use crate::{ComponentNames, ModuleNames, Producers, RegistryMetadata}; +use crate::{Author, ComponentNames, ModuleNames, Producers, RegistryMetadata}; use anyhow::Result; use std::borrow::Cow; use std::mem; @@ -9,6 +9,7 @@ use wasmparser::{KnownCustom, Parser, Payload::*}; pub(crate) fn rewrite_wasm( add_name: &Option, add_producers: &Producers, + add_author: &Option, add_registry_metadata: Option<&RegistryMetadata>, input: &[u8], ) -> Result> { @@ -90,6 +91,13 @@ pub(crate) fn rewrite_wasm( continue; } } + KnownCustom::Unknown if c.name() == "author" => { + if add_author.is_none() { + let author = Author::parse_custom_section(c)?; + author.append_to(&mut output); + continue; + } + } _ => {} } } @@ -119,6 +127,9 @@ pub(crate) fn rewrite_wasm( // Encode into output: producers.section().append_to(&mut output); } + if let Some(author) = add_author { + author.append_to(&mut output); + } if add_registry_metadata.is_some() { let registry_metadata = wasm_encoder::CustomSection { name: Cow::Borrowed("registry-metadata"), diff --git a/crates/wasm-metadata/tests/component.rs b/crates/wasm-metadata/tests/component.rs index 88be96f7dd..ae588d9b69 100644 --- a/crates/wasm-metadata/tests/component.rs +++ b/crates/wasm-metadata/tests/component.rs @@ -11,6 +11,7 @@ fn add_to_empty_component() { language: vec![("bar".to_owned(), "1.0".to_owned())], processed_by: vec![("baz".to_owned(), "1.0".to_owned())], sdk: vec![], + author: Some(Author::new("Chashu Cat")), registry_metadata: Some(RegistryMetadata { authors: Some(vec!["foo".to_owned()]), description: Some("foo bar baz".to_owned()), @@ -58,6 +59,8 @@ fn add_to_empty_component() { "1.0" ); + assert_eq!(author.unwrap(), Author::new("Chashu Cat")); + let registry_metadata = registry_metadata.unwrap(); assert!(registry_metadata.validate().is_ok()); @@ -99,10 +102,8 @@ fn add_to_empty_component() { vec!["Tools".to_owned()] ); - assert!(author.is_none()); - assert_eq!(range.start, 0); - assert_eq!(range.end, 435); + assert_eq!(range.end, 454); } _ => panic!("metadata should be component"), } @@ -117,6 +118,7 @@ fn add_to_nested_component() { language: vec![("bar".to_owned(), "1.0".to_owned())], processed_by: vec![("baz".to_owned(), "1.0".to_owned())], sdk: vec![], + author: Some(Author::new("Chashu Cat")), registry_metadata: Some(RegistryMetadata { authors: Some(vec!["Foo".to_owned()]), ..Default::default() @@ -177,16 +179,16 @@ fn add_to_nested_component() { "1.0" ); + assert_eq!(author, &Some(Author::new("Chashu Cat"))); + let registry_metadata = registry_metadata.as_ref().unwrap(); assert_eq!( registry_metadata.authors.as_ref().unwrap(), &["Foo".to_owned()] ); - assert!(author.is_none()); - - assert_eq!(range.start, 10); - assert_eq!(range.end, 123); + assert_eq!(range.start, 11); + assert_eq!(range.end, 143); } _ => panic!("child is a module"), } diff --git a/crates/wasm-metadata/tests/module.rs b/crates/wasm-metadata/tests/module.rs index e177c2c0f6..c59484a020 100644 --- a/crates/wasm-metadata/tests/module.rs +++ b/crates/wasm-metadata/tests/module.rs @@ -11,6 +11,7 @@ fn add_to_empty_module() { language: vec![("bar".to_owned(), "1.0".to_owned())], processed_by: vec![("baz".to_owned(), "1.0".to_owned())], sdk: vec![], + author: Some(Author::new("Chashu Cat")), registry_metadata: Some(RegistryMetadata { authors: Some(vec!["foo".to_owned()]), description: Some("foo bar baz".to_owned()), @@ -56,6 +57,8 @@ fn add_to_empty_module() { "1.0" ); + assert_eq!(author.unwrap(), Author::new("Chashu Cat")); + let registry_metadata = registry_metadata.unwrap(); assert!(registry_metadata.validate().is_ok()); @@ -97,10 +100,8 @@ fn add_to_empty_module() { vec!["Tools".to_owned()] ); - assert!(author.is_none()); - assert_eq!(range.start, 0); - assert_eq!(range.end, 425); + assert_eq!(range.end, 444); } _ => panic!("metadata should be module"), }