diff --git a/.rustfmt.toml b/.rustfmt.toml index 1df589a7..399dd8da 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,5 +1,12 @@ -normalize_comments = true -reorder_imports = true +# Basic +max_width = 100 +# Imports group_imports = "StdExternalCrate" -newline_style = "Unix" imports_granularity = "Module" +reorder_imports = true +# Comments +comment_width = 100 +normalize_comments = true +wrap_comments = true +# Consistency +newline_style = "Unix" diff --git a/data/incunabula.hcl b/data/incunabula.hcl index 375df82f..7ed894b6 100644 --- a/data/incunabula.hcl +++ b/data/incunabula.hcl @@ -128,10 +128,10 @@ person "hannes_hug" { created_at = "1637624150959055000" created_by = "dsp-metadata-gui" - family_name "1" { + family_name { text = "Hug" } - given_name "1" { + given_name { text = "Hannes" } // reference to organization (1-n) @@ -142,10 +142,10 @@ person "lukas_rosenthaler" { created_at = "1637624150959124000" created_by = "dsp-metadata-gui" - family_name "1" { + family_name { text = "Rosenthaler" } - given_name "1" { + given_name { text = "Lukas" } // reference to organization (1-n) @@ -156,10 +156,10 @@ person "lothar_schmitt" { created_at = "1637624150959191000" created_by = "dsp-metadata-gui" - family_name "1" { + family_name { text = "Schmitt" } - given_name "1" { + given_name { text = "Lothar" } @@ -171,13 +171,13 @@ person "katrin_graf_lamei" { created_at = "1637624150959244000" created_by = "dsp-metadata-gui" - family_name "1" { + family_name { text = "Graf" } - family_name "2" { + family_name { text = "Lamei" } - given_name "1" { + given_name { text = "Katrin" } diff --git a/src/dsp_meta/domain/convert/project.rs b/src/dsp_meta/domain/convert/project.rs index 87466de6..284e67a0 100644 --- a/src/dsp_meta/domain/convert/project.rs +++ b/src/dsp_meta/domain/convert/project.rs @@ -3,11 +3,12 @@ use tracing::warn; use crate::domain::value::discipline::Discipline; use crate::domain::value::iso_code::IsoCode; +use crate::domain::value::publication::Publication; use crate::domain::value::spatial_coverage::SpacialCoverage; use crate::domain::value::temporal_coverage::TemporalCoverage; use crate::domain::value::{ AlternativeName, ContactPoint, CreatedAt, CreatedBy, Description, EndDate, HowToCite, Keyword, - LangString, Name, Publication, Shortcode, StartDate, TeaserText, URL, + LangString, Name, Shortcode, StartDate, TeaserText, URL, }; use crate::errors::DspMetaError; @@ -37,6 +38,7 @@ impl TryFrom> for ExtractedProjectAttributes { let mut end_date: Option = None; let mut contact_point: Option = None; + // FIXME: throw error on duplicate attributes for attribute in attributes { match attribute.key() { "created_at" => { @@ -130,6 +132,15 @@ impl TryFrom> for ExtractedProjectAttributes { } } +const ALTERNATIVE_NAME_BLOCK: &str = "alternative_name"; +const DESCRIPTION_BLOCK: &str = "description"; +const URL_BLOCK: &str = "url"; +const KEYWORD_BLOCK: &str = "keyword"; +const DISCIPLINE_BLOCK: &str = "discipline"; +const SPACIAL_COVERAGE_BLOCK: &str = "spacial_coverage"; +const TEMPORAL_COVERAGE_BLOCK: &str = "temporal_coverage"; +const PUBLICATION_BLOCK: &str = "publication"; + #[derive(Debug, Default, PartialEq)] pub struct ExtractedProjectBlocks { pub alternative_names: Vec, @@ -157,10 +168,10 @@ impl TryFrom> for ExtractedProjectBlocks { for block in blocks { match block.identifier.as_str() { - "alternative_name" => { + ALTERNATIVE_NAME_BLOCK => { alternative_names.push(AlternativeName::try_from(block)?); } - "description" => { + DESCRIPTION_BLOCK => { description = if description.is_none() { Ok(Some(Description::try_from(block)?)) } else { @@ -169,7 +180,7 @@ impl TryFrom> for ExtractedProjectBlocks { )) }? } - "url" => { + URL_BLOCK => { url = if url.is_none() { Ok(Some(URL::try_from(block)?)) } else { @@ -178,21 +189,13 @@ impl TryFrom> for ExtractedProjectBlocks { )) }? } - "keyword" => { - keywords.push(Keyword::try_from(block)?); - } - "discipline" => { - disciplines.push(Discipline::try_from(block)?); - } - "spacial_coverage" => { - spacial_coverages.push(SpacialCoverage::try_from(block)?); - } - "temporal_coverage" => { - temporal_coverages.push(TemporalCoverage::try_from(block)?); - } - "publication" => { - publications = vec![]; + KEYWORD_BLOCK => keywords.push(Keyword::try_from(block)?), + DISCIPLINE_BLOCK => disciplines.push(Discipline::try_from(block)?), + SPACIAL_COVERAGE_BLOCK => spacial_coverages.push(SpacialCoverage::try_from(block)?), + TEMPORAL_COVERAGE_BLOCK => { + temporal_coverages.push(TemporalCoverage::try_from(block)?) } + PUBLICATION_BLOCK => publications.push(Publication::try_from(block)?), _ => { // catch all warn!("Parse error: unknown block '{}'.", block.identifier); @@ -544,9 +547,14 @@ mod tests { #[test] fn extract_publications() { - let blocks = vec![]; + let input1 = block!( + publication { + text = "A publication" + } + ); + let blocks = vec![&input1]; let result = ExtractedProjectBlocks::try_from(blocks).unwrap(); - assert_eq!(result.publications.len(), 0); + assert_eq!(result.publications.len(), 1); } #[traced_test] diff --git a/src/dsp_meta/domain/entity/project.rs b/src/dsp_meta/domain/entity/project.rs index b6696901..721421b7 100644 --- a/src/dsp_meta/domain/entity/project.rs +++ b/src/dsp_meta/domain/entity/project.rs @@ -1,8 +1,9 @@ use crate::domain::convert::project::{ExtractedProjectAttributes, ExtractedProjectBlocks}; use crate::domain::value::discipline::Discipline; +use crate::domain::value::publication::Publication; use crate::domain::value::{ AlternativeName, ContactPoint, CreatedAt, CreatedBy, Description, EndDate, HowToCite, Keyword, - Name, Publication, Shortcode, StartDate, TeaserText, URL, + Name, Shortcode, StartDate, TeaserText, URL, }; use crate::errors::DspMetaError; @@ -40,7 +41,8 @@ impl TryFrom<&hcl::Block> for Project { } // extract the project attributes - // created_at, created_by, shortcode, name, teaser_text, how_to_cite, start_date, end_date, datasets, funders, grants + // created_at, created_by, shortcode, name, teaser_text, how_to_cite, start_date, end_date, + // datasets, funders, grants let attributes: Vec<&hcl::Attribute> = project_block.body.attributes().collect(); diff --git a/src/dsp_meta/domain/value/mod.rs b/src/dsp_meta/domain/value/mod.rs index 1bb4f4ac..b52a4428 100644 --- a/src/dsp_meta/domain/value/mod.rs +++ b/src/dsp_meta/domain/value/mod.rs @@ -7,7 +7,9 @@ use crate::errors::DspMetaError; pub(crate) mod discipline; pub(crate) mod iso_code; mod lang_text_data; +pub(crate) mod publication; mod ref_data; +mod simple_text_data; pub(crate) mod spatial_coverage; pub(crate) mod temporal_coverage; pub(crate) mod version; @@ -183,12 +185,3 @@ pub struct ContactPoint(pub String); #[derive(Debug, Default, PartialEq)] pub struct Title(pub String); - -#[derive(Debug, PartialEq)] -pub struct Publication(String); - -impl Default for Publication { - fn default() -> Self { - Publication("Default publication".to_string()) - } -} diff --git a/src/dsp_meta/domain/value/publication.rs b/src/dsp_meta/domain/value/publication.rs new file mode 100644 index 00000000..f9f72ca6 --- /dev/null +++ b/src/dsp_meta/domain/value/publication.rs @@ -0,0 +1,62 @@ +use crate::domain::value::simple_text_data::SimpleTextData; +use crate::errors::DspMetaError; + +const PUBLICATION_BLOCK_IDENTIFIER: &str = "publication"; + +#[derive(Debug, PartialEq)] +pub enum Publication { + SimpleText(SimpleTextData), +} + +impl TryFrom<&hcl::Block> for Publication { + type Error = DspMetaError; + + fn try_from(block: &hcl::Block) -> Result { + if block.identifier.as_str() != PUBLICATION_BLOCK_IDENTIFIER { + let msg = format!( + "The passed block is not named correctly. Expected 'publication', however got '{}' instead.", + block.identifier.as_str() + ); + return Err(DspMetaError::CreateValueObject(msg)); + } + + let attributes: Vec<&hcl::Attribute> = block.body.attributes().collect(); + + SimpleTextData::try_from(attributes).map(Publication::SimpleText) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_try_from_correct_block() { + let block = hcl::block!( + publication { + text = "A publication" + } + ); + + let publication = Publication::try_from(&block).unwrap(); + + match publication { + Publication::SimpleText(data) => { + assert_eq!(data, SimpleTextData("A publication".to_string())); + } + } + } + + #[test] + fn test_try_from_incorrect_block() { + let block = hcl::block!( + publication_other { + text = "A publication" + } + ); + + let publication = Publication::try_from(&block); + + assert!(publication.is_err()); + } +} diff --git a/src/dsp_meta/domain/value/simple_text_data.rs b/src/dsp_meta/domain/value/simple_text_data.rs new file mode 100644 index 00000000..9ef83154 --- /dev/null +++ b/src/dsp_meta/domain/value/simple_text_data.rs @@ -0,0 +1,81 @@ +use tracing::warn; + +use crate::errors::DspMetaError; + +const TEXT_ATTRIBUTE_IDENTIFIER: &str = "text"; + +#[derive(Debug, PartialEq)] +pub struct SimpleTextData(pub String); + +impl TryFrom> for SimpleTextData { + type Error = DspMetaError; + + fn try_from(attributes: Vec<&hcl::Attribute>) -> Result { + let mut text_attribute_value: Option = None; + + for attribute in attributes { + match attribute.key() { + TEXT_ATTRIBUTE_IDENTIFIER => { + if text_attribute_value.is_some() { + return Err(DspMetaError::CreateValueObject( + "Multiple text attributes are not allowed.".to_string(), + )); + } + text_attribute_value = match attribute.expr() { + hcl::Expression::String(value) => Ok(Some(value.to_owned())), + _ => Err(DspMetaError::CreateValueObject( + "The attribute value is not of String type.".to_string(), + )), + }?; + } + _ => { + warn!("Parse error: unknown attribute '{}'.", attribute.key()); + } + } + } + Ok(SimpleTextData(text_attribute_value.ok_or_else(|| { + DspMetaError::CreateValueObject("Missing text attribute.".to_string()) + })?)) + } +} + +#[cfg(test)] +mod tests { + + use tracing_test::traced_test; + + use super::*; + + #[test] + fn test_try_from_attributes() { + let attribute = hcl::Attribute::new("text", "some text"); + let text_data = SimpleTextData::try_from(vec![&attribute]).unwrap(); + assert_eq!(text_data, SimpleTextData("some text".to_string())); + } + + #[traced_test] + #[test] + fn test_try_from_attributes_missing_text() { + let attribute = hcl::Attribute::new("some_other_attribute", "some text"); + let text_data = SimpleTextData::try_from(vec![&attribute]); + assert!(text_data.is_err()); + assert!(logs_contain( + "Parse error: unknown attribute 'some_other_attribute'" + )); + } + + #[test] + fn test_try_from_attributes_multiple_text() { + let attribute = hcl::Attribute::new("text", "some text"); + let attribute2 = hcl::Attribute::new("text", "some text"); + let text_data = SimpleTextData::try_from(vec![&attribute, &attribute2]); + assert!(text_data.is_err()); + } + + #[test] + fn test_try_from_attributes_wrong_type() { + let attribute = hcl::Attribute::new("text", 1); + let text_data = SimpleTextData::try_from(vec![&attribute]); + assert!(text_data.is_err()); + } +}