// @@ begin test lint list maintained by maint/add_warning @@ #![allow(clippy::bool_assert_comparison)] #![allow(clippy::clone_on_copy)] #![allow(clippy::dbg_macro)] #![allow(clippy::mixed_attributes_style)] #![allow(clippy::print_stderr)] #![allow(clippy::print_stdout)] #![allow(clippy::single_char_pattern)] #![allow(clippy::unwrap_used)] #![allow(clippy::unchecked_duration_subtraction)] #![allow(clippy::useless_vec)] #![allow(clippy::needless_pass_by_value)] //! #![allow(clippy::needless_borrows_for_generic_args)] // TODO add to maint/add_warning use super::*; use anyhow::Context as _; use testresult::TestResult; fn default() -> T { Default::default() } #[derive(Deftly, Debug, Default, Clone, Eq, PartialEq)] #[derive_deftly(NetdocParseable)] struct Top { top_intro: (), needed: (String,), optional: Option<(String,)>, several: Vec<(String,)>, #[deftly(netdoc(default))] defaulted: (String,), #[deftly(netdoc(keyword = "renamed"))] t4_renamed: Option<(String,)>, #[deftly(netdoc(subdoc))] sub1: Sub1, #[deftly(netdoc(subdoc))] sub2: Option, #[deftly(netdoc(subdoc))] sub3: Vec, #[deftly(netdoc(subdoc, default))] sub4: Sub4, } #[derive(Deftly, Debug, Default, Clone, Eq, PartialEq)] #[derive_deftly(NetdocParseable)] struct Sub1 { sub1_intro: (), sub1_field: Option<(String,)>, #[deftly(netdoc(flatten))] flatten: Flat1, } #[derive(Deftly, Debug, Default, Clone, Eq, PartialEq)] #[derive_deftly(NetdocParseableFields)] struct Flat1 { flat_needed: (String,), flat_optional: Option<(String,)>, flat_several: Vec<(String,)>, flat_defaulted: Option<(String,)>, #[deftly(netdoc(single_arg))] flat_arg_needed: String, #[deftly(netdoc(single_arg))] flat_arg_optional: Option, #[deftly(netdoc(single_arg))] flat_arg_several: Vec, #[deftly(netdoc(single_arg, default))] flat_arg_defaulted: String, #[deftly(netdoc(with = "needs_with_parse"))] flat_with_needed: NeedsWith, #[deftly(netdoc(with = "needs_with_parse"))] flat_with_optional: Option, #[deftly(netdoc(with = "needs_with_parse"))] flat_with_several: Vec, } #[derive(Deftly, Debug, Default, Clone, Eq, PartialEq)] #[derive_deftly(NetdocParseable)] struct Sub2 { #[deftly(netdoc(with = "needs_with_intro"))] sub2_intro: NeedsWith, sub2_field: Option<(String,)>, #[deftly(netdoc(single_arg))] arg_needed: String, #[deftly(netdoc(single_arg))] arg_optional: Option, #[deftly(netdoc(single_arg))] arg_several: Vec, #[deftly(netdoc(single_arg, default))] arg_defaulted: String, #[deftly(netdoc(with = "needs_with_parse"))] with_needed: NeedsWith, #[deftly(netdoc(with = "needs_with_parse"))] with_optional: Option, #[deftly(netdoc(with = "needs_with_parse"))] with_several: Vec, #[deftly(netdoc(subdoc))] subsub: SubSub, } #[derive(Deftly, Debug, Default, Clone, Eq, PartialEq)] #[derive_deftly(NetdocParseable)] struct Sub3 { sub3_intro: (), sub3_field: Option<(String,)>, } #[derive(Deftly, Debug, Default, Clone, Eq, PartialEq)] #[derive_deftly(NetdocParseable)] struct Sub4 { sub4_intro: (), sub4_field: Option<(String,)>, } #[derive(Deftly, Debug, Default, Clone, Eq, PartialEq)] #[derive_deftly(NetdocParseable)] struct SubSub { #[deftly(netdoc(single_arg))] subsub_intro: String, subsub_field: Option<(String,)>, } #[derive(Debug, Default, Clone, Eq, PartialEq)] struct NeedsWith; impl NeedsWith { fn parse_expecting(exp: &str, args: &mut ArgumentStream<'_>) -> Result { let field = "in needs with"; let got = args.next().ok_or(EP::MissingArgument { field })?; (got == exp) .then_some(NeedsWith) .ok_or(EP::InvalidArgument { field }) } } mod needs_with_parse { use super::*; pub(super) fn from_unparsed(mut item: UnparsedItem<'_>) -> Result { NeedsWith::parse_expecting("normal", item.args_mut()) } } mod needs_with_intro { use super::*; pub(super) fn from_unparsed(mut item: UnparsedItem<'_>) -> Result { NeedsWith::parse_expecting("intro", item.args_mut()) } } mod needs_with_arg { use super::*; pub(super) fn from_args( args: &mut ArgumentStream, _field: &'static str, ) -> Result { NeedsWith::parse_expecting("arg", args) } pub(super) fn from_rest(s: &str) -> Result { (s == "rest of line").then_some(NeedsWith).ok_or(()) } } fn t_ok(doc: &str, exp: &[D]) -> TestResult<()> where D: NetdocParseable + Debug + PartialEq, { if exp.len() == 1 { let got = parse_netdoc::(doc, "").context(doc.to_owned())?; assert_eq!(got, exp[0], "doc={doc}"); } let got = parse_netdoc_multiple::(doc, "")?; assert_eq!(got, exp, "doc={doc}"); Ok(()) } #[allow(clippy::unnecessary_wraps)] // Result for consistency fn t_err_raw(exp_lno: usize, exp_err: &str, doc: &str) -> TestResult where D: NetdocParseable + Debug, { let got = parse_netdoc::(doc, "").expect_err("unexpectedly parsed ok"); let got_err = got.problem.to_string(); assert_eq!( got.lno, exp_lno, "doc\n====\n{doc}====\n got={}\n exp={exp_err}", got_err ); assert_eq!( got_err, exp_err, "doc\n====\n{doc}====\n got={}\n exp={exp_err}", got_err ); Ok(()) } /// Test an error case with embedded error message /// /// `case` should be the input document, but exactly one line should /// contain `" # "`, with the expected error message as a "comment". /// /// `t_err` will check that that error is reported, at that line. fn t_err(mut case: &str) -> TestResult where D: NetdocParseable + Debug, { let mut exp = None; let mut doc = String::new(); let mut lno = 0; while let Some((l, r)) = case.split_once('\n') { lno += 1; case = r; if let Some((l, r)) = l.split_once(" # ") { assert!(exp.is_none()); exp = Some((lno, r.trim())); let l = l.trim_end(); doc += l; } else { doc += l; } doc += "\n"; } if !case.is_empty() { panic!("missing final newline"); } let (exp_lno, exp_err) = exp.expect("missing # error indication in test case"); println!("==== 8<- ====\n{doc}==== ->8 ===="); t_err_raw::(exp_lno, exp_err, &doc)?; Ok(()) } #[test] fn various_docs() -> TestResult<()> { let val = |s: &str| (s.to_owned(),); let sval = |s: &str| Some(val(s)); let sub1_minimal = Sub1 { flatten: Flat1 { flat_needed: val("FN"), flat_arg_needed: "FAN".into(), ..default() }, ..default() }; let subsub_minimal = SubSub { subsub_intro: "SSI".into(), ..default() }; let sub2_minimal = Sub2 { arg_needed: "AN".into(), subsub: subsub_minimal.clone(), ..default() }; t_ok( r#"top-intro needed N sub1-intro flat-needed FN flat-arg-needed FAN flat-with-needed normal "#, &[Top { needed: val("N"), sub1: sub1_minimal.clone(), ..default() }], )?; t_ok( r#"top-intro needed N sub1-intro flat-needed FN flat-arg-needed FAN flat-with-needed normal sub2-intro intro with-needed normal arg-needed AN subsub-intro SSI sub3-intro sub3-intro sub4-intro "#, &[Top { needed: val("N"), sub1: sub1_minimal.clone(), sub2: Some(sub2_minimal.clone()), sub3: vec![default(); 2], ..default() }], )?; t_ok( r#"top-intro needed N optional O several 1 several 2 defaulted D renamed R sub1-intro flat-several FS1 flat-needed FN flat-with-needed normal sub1-field A flat-with-several normal flat-with-several normal flat-optional FO flat-arg-needed FAN flat-with-optional normal flat-several FS2 flat-defaulted FD flat-arg-optional FAO flat-arg-several FAS1 ignored flat-arg-several FAS2 flat-arg-defaulted FAD sub2-intro intro with-several normal with-several normal with-several normal sub2-field B arg-needed AN arg-optional AO with-optional normal arg-defaulted AD arg-several A1 arg-several A2 with-needed normal subsub-intro SSI subsub-field BS sub3-intro sub3-field C1 sub3-intro sub3-field C2 sub4-intro sub4-field D "#, &[Top { needed: val("N"), optional: sval("O"), several: ["1", "2"].map(val).into(), defaulted: val("D"), t4_renamed: sval("R"), sub1: Sub1 { sub1_field: sval("A"), flatten: Flat1 { flat_needed: val("FN"), flat_optional: sval("FO"), flat_several: ["FS1", "FS2"].map(val).into(), flat_defaulted: sval("FD"), flat_arg_needed: "FAN".into(), flat_arg_several: ["FAS1", "FAS2"].map(Into::into).into(), flat_arg_optional: Some("FAO".into()), flat_arg_defaulted: "FAD".into(), flat_with_optional: Some(NeedsWith), flat_with_several: vec![NeedsWith; 2], ..Flat1::default() }, ..default() }, sub2: Some(Sub2 { sub2_field: sval("B"), arg_optional: Some("AO".into()), arg_defaulted: "AD".into(), arg_several: ["A1", "A2"].map(Into::into).into(), with_optional: Some(NeedsWith), with_several: vec![NeedsWith; 3], subsub: SubSub { subsub_field: sval("BS"), ..subsub_minimal.clone() }, ..sub2_minimal.clone() }), sub3: ["C1", "C2"] .map(|s| Sub3 { sub3_field: sval(s), ..default() }) .into(), sub4: Sub4 { sub4_field: sval("D"), ..default() }, ..default() }], )?; t_err_raw::(0, "empty document", r#""#)?; t_err::( r#"wrong-keyword # wrong document type "#, )?; t_err::( r#"top-intro sub4-intro # missing item needed "#, )?; t_err::( r#"top-intro sub1-intro flat-arg-needed arg flat-with-needed normal sub4-intro # missing item flat-needed "#, )?; t_err::( r#"top-intro sub1-intro flat-needed flat flat-with-needed normal sub4-intro # missing item flat-arg-needed "#, )?; t_err::( r#"top-intro sub1-intro flat-arg-needed FAN flat-with-needed normal flat-needed FN sub4-intro # missing item needed "#, )?; t_err::( r#"top-intro needed N sub3-intro sub4-intro # missing item sub1-intro "#, )?; t_err::( r#"top-intro needed N sub1-intro flat-needed FN1 flat-arg-needed FAN flat-with-needed normal sub1-intro # item repeated when not allowed flat-needed FN2 "#, )?; t_err::( r#"top-intro needed N sub2-intro # missing argument in needs with "#, )?; t_err::( r#"top-intro needed N sub2-intro wrong-value # invalid value for argument in needs with "#, )?; t_err::( r#"top-intro sub1-intro flat-needed FN flat-arg-needed arg sub4-intro # missing item flat-with-needed "#, )?; t_err::( r#"top-intro sub1-intro flat-needed FN flat-arg-needed arg flat-with-needed normal sub2-intro intro arg-needed AN flat-arg-needed arg sub3-intro # missing item with-needed "#, )?; Ok(()) } #[derive(Deftly, Debug, Default, Clone, Eq, PartialEq)] #[derive_deftly(NetdocParseable)] struct TopMinimal { test_item0: TestItem0, test_item: Option, test_item_rest: Option, test_item_rest_with: Option, } #[derive(Deftly, Debug, Default, Clone, Eq, PartialEq)] #[derive_deftly(ItemValueParseable)] #[deftly(netdoc(no_extra_args))] struct TestItem0 { #[deftly(netdoc(object(label = "UTF-8 STRING"), with = "string_data_object"))] object: Option, } #[derive(Deftly, Debug, Default, Clone, Eq, PartialEq)] #[derive_deftly(ItemValueParseable)] struct TestItem { needed: String, #[deftly(netdoc(with = "needs_with_arg::from_args"))] optional: Option, rest: Vec, #[deftly(netdoc(object))] object: TestObject, } #[derive(Deftly, Debug, Default, Clone, Eq, PartialEq)] #[derive_deftly(ItemValueParseable)] struct TestItemRest { optional: Option, #[deftly(netdoc(rest))] rest: String, } #[derive(Deftly, Debug, Default, Clone, Eq, PartialEq)] #[derive_deftly(ItemValueParseable)] struct TestItemRestWith { #[deftly(netdoc(rest, with = "needs_with_arg::from_rest"))] rest: NeedsWith, } #[derive(Debug, Default, Clone, Eq, PartialEq)] struct TestObject(String); /// Conversion module for `String` as Object with [`ItemValueParseable`] mod string_data_object { use super::*; /// Parse the data pub(super) fn try_from(data: Vec) -> Result { String::from_utf8(data) } } impl ItemObjectParseable for TestObject { fn check_label(label: &str) -> Result<(), ErrorProblem> { if label != "TEST OBJECT" { return Err(EP::ObjectIncorrectLabel); } Ok(()) } fn from_bytes(data: &[u8]) -> Result { Ok(TestObject( String::from_utf8(data.to_owned()).map_err(|_| EP::ObjectInvalidData)?, )) } } #[test] fn various_items() -> TestResult<()> { let test_item_minimal = TestItem { needed: "N".into(), object: TestObject("hello".into()), ..default() }; t_ok( r#"test-item0 "#, &[TopMinimal { ..default() }], )?; t_ok( r#"test-item0 test-item N -----BEGIN TEST OBJECT----- aGVsbG8= -----END TEST OBJECT----- "#, &[TopMinimal { test_item: Some(test_item_minimal.clone()), ..default() }], )?; t_ok( r#"test-item0 test-item N arg -----BEGIN TEST OBJECT----- aGVsbG8= -----END TEST OBJECT----- "#, &[TopMinimal { test_item: Some(TestItem { optional: Some(NeedsWith), ..test_item_minimal.clone() }), ..default() }], )?; t_ok( r#"test-item0 -----BEGIN UTF-8 STRING----- aGVsbG8= -----END UTF-8 STRING----- test-item N arg R1 R2 -----BEGIN TEST OBJECT----- aGVsbG8= -----END TEST OBJECT----- test-item-rest O and the rest test-item-rest-with rest of line "#, &[TopMinimal { test_item0: TestItem0 { object: Some("hello".into()), }, test_item: Some(TestItem { optional: Some(NeedsWith), rest: ["R1", "R2"].map(Into::into).into(), ..test_item_minimal.clone() }), test_item_rest: Some(TestItemRest { optional: Some("O".into()), rest: "and the rest".into(), }), test_item_rest_with: Some(TestItemRestWith { rest: NeedsWith }), }], )?; t_err::( r#"test-item0 wrong # too many arguments "#, )?; t_err::( r#"test-item0 # base64-encoded Object label is not as expected -----BEGIN WRONG LABEL----- aGVsbG8= -----END WRONG LABEL----- "#, )?; t_err::( r#"test-item0 # base64-encoded Object END label does not match BEGIN -----BEGIN UTF-8 STRING----- aGVsbG8= -----END WRONG LABEL----- "#, )?; t_err::( r#"test-item0 # base64-encoded Object has incorrectly formatted delimiter lines -----BEGIN UTF-8 STRING----- aGVsbG8= -----END UTF-8 STRING "#, )?; t_err::( r#"test-item0 # base64-encoded Object contains invalid base64 -----BEGIN UTF-8 STRING----- bad b64 ! -----END UTF-8 STRING----- "#, )?; t_err::( r#"test-item0 # base64-encoded Object contains invalid data -----BEGIN UTF-8 STRING----- hU6Qo2fW7+9PXkcrEyiB62ZDne/gwKPHXBo8lMeV8JCOfVBF5vT4BtKRLP+Jw66x -----END UTF-8 STRING----- "#, )?; Ok(()) }