polyxml 0.33.7__tar.gz → 0.33.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. {polyxml-0.33.7 → polyxml-0.33.8}/Cargo.lock +6 -6
  2. {polyxml-0.33.7 → polyxml-0.33.8}/Cargo.toml +1 -1
  3. {polyxml-0.33.7 → polyxml-0.33.8}/PKG-INFO +1 -1
  4. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/codegen/csharp/mod.rs +40 -10
  5. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/codegen/python/mod.rs +3 -0
  6. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/parser.rs +10 -3
  7. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/schema.rs +46 -0
  8. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/serializer.rs +6 -0
  9. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_csharp_codegen.rs +44 -0
  10. polyxml-0.33.8/crates/polyxml-core/tests/test_value_constraints.rs +30 -0
  11. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-python/src/lib.rs +9 -0
  12. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-python/tests/test_generated_models.py +36 -0
  13. {polyxml-0.33.7 → polyxml-0.33.8}/pyproject.toml +1 -1
  14. {polyxml-0.33.7 → polyxml-0.33.8}/README.md +0 -0
  15. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/Cargo.toml +0 -0
  16. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/README.md +0 -0
  17. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
  18. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs +0 -0
  19. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/benches/tag_dispatch.rs +0 -0
  20. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
  21. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/codegen/go/mod.rs +0 -0
  22. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/codegen/java/codec.rs +0 -0
  23. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/codegen/java/mod.rs +0 -0
  24. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/codegen/java/models.rs +0 -0
  25. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/codegen/mod.rs +0 -0
  26. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/codegen/python/aot.rs +0 -0
  27. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/codegen/rust/mod.rs +0 -0
  28. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/codegen/typescript/mod.rs +0 -0
  29. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/converters.rs +0 -0
  30. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/error.rs +0 -0
  31. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/ir/chunker.rs +0 -0
  32. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/ir/mod.rs +0 -0
  33. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
  34. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/json.rs +0 -0
  35. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/lib.rs +0 -0
  36. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/schema_parser/mod.rs +0 -0
  37. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/transcoder.rs +0 -0
  38. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/src/value.rs +0 -0
  39. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/fixtures/lexical_union.xsd +0 -0
  40. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/fixtures/mixed_content.xsd +0 -0
  41. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/fixtures/ts_mixed_content_inheritance.xsd +0 -0
  42. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_adversarial_identifiers.rs +0 -0
  43. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_any_attribute.rs +0 -0
  44. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_any_element.rs +0 -0
  45. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_conformance.rs +0 -0
  46. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_core.rs +0 -0
  47. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
  48. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_go_codegen.rs +0 -0
  49. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_java_codegen.rs +0 -0
  50. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_json.rs +0 -0
  51. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_lexical_union_codegen.rs +0 -0
  52. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_mixed_content.rs +0 -0
  53. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_pattern_codegen.rs +0 -0
  54. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_python_codegen.rs +0 -0
  55. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_qualified_attributes.rs +0 -0
  56. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
  57. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_rust_codegen.rs +0 -0
  58. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_schema_audit.rs +0 -0
  59. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_schema_ir.rs +0 -0
  60. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_security.rs +0 -0
  61. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
  62. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
  63. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_unbounded_choice.rs +0 -0
  64. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-core/tests/test_xsi_type.rs +0 -0
  65. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-python/Cargo.toml +0 -0
  66. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-python/README.md +0 -0
  67. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-python/benches/aot_vs_dataclass.py +0 -0
  68. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-python/tests/test_conformance.py +0 -0
  69. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-python/tests/test_json.py +0 -0
  70. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-python/tests/test_mixed_content.py +0 -0
  71. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-python/tests/test_polyxml.py +0 -0
  72. {polyxml-0.33.7 → polyxml-0.33.8}/crates/polyxml-python/tests/test_xsi_type.py +0 -0
  73. {polyxml-0.33.7 → polyxml-0.33.8}/python/polyxml/AGENT_GUIDE.md +0 -0
  74. {polyxml-0.33.7 → polyxml-0.33.8}/python/polyxml/__init__.py +0 -0
  75. {polyxml-0.33.7 → polyxml-0.33.8}/python/polyxml/compat/__init__.py +0 -0
  76. {polyxml-0.33.7 → polyxml-0.33.8}/python/polyxml/compat/xsdata.py +0 -0
  77. {polyxml-0.33.7 → polyxml-0.33.8}/python/polyxml/py.typed +0 -0
@@ -608,7 +608,7 @@ dependencies = [
608
608
 
609
609
  [[package]]
610
610
  name = "polyxml"
611
- version = "0.33.7"
611
+ version = "0.33.8"
612
612
  dependencies = [
613
613
  "criterion",
614
614
  "heck",
@@ -629,14 +629,14 @@ dependencies = [
629
629
 
630
630
  [[package]]
631
631
  name = "polyxml-c"
632
- version = "0.33.7"
632
+ version = "0.33.8"
633
633
  dependencies = [
634
634
  "polyxml",
635
635
  ]
636
636
 
637
637
  [[package]]
638
638
  name = "polyxml-cli"
639
- version = "0.33.7"
639
+ version = "0.33.8"
640
640
  dependencies = [
641
641
  "clap",
642
642
  "glob",
@@ -650,7 +650,7 @@ dependencies = [
650
650
 
651
651
  [[package]]
652
652
  name = "polyxml-js"
653
- version = "0.33.7"
653
+ version = "0.33.8"
654
654
  dependencies = [
655
655
  "napi",
656
656
  "napi-build",
@@ -660,7 +660,7 @@ dependencies = [
660
660
 
661
661
  [[package]]
662
662
  name = "polyxml-python"
663
- version = "0.33.7"
663
+ version = "0.33.8"
664
664
  dependencies = [
665
665
  "lexical-core",
666
666
  "polyxml",
@@ -670,7 +670,7 @@ dependencies = [
670
670
 
671
671
  [[package]]
672
672
  name = "polyxml-wasm"
673
- version = "0.33.7"
673
+ version = "0.33.8"
674
674
  dependencies = [
675
675
  "polyxml",
676
676
  "quick-xml",
@@ -3,7 +3,7 @@ resolver = "2"
3
3
  members = ["crates/polyxml-core", "crates/polyxml-python"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.33.7"
6
+ version = "0.33.8"
7
7
  edition = "2021"
8
8
  rust-version = "1.80"
9
9
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polyxml
3
- Version: 0.33.7
3
+ Version: 0.33.8
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -877,11 +877,11 @@ impl CSharpCodegen {
877
877
  ir: &SchemaIR,
878
878
  indent: &str,
879
879
  ) -> bool {
880
- let Some(default) = field.default_value.as_ref() else {
880
+ let Some(default) = field.default_value.as_ref().or(field.fixed_value.as_ref()) else {
881
881
  return false;
882
882
  };
883
883
  if !self.options.emit_xml_attributes
884
- || field.kind != FieldKind::Element
884
+ || !matches!(field.kind, FieldKind::Element | FieldKind::Attribute)
885
885
  || field.cardinality.is_list()
886
886
  || matches!(field.type_ref, TypeRef::Named(ref q) if matches!(ir.types.get(q), Some(TypeDef::Struct(_) | TypeDef::Union(_))))
887
887
  {
@@ -894,19 +894,49 @@ impl CSharpCodegen {
894
894
  .as_ref()
895
895
  .map(|ns| format!(", Namespace = {ns:?}"))
896
896
  .unwrap_or_default();
897
+ let parse = |text: &str| {
898
+ format!("({base})new XmlSerializer(typeof({base}), new XmlRootAttribute(\"value\")).Deserialize(new System.IO.StringReader(\"<value>\" + System.Security.SecurityElement.Escape({text}) + \"</value>\"))!")
899
+ };
900
+ if let Some(fixed) = &field.fixed_value {
901
+ let expected = parse(&format!("{fixed:?}"));
902
+ let initial = if field.cardinality.is_optional() && field.kind == FieldKind::Element {
903
+ "default!".to_string()
904
+ } else {
905
+ expected.clone()
906
+ };
907
+ writeln!(out, "{indent} private {ty} _{name} = {initial};
908
+ {indent} [XmlIgnore] public {ty} {name} {{ get => _{name}; set {{ if (value is not null && !object.Equals(value, {expected})) throw new System.ComponentModel.DataAnnotations.ValidationException(\"Fixed value constraint violated for {name}\"); _{name} = value; }} }}").unwrap();
909
+ } else {
910
+ let initial = if field.kind == FieldKind::Attribute {
911
+ parse(&format!("{default:?}"))
912
+ } else {
913
+ "default!".to_string()
914
+ };
915
+ writeln!(
916
+ out,
917
+ "{indent} [XmlIgnore] public {ty} {name} {{ get; set; }} = {initial};"
918
+ )
919
+ .unwrap();
920
+ }
921
+ let kind = if field.kind == FieldKind::Attribute {
922
+ "XmlAttribute"
923
+ } else {
924
+ "XmlElement"
925
+ };
897
926
  writeln!(
898
927
  out,
899
- "{indent} [XmlIgnore] public {ty} {name} {{ get; set; }} = default!;"
900
- )
901
- .unwrap();
902
- writeln!(
903
- out,
904
- "{indent} [XmlElement({:?}{namespace})] public string? {name}Xml {{",
928
+ "{indent} [{kind}({:?}{namespace})] public string? {name}Xml {{",
905
929
  field.xml_name
906
930
  )
907
931
  .unwrap();
908
932
  writeln!(out, "{indent} get {{ if ({name} is null) return null; var writer = new System.IO.StringWriter(System.Globalization.CultureInfo.InvariantCulture); new XmlSerializer(typeof({base}), new XmlRootAttribute(\"value\")).Serialize(writer, {name}); return System.Xml.Linq.XDocument.Parse(writer.ToString()).Root!.Value; }}").unwrap();
909
- writeln!(out, "{indent} set {{ if (value is null) {{ {name} = default!; return; }} var text = value.Length == 0 ? {default:?} : value; {name} = ({base})new XmlSerializer(typeof({base}), new XmlRootAttribute(\"value\")).Deserialize(new System.IO.StringReader(\"<value>\" + System.Security.SecurityElement.Escape(text) + \"</value>\"))!; }}
933
+ let text = if field.kind == FieldKind::Attribute {
934
+ "value".into()
935
+ } else {
936
+ format!("value.Length == 0 ? {default:?} : value")
937
+ };
938
+ let parsed = parse("text");
939
+ writeln!(out, "{indent} set {{ if (value is null) {{ {name} = default!; return; }} var text = {text}; {name} = {parsed}; }}
910
940
  {indent} }}").unwrap();
911
941
  if field.cardinality.is_optional() || field.nillable {
912
942
  writeln!(
@@ -1079,7 +1109,7 @@ impl CSharpCodegen {
1079
1109
  }
1080
1110
 
1081
1111
  let has_lexical_union = self.options.emit_xml_attributes && s.fields.iter().any(|f| {
1082
- f.default_value.is_some() || matches!(&f.type_ref, TypeRef::Named(q) if matches!(ir.types.get(q), Some(TypeDef::Union(u)) if u.is_lexical()))
1112
+ f.default_value.is_some() || f.fixed_value.is_some() || matches!(&f.type_ref, TypeRef::Named(q) if matches!(ir.types.get(q), Some(TypeDef::Union(u)) if u.is_lexical()))
1083
1113
  });
1084
1114
 
1085
1115
  if self.options.use_records && has_lexical_union {
@@ -993,6 +993,9 @@ impl PythonCodegen {
993
993
  parts.push(format!("\"namespace\": \"{}\"", ns));
994
994
  }
995
995
 
996
+ if let Some(ref fixed) = field.fixed_value {
997
+ parts.push(format!("\"fixed\": {fixed:?}"));
998
+ }
996
999
  if let Some(ref default) = field.default_value {
997
1000
  parts.push(format!("\"default\": {default:?}"));
998
1001
  }
@@ -73,9 +73,10 @@ impl StackFrame {
73
73
  fn finish(mut self) -> Result<PolyValue> {
74
74
  for (index, field) in self.schema.fields.iter().enumerate() {
75
75
  if field.kind == FieldKind::Attribute && self.values[index].is_none() {
76
- if let (Some(default), ValueType::Scalar(scalar)) =
77
- (&field.default_value, &field.val_type)
78
- {
76
+ if let (Some(default), ValueType::Scalar(scalar)) = (
77
+ &field.default_value.as_ref().or(field.fixed_value.as_ref()),
78
+ &field.val_type,
79
+ ) {
79
80
  self.values[index] = Some(ValueConverter::parse_scalar(
80
81
  scalar,
81
82
  default.as_bytes(),
@@ -113,6 +114,11 @@ impl StackFrame {
113
114
  }
114
115
  }
115
116
 
117
+ for (index, field) in self.schema.fields.iter().enumerate() {
118
+ if let Some(value) = self.values[index].as_ref() {
119
+ crate::schema::validate_fixed(field, value)?;
120
+ }
121
+ }
116
122
  Ok(PolyValue::Record {
117
123
  schema: self.schema,
118
124
  values: self.values.into_vec().into_boxed_slice(),
@@ -129,6 +135,7 @@ fn parse_field_scalar(
129
135
  field
130
136
  .default_value
131
137
  .as_ref()
138
+ .or(field.fixed_value.as_ref())
132
139
  .map_or(text, |value| value.as_bytes())
133
140
  } else {
134
141
  text
@@ -66,6 +66,7 @@ pub struct FieldSchema {
66
66
  pub val_type: ValueType,
67
67
  pub required: bool,
68
68
  pub default_value: Option<String>,
69
+ pub fixed_value: Option<String>,
69
70
  }
70
71
 
71
72
  impl FieldSchema {
@@ -83,6 +84,7 @@ impl FieldSchema {
83
84
  val_type,
84
85
  required: false,
85
86
  default_value: None,
87
+ fixed_value: None,
86
88
  }
87
89
  }
88
90
 
@@ -354,6 +356,7 @@ impl ModelSchema {
354
356
 
355
357
  let mut field_schema = FieldSchema::new(&f.name, f.xml_name.as_bytes(), kind, val_type);
356
358
  field_schema.default_value = f.default_value.clone();
359
+ field_schema.fixed_value = f.fixed_value.clone();
357
360
  if let Some(ref ns) = f.namespace {
358
361
  field_schema = field_schema.namespace(ns);
359
362
  }
@@ -633,3 +636,46 @@ impl ModelSchemaBuilder {
633
636
  })
634
637
  }
635
638
  }
639
+
640
+ /// Validate fixed constraints in value space rather than comparing lexical spellings.
641
+ pub(crate) fn validate_fixed(
642
+ field: &FieldSchema,
643
+ value: &crate::value::PolyValue,
644
+ ) -> crate::error::Result<()> {
645
+ use crate::value::PolyValue;
646
+ let Some(fixed) = &field.fixed_value else {
647
+ return Ok(());
648
+ };
649
+ fn check(
650
+ ty: &ValueType,
651
+ value: &PolyValue,
652
+ fixed: &str,
653
+ name: &str,
654
+ ) -> crate::error::Result<()> {
655
+ if matches!(value, PolyValue::Null) {
656
+ return Ok(());
657
+ }
658
+ match (ty, value) {
659
+ (ValueType::Scalar(scalar), value) => {
660
+ let expected = crate::converters::ValueConverter::parse_scalar(
661
+ scalar,
662
+ fixed.as_bytes(),
663
+ name,
664
+ )?;
665
+ if *value != expected {
666
+ return Err(crate::error::PolyXmlError::SchemaError(format!(
667
+ "Fixed value constraint violated for {name}"
668
+ )));
669
+ }
670
+ }
671
+ (ValueType::List(inner), PolyValue::List(items)) => {
672
+ for item in items {
673
+ check(inner, item, fixed, name)?;
674
+ }
675
+ }
676
+ _ => {}
677
+ }
678
+ Ok(())
679
+ }
680
+ check(&field.val_type, value, fixed, &field.name)
681
+ }
@@ -296,6 +296,12 @@ impl XmlSerializer {
296
296
  }
297
297
  };
298
298
 
299
+ for (index, field) in schema.fields.iter().enumerate() {
300
+ if let Some(value) = get_field(index, &field.name) {
301
+ crate::schema::validate_fixed(field, value)?;
302
+ }
303
+ }
304
+
299
305
  let local_tag = std::str::from_utf8(tag_name)?;
300
306
  let qualified_tag = if let Some(ctx) = ns_ctx {
301
307
  ctx.qualify_element(local_tag, element_ns)
@@ -1278,3 +1278,47 @@ if(present&&!writer.ToString().Contains("<Flag>false</Flag>"))throw new Exceptio
1278
1278
  );
1279
1279
  }
1280
1280
  }
1281
+
1282
+ #[test]
1283
+ fn fixed_values_are_enforced_on_read_and_write() {
1284
+ let ir = polyxml::schema_parser::XsdParser::new()
1285
+ .parse_str(include_str!("../../../research/fixtures/fixed_values.xsd"))
1286
+ .unwrap();
1287
+ for use_records in [true, false] {
1288
+ let temp = tempdir().unwrap();
1289
+ fs::write(temp.path().join("App.csproj"),r#"<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net8.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable></PropertyGroup></Project>"#).unwrap();
1290
+ fs::write(
1291
+ temp.path().join("Models.cs"),
1292
+ CSharpCodegen::new(CSharpOptions {
1293
+ namespace: "Models".into(),
1294
+ use_records,
1295
+ ..Default::default()
1296
+ })
1297
+ .generate_module(&ir),
1298
+ )
1299
+ .unwrap();
1300
+ fs::write(temp.path().join("Program.cs"),r#"using Models;using System.Xml.Serialization;using System.ComponentModel.DataAnnotations;
1301
+ var serializer=new XmlSerializer(typeof(Root));
1302
+ foreach(var xml in new[]{"<Root><Code>0013</Code><Flag>1</Flag></Root>","<Root><Code/><Flag/></Root>"}){
1303
+ var root=(Root)serializer.Deserialize(new StringReader(xml))!;
1304
+ if(root.Code!="0013"||root.Flag!=true||root.Mode!="auto")throw new Exception("fixed defaults missing");
1305
+ var writer=new StringWriter();serializer.Serialize(writer,root);
1306
+ try{root.Code="wrong";throw new Exception("mutation accepted");}catch(ValidationException){}
1307
+ }
1308
+ foreach(var xml in new[]{"<Root><Code>wrong</Code></Root>","<Root mode='wrong'><Code>0013</Code></Root>","<Root><Code>0013</Code><Flag>false</Flag></Root>"}){
1309
+ try{serializer.Deserialize(new StringReader(xml));throw new Exception("invalid input accepted");}catch(InvalidOperationException){}
1310
+ }"#).unwrap();
1311
+ let _lock = DOTNET_LOCK.lock().unwrap();
1312
+ let result = dotnet_command()
1313
+ .arg("run")
1314
+ .current_dir(temp.path())
1315
+ .output()
1316
+ .unwrap();
1317
+ assert!(
1318
+ result.status.success(),
1319
+ "{}{}",
1320
+ String::from_utf8_lossy(&result.stdout),
1321
+ String::from_utf8_lossy(&result.stderr)
1322
+ );
1323
+ }
1324
+ }
@@ -0,0 +1,30 @@
1
+ use polyxml::schema::ModelSchema;
2
+ use polyxml::schema_parser::XsdParser;
3
+ use polyxml::{deserialize, serialize};
4
+ use std::sync::Arc;
5
+
6
+ #[test]
7
+ fn fixed_constraints_cover_input_and_output_in_value_space() {
8
+ let ir = XsdParser::new()
9
+ .parse_str(include_str!("../../../research/fixtures/fixed_values.xsd"))
10
+ .unwrap();
11
+ let schema = ModelSchema::from_ir(&ir, Some("Root")).unwrap();
12
+ for xml in [
13
+ "<Root><Code>0013</Code><Flag>1</Flag></Root>",
14
+ "<Root><Code/><Flag/></Root>",
15
+ ] {
16
+ let value = deserialize(xml.as_bytes(), Arc::clone(&schema)).unwrap();
17
+ let output = serialize("Root", &value, &schema, None).unwrap();
18
+ deserialize(&output, Arc::clone(&schema)).unwrap();
19
+ }
20
+ for xml in [
21
+ "<Root><Code>wrong</Code></Root>",
22
+ "<Root mode='wrong'><Code>0013</Code></Root>",
23
+ "<Root><Code>0013</Code><Flag>false</Flag></Root>",
24
+ ] {
25
+ assert!(deserialize(xml.as_bytes(), Arc::clone(&schema))
26
+ .unwrap_err()
27
+ .to_string()
28
+ .contains("Fixed value constraint"));
29
+ }
30
+ }
@@ -417,6 +417,10 @@ fn extract_schema_from_class<'py>(
417
417
  });
418
418
 
419
419
  let mut field_schema = FieldSchema::new(py_name, xml_name.as_bytes(), kind, val_type);
420
+ field_schema.fixed_value = meta
421
+ .as_ref()
422
+ .and_then(|m| m.get_item("fixed").ok())
423
+ .and_then(|v| v.extract::<String>().ok());
420
424
  field_schema.default_value = meta
421
425
  .as_ref()
422
426
  .and_then(|m| m.get_item("default").ok())
@@ -498,6 +502,11 @@ fn extract_schema_from_class<'py>(
498
502
  });
499
503
 
500
504
  let mut field_schema = FieldSchema::new(py_name, xml_name.as_bytes(), kind, val_type);
505
+ field_schema.fixed_value = field_obj
506
+ .getattr("metadata")
507
+ .ok()
508
+ .and_then(|m| m.get_item("fixed").ok())
509
+ .and_then(|v| v.extract::<String>().ok());
501
510
  field_schema.default_value = field_obj
502
511
  .getattr("metadata")
503
512
  .ok()
@@ -598,3 +598,39 @@ def test_element_defaults_preserve_absence(tmp_path, backend):
598
598
  encoded = present.to_xml()
599
599
  assert ET.fromstring(encoded).findtext("Flag") == "false"
600
600
  assert module.Root.from_xml(encoded).count == 42
601
+
602
+
603
+ @pytest.mark.parametrize("backend", ["dataclass", "pydantic"])
604
+ def test_fixed_values_validate_input_and_output(tmp_path, backend):
605
+ schema = pathlib.Path(__file__).resolve().parents[3] / "research/fixtures/fixed_values.xsd"
606
+ subprocess.run(
607
+ [
608
+ str(_get_polyxml_bin()),
609
+ "generate",
610
+ str(schema),
611
+ "--lang",
612
+ "python",
613
+ "--backend",
614
+ backend,
615
+ "--out",
616
+ str(tmp_path),
617
+ ],
618
+ check=True,
619
+ capture_output=True,
620
+ )
621
+ module = _load_module_from_file(f"fixed_{backend}", tmp_path / "fixed_values.py")
622
+ for document in ["<Root><Code>0013</Code><Flag>1</Flag></Root>", "<Root><Code/><Flag/></Root>"]:
623
+ root = module.Root.from_xml(document)
624
+ assert (root.code, root.flag, root.mode) == ("0013", True, "auto")
625
+ assert module.Root.from_xml(root.to_xml()).code == "0013"
626
+ for document in [
627
+ "<Root><Code>wrong</Code></Root>",
628
+ "<Root mode='wrong'><Code>0013</Code></Root>",
629
+ "<Root><Code>0013</Code><Flag>false</Flag></Root>",
630
+ ]:
631
+ with pytest.raises(ValueError, match="Fixed value constraint"):
632
+ module.Root.from_xml(document)
633
+ root = module.Root.from_xml("<Root><Code>0013</Code></Root>")
634
+ root.code = "wrong"
635
+ with pytest.raises(ValueError, match="Fixed value constraint"):
636
+ root.to_xml()
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "polyxml"
7
- version = "0.33.7"
7
+ version = "0.33.8"
8
8
  description = "High-performance, polyglot XML data-binding engine built in Rust"
9
9
  authors = [
10
10
  { name = "Bailey Nguyen", email = "bailey.tan.nguyen@gmail.com" }
File without changes