diff --git a/src/parse/text/simple_extensions/argument.rs b/src/parse/text/simple_extensions/argument.rs index e5913ee..065fd53 100644 --- a/src/parse/text/simple_extensions/argument.rs +++ b/src/parse/text/simple_extensions/argument.rs @@ -11,13 +11,13 @@ use crate::{ #[derive(Clone, Debug)] pub enum ArgumentsItem { /// Arguments that support a fixed set of declared values as constant arguments. - EnumArgument(EnumArgument), + EnumArgument(EnumerationArg), /// Arguments that refer to a data value. - ValueArgument(ValueArgument), + ValueArgument(ValueArg), /// Arguments that are used only to inform the evaluation and/or type derivation of the function. - TypeArgument(TypeArgument), + TypeArgument(TypeArg), } impl ArgumentsItem { @@ -50,24 +50,9 @@ impl Parse for simple_extensions::ArgumentsItem { fn parse(self, ctx: &mut C) -> Result { match self { - simple_extensions::ArgumentsItem::Variant0 { - name, - description, - options, - } => EnumArgument::parse(name, description, options).map(Into::into), - - simple_extensions::ArgumentsItem::Variant1 { - name, - description, - value, - constant, - } => ValueArgument::parse(ctx, name, description, value, constant).map(Into::into), - - simple_extensions::ArgumentsItem::Variant2 { - name, - description, - type_, - } => TypeArgument::parse(ctx, name, description, type_).map(Into::into), + simple_extensions::ArgumentsItem::EnumerationArg(arg) => Ok(ctx.parse(arg)?.into()), + simple_extensions::ArgumentsItem::ValueArg(arg) => Ok(ctx.parse(arg)?.into()), + simple_extensions::ArgumentsItem::TypeArg(arg) => Ok(ctx.parse(arg)?.into()), } } } @@ -75,37 +60,9 @@ impl Parse for simple_extensions::ArgumentsItem { impl From for simple_extensions::ArgumentsItem { fn from(value: ArgumentsItem) -> Self { match value { - ArgumentsItem::EnumArgument(EnumArgument { - name, - description, - options, - }) => simple_extensions::ArgumentsItem::Variant0 { - name, - description, - options, - }, - - ArgumentsItem::ValueArgument(ValueArgument { - name, - description, - value, - constant, - }) => simple_extensions::ArgumentsItem::Variant1 { - name, - description, - value, - constant, - }, - - ArgumentsItem::TypeArgument(TypeArgument { - name, - description, - type_, - }) => simple_extensions::ArgumentsItem::Variant2 { - name, - description, - type_, - }, + ArgumentsItem::EnumArgument(arg) => arg.into(), + ArgumentsItem::ValueArgument(arg) => arg.into(), + ArgumentsItem::TypeArgument(arg) => arg.into(), } } } @@ -113,9 +70,9 @@ impl From for simple_extensions::ArgumentsItem { /// Parse errors for [simple_extensions::ArgumentsItem]. #[derive(Debug, Error, PartialEq)] pub enum ArgumentsItemError { - /// Empty enumeration option. - #[error("empty enumeration option")] - EmptyEnumOption, + /// Invalid enumeration options. + #[error("invalid enumeration options: {0}")] + InvalidEnumOptions(#[from] EnumOptionsError), /// Empty optional field. #[error("the optional field `{0}` is empty and should be removed")] @@ -124,7 +81,7 @@ pub enum ArgumentsItemError { /// Arguments that support a fixed set of declared values as constant arguments. #[derive(Clone, Debug, PartialEq)] -pub struct EnumArgument { +pub struct EnumerationArg { /// A human-readable name for this argument to help clarify use. name: Option, @@ -132,36 +89,99 @@ pub struct EnumArgument { description: Option, /// List of valid string options for this argument. - options: Vec, + options: EnumOptions, } -impl EnumArgument { - fn parse( - name: Option, - description: Option, - options: Vec, - ) -> Result { - if options.iter().any(String::is_empty) { - return Err(ArgumentsItemError::EmptyEnumOption); - } +impl Parse for simple_extensions::EnumerationArg { + type Parsed = EnumerationArg; + + type Error = ArgumentsItemError; - Ok(EnumArgument { - name: ArgumentsItem::parse_name(name)?, - description: ArgumentsItem::parse_description(description)?, - options, + fn parse(self, ctx: &mut C) -> Result { + Ok(EnumerationArg { + name: ArgumentsItem::parse_name(self.name)?, + description: ArgumentsItem::parse_description(self.description)?, + options: ctx.parse(self.options)?, }) } } -impl From for ArgumentsItem { - fn from(value: EnumArgument) -> Self { +impl From for simple_extensions::EnumerationArg { + fn from(value: EnumerationArg) -> Self { + simple_extensions::EnumerationArg { + name: value.name, + description: value.description, + options: value.options.into(), + } + } +} + +impl From for simple_extensions::ArgumentsItem { + fn from(value: EnumerationArg) -> Self { + simple_extensions::ArgumentsItem::EnumerationArg(value.into()) + } +} + +impl From for ArgumentsItem { + fn from(value: EnumerationArg) -> Self { ArgumentsItem::EnumArgument(value) } } +/// List of valid string options +#[derive(Clone, Debug, PartialEq)] +pub struct EnumOptions(Vec); + +impl Parse for simple_extensions::EnumOptions { + type Parsed = EnumOptions; + + type Error = EnumOptionsError; + + fn parse(self, _ctx: &mut C) -> Result { + let options = self.0; + if options.is_empty() { + return Err(EnumOptionsError::EmptyList); + } + + let mut unique_set = std::collections::HashSet::new(); + for option in options.iter() { + if option.is_empty() { + return Err(EnumOptionsError::EmptyOption); + } + if !unique_set.insert(option) { + return Err(EnumOptionsError::DuplicatedOption(option.clone())); + } + } + + Ok(EnumOptions(options)) + } +} + +impl From for simple_extensions::EnumOptions { + fn from(value: EnumOptions) -> Self { + simple_extensions::EnumOptions(value.0) + } +} + +/// Parse errors for [simple_extensions::EnumOptions]. +#[derive(Debug, Error, PartialEq)] +pub enum EnumOptionsError { + /// Empty list. + #[error("empty list")] + EmptyList, + + /// Duplicated option. + #[error("duplicated option: {0}")] + DuplicatedOption(String), + + /// Empty option. + #[error("empty option")] + EmptyOption, +} + /// Arguments that refer to a data value. #[derive(Clone, Debug)] -pub struct ValueArgument { +pub struct ValueArg { /// A human-readable name for this argument to help clarify use. name: Option, @@ -179,33 +199,47 @@ pub struct ValueArgument { constant: Option, } -impl ValueArgument { - fn parse( - // Retain this parameter for future value parsing - _ctx: &mut C, - name: Option, - description: Option, - value: simple_extensions::Type, - constant: Option, - ) -> Result { - Ok(ValueArgument { - name: ArgumentsItem::parse_name(name)?, - description: ArgumentsItem::parse_description(description)?, - value, - constant, +impl Parse for simple_extensions::ValueArg { + type Parsed = ValueArg; + + type Error = ArgumentsItemError; + + fn parse(self, _ctx: &mut C) -> Result { + Ok(ValueArg { + name: ArgumentsItem::parse_name(self.name)?, + description: ArgumentsItem::parse_description(self.description)?, + value: self.value, + constant: self.constant, }) } } -impl From for ArgumentsItem { - fn from(value: ValueArgument) -> Self { +impl From for simple_extensions::ValueArg { + fn from(value: ValueArg) -> Self { + simple_extensions::ValueArg { + name: value.name, + description: value.description, + value: value.value, + constant: value.constant, + } + } +} + +impl From for simple_extensions::ArgumentsItem { + fn from(value: ValueArg) -> Self { + simple_extensions::ArgumentsItem::ValueArg(value.into()) + } +} + +impl From for ArgumentsItem { + fn from(value: ValueArg) -> Self { ArgumentsItem::ValueArgument(value) } } /// Arguments that are used only to inform the evaluation and/or type derivation of the function. #[derive(Clone, Debug, PartialEq)] -pub struct TypeArgument { +pub struct TypeArg { /// A human-readable name for this argument to help clarify use. name: Option, @@ -218,24 +252,38 @@ pub struct TypeArgument { type_: String, } -impl TypeArgument { - fn parse( - // Retain this parameter for future type parsing - _ctx: &mut C, - name: Option, - description: Option, - type_: String, - ) -> Result { - Ok(TypeArgument { - name: ArgumentsItem::parse_name(name)?, - description: ArgumentsItem::parse_description(description)?, - type_, +impl Parse for simple_extensions::TypeArg { + type Parsed = TypeArg; + + type Error = ArgumentsItemError; + + fn parse(self, _ctx: &mut C) -> Result { + Ok(TypeArg { + name: ArgumentsItem::parse_name(self.name)?, + description: ArgumentsItem::parse_description(self.description)?, + type_: self.type_, }) } } -impl From for ArgumentsItem { - fn from(value: TypeArgument) -> Self { +impl From for simple_extensions::TypeArg { + fn from(value: TypeArg) -> Self { + simple_extensions::TypeArg { + name: value.name, + description: value.description, + type_: value.type_, + } + } +} + +impl From for simple_extensions::ArgumentsItem { + fn from(value: TypeArg) -> Self { + simple_extensions::ArgumentsItem::TypeArg(value.into()) + } +} + +impl From for ArgumentsItem { + fn from(value: TypeArg) -> Self { ArgumentsItem::TypeArgument(value) } } @@ -248,11 +296,12 @@ mod tests { #[test] fn parse_enum_argument() -> Result<(), ArgumentsItemError> { - let enum_argument = simple_extensions::ArgumentsItem::Variant0 { - name: Some("arg".to_string()), - description: Some("desc".to_string()), - options: vec!["OVERFLOW".to_string()], - }; + let enum_argument = + simple_extensions::ArgumentsItem::EnumerationArg(simple_extensions::EnumerationArg { + name: Some("arg".to_string()), + description: Some("desc".to_string()), + options: simple_extensions::EnumOptions(vec!["OVERFLOW".to_string()]), + }); let item = enum_argument.parse(&mut Context::default())?; let enum_argument = match item { ArgumentsItem::EnumArgument(enum_argument) => enum_argument, @@ -260,41 +309,66 @@ mod tests { }; assert_eq!( enum_argument, - EnumArgument { + EnumerationArg { name: Some("arg".to_string()), description: Some("desc".to_string()), - options: vec!["OVERFLOW".to_string()], + options: EnumOptions(vec!["OVERFLOW".to_string()]), } ); Ok(()) } #[test] - fn parse_enum_argument_with_empty_option() -> Result<(), ArgumentsItemError> { - let enum_argument = simple_extensions::ArgumentsItem::Variant0 { - name: None, - description: Some("desc".to_string()), - options: vec!["".to_string()], - }; - let is_err = enum_argument + fn parse_empty_enum_options() -> Result<(), ArgumentsItemError> { + let options = simple_extensions::EnumOptions(vec![]); + let is_err = options + .parse(&mut Context::default()) + .err() + .map(|err| matches!(err, EnumOptionsError::EmptyList)); + assert_eq!(is_err, Some(true)); + Ok(()) + } + + #[test] + fn parse_enum_options_with_empty_value() -> Result<(), ArgumentsItemError> { + let options = simple_extensions::EnumOptions(vec!["".to_string()]); + let is_err = options + .parse(&mut Context::default()) + .err() + .map(|err| matches!(err, EnumOptionsError::EmptyOption)); + assert_eq!(is_err, Some(true)); + Ok(()) + } + + #[test] + fn parse_enum_argument_with_duplicated_option() -> Result<(), ArgumentsItemError> { + let options = + simple_extensions::EnumOptions(vec!["OVERFLOW".to_string(), "OVERFLOW".to_string()]); + let is_err = options + .clone() .parse(&mut Context::default()) .err() - .map(|err| matches!(err, ArgumentsItemError::EmptyEnumOption)); + .map(|err| { + matches!( + err, + EnumOptionsError::DuplicatedOption(opt) if opt == "OVERFLOW" + ) + }); assert_eq!(is_err, Some(true)); Ok(()) } #[test] fn parse_value_argument() -> Result<(), ArgumentsItemError> { - let item = simple_extensions::ArgumentsItem::Variant1 { + let item = simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg { name: Some("arg".to_string()), description: Some("desc".to_string()), - value: text::simple_extensions::Type::Variant0("".to_string()), + value: text::simple_extensions::Type::Variant0("i32".to_string()), constant: Some(true), - }; + }); let item = item.parse(&mut Context::default())?; match item { - ArgumentsItem::ValueArgument(ValueArgument { + ArgumentsItem::ValueArgument(ValueArg { name, description, value, @@ -303,7 +377,7 @@ mod tests { assert_eq!(name, Some("arg".to_string())); assert_eq!(description, Some("desc".to_string())); assert!( - matches!(value, text::simple_extensions::Type::Variant0(type_) if type_.is_empty()) + matches!(value, text::simple_extensions::Type::Variant0(type_) if type_ == "i32") ); assert_eq!(constant, Some(true)); } @@ -314,14 +388,14 @@ mod tests { #[test] fn parse_type_argument() -> Result<(), ArgumentsItemError> { - let type_argument = simple_extensions::ArgumentsItem::Variant2 { + let type_argument = simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg { name: Some("arg".to_string()), description: Some("desc".to_string()), type_: "".to_string(), - }; + }); let item = type_argument.parse(&mut Context::default())?; match item { - ArgumentsItem::TypeArgument(TypeArgument { + ArgumentsItem::TypeArgument(TypeArg { name, description, type_, @@ -338,32 +412,32 @@ mod tests { #[test] fn parse_argument_with_nones() -> Result<(), ArgumentsItemError> { let items = vec![ - simple_extensions::ArgumentsItem::Variant0 { + simple_extensions::ArgumentsItem::EnumerationArg(simple_extensions::EnumerationArg { name: None, description: None, - options: vec!["OVERFLOW".to_string()], - }, - simple_extensions::ArgumentsItem::Variant1 { + options: simple_extensions::EnumOptions(vec!["OVERFLOW".to_string()]), + }), + simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg { name: None, description: None, - value: text::simple_extensions::Type::Variant0("".to_string()), + value: text::simple_extensions::Type::Variant0("i32".to_string()), constant: None, - }, - simple_extensions::ArgumentsItem::Variant2 { + }), + simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg { name: None, description: None, type_: "".to_string(), - }, + }), ]; for item in items { let item = item.parse(&mut Context::default())?; let (name, description) = match item { - ArgumentsItem::EnumArgument(EnumArgument { + ArgumentsItem::EnumArgument(EnumerationArg { name, description, .. }) => (name, description), - ArgumentsItem::ValueArgument(ValueArgument { + ArgumentsItem::ValueArgument(ValueArg { name, description, constant, @@ -373,7 +447,7 @@ mod tests { (name, description) } - ArgumentsItem::TypeArgument(TypeArgument { + ArgumentsItem::TypeArgument(TypeArg { name, description, .. }) => (name, description), }; @@ -387,22 +461,22 @@ mod tests { #[test] fn parse_argument_with_empty_fields() -> Result<(), ArgumentsItemError> { let items = vec![ - simple_extensions::ArgumentsItem::Variant0 { + simple_extensions::ArgumentsItem::EnumerationArg(simple_extensions::EnumerationArg { name: Some("".to_string()), - description: Some("desc".to_string()), - options: vec!["OVERFLOW".to_string()], - }, - simple_extensions::ArgumentsItem::Variant1 { + description: None, + options: simple_extensions::EnumOptions(vec!["OVERFLOW".to_string()]), + }), + simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg { name: Some("".to_string()), - description: Some("desc".to_string()), - value: text::simple_extensions::Type::Variant0("".to_string()), - constant: Some(true), - }, - simple_extensions::ArgumentsItem::Variant2 { + description: None, + value: text::simple_extensions::Type::Variant0("i32".to_string()), + constant: None, + }), + simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg { name: Some("".to_string()), - description: Some("desc".to_string()), + description: None, type_: "".to_string(), - }, + }), ]; for item in items { assert_eq!( @@ -412,22 +486,22 @@ mod tests { } let items = vec![ - simple_extensions::ArgumentsItem::Variant0 { - name: Some("arg".to_string()), + simple_extensions::ArgumentsItem::EnumerationArg(simple_extensions::EnumerationArg { + name: None, description: Some("".to_string()), - options: vec!["OVERFLOW".to_string()], - }, - simple_extensions::ArgumentsItem::Variant1 { - name: Some("arg".to_string()), + options: simple_extensions::EnumOptions(vec!["OVERFLOW".to_string()]), + }), + simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg { + name: None, description: Some("".to_string()), - value: text::simple_extensions::Type::Variant0("".to_string()), - constant: Some(true), - }, - simple_extensions::ArgumentsItem::Variant2 { - name: Some("arg".to_string()), + value: text::simple_extensions::Type::Variant0("i32".to_string()), + constant: None, + }), + simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg { + name: None, description: Some("".to_string()), type_: "".to_string(), - }, + }), ]; for item in items { assert_eq!( @@ -443,23 +517,25 @@ mod tests { #[test] fn from_enum_argument() { - let item: ArgumentsItem = EnumArgument { + let item: ArgumentsItem = EnumerationArg { name: Some("arg".to_string()), description: Some("desc".to_string()), - options: vec!["OVERFLOW".to_string()], + options: EnumOptions(vec!["OVERFLOW".to_string()]), } .into(); let item: text::simple_extensions::ArgumentsItem = item.into(); match item { - text::simple_extensions::ArgumentsItem::Variant0 { - name, - description, - options, - } => { + text::simple_extensions::ArgumentsItem::EnumerationArg( + simple_extensions::EnumerationArg { + name, + description, + options, + }, + ) => { assert_eq!(name, Some("arg".to_string())); assert_eq!(description, Some("desc".to_string())); - assert_eq!(options, vec!["OVERFLOW".to_string()]); + assert_eq!(options.0, vec!["OVERFLOW".to_string()]); } _ => unreachable!(), } @@ -467,7 +543,7 @@ mod tests { #[test] fn from_value_argument() { - let item: ArgumentsItem = ValueArgument { + let item: ArgumentsItem = ValueArg { name: Some("arg".to_string()), description: Some("desc".to_string()), value: text::simple_extensions::Type::Variant0("".to_string()), @@ -477,12 +553,12 @@ mod tests { let item: text::simple_extensions::ArgumentsItem = item.into(); match item { - text::simple_extensions::ArgumentsItem::Variant1 { + text::simple_extensions::ArgumentsItem::ValueArg(simple_extensions::ValueArg { name, description, value, constant, - } => { + }) => { assert_eq!(name, Some("arg".to_string())); assert_eq!(description, Some("desc".to_string())); assert!( @@ -496,7 +572,7 @@ mod tests { #[test] fn from_type_argument() { - let item: ArgumentsItem = TypeArgument { + let item: ArgumentsItem = TypeArg { name: Some("arg".to_string()), description: Some("desc".to_string()), type_: "".to_string(), @@ -505,11 +581,11 @@ mod tests { let item: text::simple_extensions::ArgumentsItem = item.into(); match item { - text::simple_extensions::ArgumentsItem::Variant2 { + text::simple_extensions::ArgumentsItem::TypeArg(simple_extensions::TypeArg { name, description, type_, - } => { + }) => { assert_eq!(name, Some("arg".to_string())); assert_eq!(description, Some("desc".to_string())); assert_eq!(type_, "");