use std::collections::BTreeMap; use maplit::btreemap; use schema_analysis::{helpers, Field, FieldStatus, InferredSchema, Schema}; mod shared; use shared::FormatTests; struct Xml; test_format!(Xml); impl FormatTests<String> for Xml { fn convert_to_inferred_schema(value: String) -> InferredSchema { let mut processed_schema: InferredSchema = quick_xml::de::from_str(&value).unwrap(); helpers::xml::cleanup_xml_schema(&mut processed_schema.schema); processed_schema } // Xml doesn't allow top-level primitives fn null() -> Option<String> { None } fn boolean() -> Option<String> { None } fn integer() -> Option<String> { None } fn float() -> Option<String> { None } fn string() -> Option<String> { None } // Xml doesn't allow top-level arrays (quick_xml ignores later elements anyway) fn empty_sequence() -> Option<String> { None } fn string_sequence() -> Option<String> { None } fn integer_sequence() -> Option<String> { None } fn mixed_sequence() -> Option<String> { None } fn optional_mixed_sequence() -> Option<String> { None } // Note: root name is discarded fn empty_map_struct() -> Option<String> { Some(r#"<wrapper></wrapper>"#.into()) } fn map_struct_single() -> Option<String> { Some(r#"<wrapper><hello>1</hello></wrapper>"#.into()) } fn test_map_struct_single() { // Xml doesn't have integer values let fields: BTreeMap<String, Field> = { let mut hello_field = Field { status: FieldStatus::default(), schema: Some(Schema::String(Default::default())), }; hello_field.status.may_be_normal = true; btreemap! { "hello".into() => hello_field } }; Self::_compare_map_struct(Self::map_struct_single(), fields); } fn map_struct_double() -> Option<String> { Some(r#"<wrapper><hello>1</hello><world>!</world></wrapper>"#.into()) } fn test_map_struct_double() { // Xml doesn't have integer values let fields: BTreeMap<String, Field> = { let mut hello_field = Field { status: FieldStatus::default(), schema: Some(Schema::String(Default::default())), }; hello_field.status.may_be_normal = true; let mut world_field = Field { status: FieldStatus::default(), schema: Some(Schema::String(Default::default())), }; world_field.status.may_be_normal = true; btreemap! { "hello".into() => hello_field, "world".into() => world_field, } }; Self::_compare_map_struct(Self::map_struct_double(), fields); } // Xml only has strings, so there is no meaning to 'mixed'. fn sequence_map_struct_mixed() -> Option<String> { None } fn sequence_map_struct_optional_or_missing() -> Option<String> { Some( " <wrapper> <element> <hello>1</hello> <possibly_null></possibly_null> <possibly_missing>1.1</possibly_missing> <null_or_missing></null_or_missing> </element> <element> <hello>1</hello> <possibly_null>!</possibly_null> </element> </wrapper>" .into(), ) } fn test_sequence_map_struct_optional_or_missing() { // NOTE: in xml sequences are detected as the same key appearing multiple times, so // the inner schema is correctly computed over all instances but it is not detected // as a sequence. // The cleanup_xml_schema fixes this by modifying the fields marked as duplicates // by the parser. let inner_fields = { let mut hello_field = Field::with_schema(Schema::String(Default::default())); hello_field.status.may_be_normal = true; let mut possibly_null_field = Field::with_schema(Schema::String(Default::default())); possibly_null_field.status.may_be_normal = true; // possibly_null_field.status.may_be_null = true; // No built-in null let mut possibly_missing_field = Field::with_schema(Schema::String(Default::default())); possibly_missing_field.status.may_be_normal = true; possibly_missing_field.status.may_be_missing = true; let mut null_or_missing_field = Field::default(); // null_or_missing_field.status.may_be_null = true; // No built-in null null_or_missing_field.status.may_be_normal = true; // No built-in null null_or_missing_field.status.may_be_missing = true; btreemap! { "hello".into() => hello_field, "possibly_null".into() => possibly_null_field, "possibly_missing".into() => possibly_missing_field, "null_or_missing".into() => null_or_missing_field, } }; let mut element_field = Field::with_schema(Schema::Struct { fields: inner_fields, context: Default::default(), }); element_field.status.may_be_normal = true; element_field.status.allow_duplicates(true); // Note: xml files always have a root struct, so we need to wrap it for the // comparison to make sense. let mut sequence_field = Field::with_schema(Schema::Sequence { field: Box::new(element_field), context: Default::default(), }); sequence_field.status.may_be_normal = true; Self::_compare_map_struct( Self::sequence_map_struct_optional_or_missing(), btreemap! { "element".into() => sequence_field, }, ); } fn map_struct_mixed_sequence() -> Option<String> { Some( " <wrapper> <hello>1</hello> <world>!</world> <sequence>one</sequence><sequence>two</sequence><sequence>three</sequence> </wrapper>" .into(), ) } fn test_map_struct_mixed_sequence() { let fields: BTreeMap<String, Field> = { let mut hello_field = Field::with_schema(Schema::String(Default::default())); // hello_field.status.may_be_normal = true; let mut world_field = Field::with_schema(Schema::String(Default::default())); world_field.status.may_be_normal = true; let mut sequence_field = { let mut sequence_element_field = Field::with_schema(Schema::String(Default::default())); sequence_element_field.status.may_be_normal = true; sequence_element_field.status.allow_duplicates(true); // Field::with_schema(Schema::Sequence { field: Box::new(sequence_element_field), context: Default::default(), }) }; sequence_field.status.may_be_normal = true; btreemap! { "hello".into() => hello_field, "world".into() => world_field, "sequence".into() => sequence_field, } }; Self::_compare_map_struct(Self::map_struct_mixed_sequence(), fields); } // No built-in null makes this equivalent to the above. fn map_struct_mixed_sequence_optional() -> Option<String> { None } }