diff --git a/.agents/skills/polyxml-codegen-workflow/SKILL.md b/.agents/skills/polyxml-codegen-workflow/SKILL.md index f1e2cd7a..8572db07 100644 --- a/.agents/skills/polyxml-codegen-workflow/SKILL.md +++ b/.agents/skills/polyxml-codegen-workflow/SKILL.md @@ -128,12 +128,55 @@ If external compilers are installed on the development machine, run the E2E veri 5. **Never Derive Type Identifiers from `qname.local` Directly**: all named-type identifiers must flow through the language's `type_ident` helper (see section 7) so cross-namespace collisions stay disambiguated. +6. **`xsd:extension` Base-Field Flattening (Rust + Java records)**: targets + without inheritance must inline the base chain via the shared + `codegen/mod.rs::flatten_fields(s, ir)` (root first, cycle-cut on revisit, + most-derived declaration of a schema name shadows inherited duplicates so + the simpleContent `value` field stays singular). For Rust it is required at + EVERY field-iteration site: `compute_types_with_lifetime`, `emit_struct`, + the `has_patterns` probe, and the decode `field_metas` build — + `encode_xml`/`validate_patterns` follow `field_metas` automatically. + Missing the lifetime site emits `Cow<'a, str>` in a struct without `<'a>` + (generated code fails to compile); missing codec sites silently drops + inherited data. Java's `model_fields` returns the flattened list for every + mode — records declare it as components, classes re-slice the own-fields + tail because they `extends` instead. Backends with real inheritance (TS + `extends`, Go embed, C++/C# `: Base`, Python `class Derived(Base)`) must + NOT flatten — Python leans on `__dataclass_fields__` (see §11). +7. **simpleContent `FieldKind::Text` codecs (fixed 2026-09-22)**: generated + Rust codecs now consume `FieldKind::Text` — `emit_text_content_parse` + reads the element content via `read_element_text` and runs it through the + same scalar/facet path as elements (`var_value = Some(...)`; patterns from + the extension base are enforced via `validate_patterns`), and + `emit_text_content_serialize` writes the value between Start/End. Only + scalar-backed Text is emitted: a struct-typed value (`value: Measurement`) + stays a documented cross-backend gap pending issue #51 item 4 IR modeling. + The module allow-header includes `unused_assignments` because the text path + assigns its slot unconditionally. The companion Java gap — plain record + mode dropping inherited fields — was fixed the same day: records cannot + `extends`, so `model_fields` now always inlines `flatten_fields`. + Regression-locked by `test_rust_simple_content_text_codec` plus runtime + round-trips (numeric, patterned, string, bad-number rejection). +8. **Text events arrive split — readers must accumulate**: quick-xml emits + `a & b` as `Text("a ")` + `GeneralRef("amp")` + `Text(" b")` and CDATA + as its own event, so `read_element_text` (both `zero_copy` variants) + appends every segment and resolves refs exactly like + `parser.rs::append_general_ref` (char refs → `resolve_char_ref()`, + predefined → `escape::resolve_xml_entity()`, otherwise the raw name — + lenient, never an error). Never overwrite the accumulator per event (the + pre-fix last-wins helper silently dropped data) and never pair split-text + assembly with `trim_text(true)` — per-segment trimming eats spaces adjacent + to refs (`x & y` → `x&y`); trim once on the assembled buffer (see + `transcoder.rs`, which now also unescapes attribute values and resolves + general refs instead of dropping them via `_ => {}`). Regression-locked by + `test_rust_read_element_text_accumulates_refs_and_cdata` and + `test_schemaless_preserves_refs_cdata_and_attr_entities`. ## 6. Java/C# Model Styles and Direct Java Codecs - `--style record|pojo|class` is shared by Java and C#. Records remain the default; - `pojo`/`class` select mutable models. Java `--builder` and - `--codec annotation|direct` must also be forwarded in both manifest forms. + `pojo`/`class` select mutable models. Java's `--feature builder,direct-codec` + must also be forwarded as `features = [...]` in both manifest forms. - Java mutable models/builders live in `java/models.rs`; StAX companions live in `java/codec.rs`. Inheritance must share field-name allocation between accessors, builders, and codecs. A derived builder extends its base builder and overrides @@ -159,7 +202,7 @@ If external compilers are installed on the development machine, run the E2E veri all 7 targets from whichever local `target/{debug,release}/polyxml` is newer; a clean `git status` there proves byte-for-byte output parity after codegen changes. The same schema is the best large-schema smoke for - `--style pojo --builder --codec direct`: `javac` the output and round-trip + `--style pojo --feature builder,direct-codec`: `javac` the output and round-trip `data/pacs_008_customer_credit_transfer.xml` through the generated root codec. - `benchmarks/java -Ppanama` needs JDK 22+, but the host default can be JDK 21. Set `JAVA_HOME`/`PATH` to a downloaded JDK (Temurin 25 worked) and run @@ -216,12 +259,166 @@ derived types inherit their base's patterns at parse time): - `emit_simple_type` appends `AfterValidator(_polyxml_patterns(r"p1", r"p2"))` to the `Annotated[...]` alias (single-pattern types keep the plain - `Field(pattern=...)` kwarg, byte-identical to before). + `Field(pattern=...)` kwarg, anchored for full-value matching). - `needs_pattern_validator(ir)` gates emission of the module-level `_polyxml_patterns` factory helper, the `import re` line, and the `AfterValidator,` prefix on the pydantic import — keep these three in sync. -- Semantics: unanchored `regex.search` per pattern, all must match (AND), - matching XSD pattern search semantics. +- Semantics: each pattern must match the entire lexical value (`fullmatch`); + patterns inherited across derivation steps all apply (AND). - Field-level facets (`FieldDef.facets`) are never populated by the parser today, so `emit_struct`'s field path needs no `AfterValidator` wiring; if that ever changes, mirror the type-level handling there. + +## 9. Unified CLI Options (issue #50) + +- `polyxml-cli/src/options.rs` normalizes and validates backend/style/features + for direct generation and both manifest forms. Validate all targets before + schema parsing, dry-run success, or output creation. Core `from_str_loose` + parsers can still return `None`; the CLI must reject invalid values before + emitter defaults are applied. +- New opt-ins belong in repeatable `--feature` and manifest `features` arrays. + The legacy hidden flags (`--zod`, `--source-gen`, `--record-kind`, `--rkyv`, + `--builder`, `--codec`) and their manifest keys were deleted outright: clap + rejects the flags as unexpected arguments, `#[serde(deny_unknown_fields)]` + rejects the keys, and the deprecation-warning loop no longer exists. Keep + CLI and manifest parity. +- `--zero-copy` (and manifest `zero_copy`) stays a first-class bool because + `--feature` cannot express `false`; it alone selects owned Rust output, and + `--zero-copy=false --feature zero-copy` is still rejected as a contradiction. +- Defaults remain unchanged, including C# record classes, Rust zero-copy, and + Python slots/kw-only. Expose only implemented styles/features. The issue's + future examples (aot, Python plain class, C# mutable struct) are not yet + generator capabilities. +- Rust features today: `zero-copy`, `rkyv`, `phf`. `--feature phf` (issue #49) + emits a per-struct `__{Struct}ElementId` enum plus a + `__{STRUCT}_ELEMENT_DISPATCH: ::phf::Map<&'static str, ...>` static built with + `phf_codegen`, and routes both `Event::Start` and `Event::Empty` child arms + through `.get(e.local_name().as_ref())`; union branch tags all map to one + variant. Default (feature off) output must stay byte-identical — lock both + directions in the codegen test (`test_rust_phf_dispatch_emission`). Attr, + union, and enum-value `match` sites are intentionally left as `match`. + Consuming crates need `phf = "0.14"`. Benchmarks/docs live in + `benches/tag_dispatch.rs` (regenerate fixtures via + `scripts/gen_tag_dispatch_fixtures.py`) and + `docs/benchmarks/rust-phf-dispatch.md`; hardware counters on hosts without + the `perf` binary go through `scripts/perf_stat.sh` (`perf_event_open`). +- **Cap heavy builds/benches with `scripts/memcap.sh`.** Benchmark entry + points (`benchmarks/run_all.sh`, `benchmarks/cli/benchmark.sh`, + `scripts/perf_stat.sh`) already re-exec through it; ad-hoc + `cargo bench`/`--release` builds of large generated crates should be + wrapped too. It caps the tree at `POLYXML_MEMCAP_PCT` (default 60%) of + available RAM in a systemd scope (`MemorySwapMax=0`, `ulimit -v` + fallback): an over-limit build gets OOM-killed inside its cgroup + (exit 137) instead of freezing the host — uncapped 1500-element builds + froze a 7.7-GiB WSL box repeatedly before this guard existed. Wrap + measure/compile-size scripts per build step (own scope each) so one + OOM kill doesn't abort the rest; `POLYXML_MEMCAP_LEVEL` (nesting depth) + is set automatically so wrapped scripts can't re-exec in a loop, and + `POLYXML_MEMCAP_DISABLE=1` opts out. Known datum: generated consumer + crates at **600 and 1500 elements need >=3.2 GiB for a single `rustc`** + (both the `match` and `phf` variants — it scales with field count, not + match-arm count), so compile-time/size measurements at those tiers are + deferred to #55 and must not be retried uncapped on + 8-GiB-class hosts. +- Shared CLI options apply to every `--lang`, not just the preceding one. Use + per-target manifest entries for heterogeneous configurations. Schema-less + `generate` must not silently ignore generation overrides. + +- Shell completion adapters live under `polyxml-cli/src/completions/`. Their + hidden `__complete` helper filters candidates through the same option resolver + used for generation; preserve multi-target intersection and language aliases. + Bash is exercised in CLI integration tests. Zsh/Fish tests run when those + shells are available on PATH, and skip otherwise. +- `benchmarks/cli/benchmark.sh` uses hyperfine with `--shell=none` to avoid shell + calibration error for sub-5ms startup measurements. Results are fresh processes + with warm OS caches, not machine-reboot or cold-disk startup measurements. + +- Color diagnostics respect `NO_COLOR`. When checking terminal color in a PTY, + unset `NO_COLOR` in the test subprocess and use a color-capable `TERM`; test + the opt-out separately. This development environment sets `NO_COLOR=1`. + +## 10. Pattern OR/AND Semantics & Enforcement (issue #54) + +W3C XSD combines `xs:pattern` two ways: multiple patterns **within one +`` are OR'd**; patterns inherited **across derivation steps are +AND'd**. The IR keeps `RestrictionFacets.patterns: Vec` flat — the +parser encodes each restriction's alternatives as ONE regex group: + +- `schema_parser` collapses `patterns.len() > 1` per restriction into + `(alt1)|(alt2)` (a singleton stays verbatim), then inheritance prepends/appends + each derived step's entry. Downstream code just ANDs the flat list — each + entry is already an OR-group. **Never** AND the pre-collapse alternatives. +- Round-trip tests must serialize `RestrictionFacets`, not the whole + `SchemaIR`: `SchemaIR.types` is a `BTreeMap` and serde_json rejects + struct map keys ("key must be a string"). This is pre-existing and unrelated + to facets. + +Shared helpers in `codegen/mod.rs` (use them; don't re-derive): + +- `primitive_base(ty, ir)` — resolves a simple alias chain to its primitive, + cycle-safe. Patterned simple types must emit their **primitive** base (e.g. + `std::string`, not the alias) so validators/codecs operate on real scalars. +- `patterned_simple(ty, ir)` — the patterned `SimpleTypeDef` behind a field + type, unwrapping `Boxed`/`List`. + +Per-language enforcement (all gated so unpatterned schemas stay byte-identical): + +- **Rust**: free `validate_{Type}_patterns(&str)` fns with `OnceLock` + per pattern; structs gain `validate_patterns()` called from `from_xml`/ + `to_xml`, attributes/text/empty-element paths call the free fn directly. +- **Go**: `Validate() error` on patterned aliases (+ `UnmarshalText`/ + `MarshalText` for string bases so `encoding/xml` enforces on read/write); + struct `Validate` recurses into fields, lists, and pointers. Imports + `regexp`/`fmt` only when patterns exist. +- **C++**: free `validate_{Type}_patterns(std::string_view)` with function-local + `static const std::regex`; struct `validate()` calls it. **Inline field + expressions into the call** — binding `const auto& value = ;` + self-references when the field is named `value` (range-for over a member named + `value` is fine). `#include ` is conditional on `ir`. +- **Java**: `Pattern.compile(p).matcher(v).matches()`; direct codecs resolve + patterned aliases through `primitive_base`. +- **C#**: `Regex.IsMatch` with `\A(?:...)\z` full-value anchors; + escape `\` before `"` in the pattern string. +- **TypeScript**: one pattern → `pattern:` option (zod/valibot) or single + `Type.String({pattern})`; multiple → `AfterValidator`-equivalent AND via + `_polyxml_patterns` (Python) / `Type.Intersect([...])` (TypeBox — duplicating + the `pattern:` key silently kept only one). Escape `/` inside regex literals. + +Execution tests live in `crates/polyxml-core/tests/test_pattern_codegen.rs` +(wired into `scripts/test_codegen.sh`): they **run** generated Go/C++/Rust/ +Python/Java/C# validators against values matching only one alternative, values +satisfying two restriction steps, and reject lists/optionals carrying a bad +element. The TS leg needs `POLYXML_TS_TEST_MODULES` pointing at a node_modules +with `zod@3 valibot@1 @sinclair/typebox@0.34 typescript@5` and skips otherwise. + +## 11. Python Abstract Meta & Runtime Type Discovery (issue #53) + +`xsi:type` dispatch is a **dynamic-runtime feature** (see +`polyxml-core-engine` skill §5 and `docs/guides/polymorphism.md`); the +codegen only has to expose two facts to the PyO3 layer: + +- **`Meta.abstract = True`** is emitted for `is_abstract` structs in + `codegen/python/mod.rs` (inside the `emit_meta` block, after + `namespace`). PyO3 reads it in `extract_schema_from_class` → + `builder.is_abstract(true)`, which turns unknown `xsi:type` values into + loud errors instead of silent truncation. Only abstract types may emit + it — locked by `test_python_abstract_meta_emission`. +- **Class inheritance must stay intact**: `emit_struct` already emits + `class Derived(Base):`, and PyO3 builds a derived class's schema from + `__dataclass_fields__`/`model_fields`, which include inherited fields — + do not flatten inheritance away or variant schemas will miss base fields. + +Key gotchas when touching this area: + +- Variant discovery (`discover_variants`) must run **after** both global + cache inserts in `get_or_create_schema_meta`, otherwise a subclass field + typed as the base class re-enters extraction and recurses infinitely. +- Variant keys come from `Meta.name` (the XML type name), not the class + name — codegen may mangle class identifiers (`type_ident`) while + `xsi:type` always carries the wire QName local part. +- `polyxml.serialize` takes an optional **`target_type`** (pyo3 + wrapper): + given the declared base, a concrete instance is converted against the + base schema and the variant's record triggers `xsi:type` re-emission at + the root; omitted, the instance's concrete schema is used (no selector). + Both branches are covered in `tests/test_xsi_type.py` (11 tests, kept at + 100% statement/branch coverage). diff --git a/.agents/skills/polyxml-core-engine/SKILL.md b/.agents/skills/polyxml-core-engine/SKILL.md index 8dab3f92..ae8b48b7 100644 --- a/.agents/skills/polyxml-core-engine/SKILL.md +++ b/.agents/skills/polyxml-core-engine/SKILL.md @@ -17,10 +17,15 @@ This skill documents the high-performance design patterns and strict constraints - Never import `pyo3`, Python runtime types, or Python C-API dependencies in this crate. 2. **Streaming Event Model**: - Avoid building DOM trees in memory during parsing. - - Stream tokens using `quick-xml::events::Event` (`Event::Start`, `Event::End`, `Event::Text`, `Event::Empty`). + - Stream tokens using `quick-xml::events::Event` (`Event::Start`, `Event::End`, `Event::Text`, `Event::Empty`, `Event::CData`, `Event::GeneralRef`). 3. **Zero Intermediate Allocations**: - Parse integers, floats, and booleans directly from raw byte slices `&[u8]` using `lexical_core::parse` and custom byte parsers. - Use `smallvec::SmallVec` for small, bounded collections of attributes or namespace declarations on the stack. +4. **Complete Event Coverage in Read Loops** (found 2026-09-22): + - quick-xml SPLITS character data: `a & b` arrives as `Text` + `GeneralRef` + `Text`, and CDATA is its own event. Any accumulating loop (`parser.rs`, `transcoder.rs`, generated codecs) must handle `Text`/`CData`/`GeneralRef` or content silently vanishes. + - Resolve refs leniently like `parser.rs::append_general_ref`: `is_char_ref()`/`resolve_char_ref()` for `&#NN;`/`&#xNN;`, `escape::resolve_xml_entity()` for `amp`/`lt`/`gt`/`quot`/`apos`, else push the raw name (never error — a document the core accepts must not fail in generated code). + - Never enable `trim_text` on a reader that assembles split text: per-segment trimming loses spaces around refs (`x & y` → `x&y`). Trim the assembled buffer once at element end (the transcoder `End` arm). + - Attribute values arrive raw too — always `escape::unescape` them (`parse_attributes`, the transcoder's Start/Empty attr arms). ## 2. Testing & Verification @@ -63,7 +68,9 @@ Key mechanisms (regression-locked in `crates/polyxml-core/tests/test_issue51_aud `complexType`/`simpleType` is extracted as a uniquely named top-level type (`unique_type_name`) — its fields must never leak into the parent struct. 4. **simpleContent**: `` emits a `Text` field named `value` - carrying the extension base type. + carrying the extension base type (decoded/encoded by generated Rust codecs + via `emit_text_content_parse`/`emit_text_content_serialize` since + 2026-09-22). 5. **Pattern inheritance**: derived simple types append their base type's patterns (AND semantics) via `inherit_pattern_facets`; enum facet inheritance is intentionally skipped (`EnumDef` has no facets). @@ -73,3 +80,63 @@ Key mechanisms (regression-locked in `crates/polyxml-core/tests/test_issue51_aud per-includer via `rekey_to_namespace`/`rekey_new_state`. 7. Parser reuse across `parse_file` calls is supported; groups partially replay from `file_groups` on cache hit. + +## 5. `xsi:type` Polymorphic Dispatch (issue #53) + +The runtime dispatches polymorphic elements through a **type registry on +`ModelSchema`** — never through generated code. Documented for users in +`docs/guides/polymorphism.md`; regression-locked in +`crates/polyxml-core/tests/test_issue53_xsi_type.rs`. + +- **Registry**: `ModelSchema` carries `is_abstract: bool` plus a + `variants: Arc>>>` (methods: `set_variants`, + `variants`, `has_variants`, `find_variant_qname(namespace, local_bytes)`, + `matches_variant(candidate)`). The lock lets Python refresh the registry + after new subclasses are imported. Variants carry their QName namespace + and local name (`xml_name`), which is `Meta.name` on the Python side. +- **Population (`schema.rs::from_ir`)**: `build_struct` (a) flattens the + `xs:extension` content model by collecting the base-chain structs + (root-base first, cycle-guarded) and prepending their fields — mirroring + Java codegen's `collect`, so derived runtime schemas carry inherited + fields — and (b) scans the `BTreeMap` of types for transitive + derivations (`derives_from`), building each inside a `visited` + insert/remove guard before `set_variants`. **Python-side population + (pyo3 `lib.rs`)**: after both global caches are inserted in + `get_or_create_schema_meta`, `discover_variants` walks + `__subclasses__()` transitively (ptr-seen set, extracts only + dataclass/Pydantic classes) — running *after* cache insertion so a + subclass field typed as the base resolves from cache instead of + re-entering extraction. Cached classes refresh the registry on lookup; + refresh walks nested field schemas too, so a subclass loaded after a + containing model was cached is visible. A thread-local discovery guard + prevents recursive refresh during extraction. +- **Parse (`parser.rs`)**: `resolve_record_schema(declared, start, scope)` runs at + *every* frame creation site — root `Start`/`Empty` in + `deserialize_with_limit`, the root frame in `parse_sub_tree`, both + nested `Start`/`Empty` arms (single and list), and the `XmlItemStream` + `Empty` arm. Namespace scopes track declarations through nested elements, + including streamed records. The attribute must resolve to the XML Schema + Instance namespace, and its QName value must match both the variant's + namespace and local name. Policy: registered concrete variant → switch + schema; absent or unknown value on `is_abstract` → + `PolyXmlError::SchemaError` listing known variants, or an + "escape hatch" error when none are registered; everything else falls + back to the declared schema. Schemas that are neither abstract nor have + variants return immediately without scanning attributes (zero-overhead + fast path — this is also why a content attribute named `type` can never + be misdispatched on plain types). +- **Serialize (`serializer.rs`)**: `write_model` checks + `schema.matches_variant(rec)` on `Record` values, shadows the effective + schema to the variant, and pushes `xsi:type` (qualified QName via + `qualify_element`) after content attributes. Serialization errors when + namespaces are disabled or XSI has no bound prefix. `NamespaceContext::collect_namespaces` + recurses `variants` and injects the XSI URI so `xmlns:xsi` is always + declared at the root whenever dispatch is possible. +- **JSON (`json.rs`)**: `poly_value_to_json_value` prefers the record's + own schema when its name differs from the declared one, so variant-only + fields survive `xml_to_json` (JSON carries no selector marker). +- **Escape hatches**: generated static codecs (Rust serde, Java) do *not* + dispatch — Java's codec errors loudly; deserialize the concrete type or + use the dynamic runtime. `polyxml-js` bindings are scalar-flat today, so + dispatch is unreachable from JS until nested schemas land (shared core + means no future binding work). diff --git a/Cargo.lock b/Cargo.lock index 77441c50..dd5114eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -547,6 +547,59 @@ dependencies = [ "winapi", ] +[[package]] +name = "phf" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "010378780309880b08997fae13be7834dba947d36393bd372f2b1556deb2a2f6" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b585a510fb76fdebead6897982ef2a03a21d8e6cbcca904999742a4afc6ffe" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb62e0959d5a1bebc965f4d15d9e2b7cea002b6b0f5ba8cde6cc26738467100" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fa8d0ca26d424d27630da600c6624696e7dec8bf7b3b492b383c5dc49e5e085" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_shared" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fd9027e2d9319be6349febd1db4e8d02aa544921200c9b777720ac34a3aa89" +dependencies = [ + "siphasher", +] + [[package]] name = "polyxml" version = "0.20.1" @@ -556,6 +609,8 @@ dependencies = [ "lexical-core", "memchr", "minijinja", + "phf", + "phf_codegen", "pretty_assertions", "quick-xml", "serde", @@ -828,6 +883,12 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + [[package]] name = "smallvec" version = "1.16.1" diff --git a/README.md b/README.md index c9b700ea..6313ae42 100644 --- a/README.md +++ b/README.md @@ -64,17 +64,15 @@ curl -fsSL https://raw.githubusercontent.com/nth-bailey/PolyXML/main/scripts/ins Generate strongly-typed code for all 7 languages from any W3C XML Schema in a single command: ```bash -# 1. Multi-target compilation in a single invocation -polyxml generate \ - --lang python --backend pydantic-v2 \ - --lang rust --zero-copy --codecs \ - --lang csharp --namespace Enterprise.Banking \ - --lang java --backend jackson --package com.enterprise.banking \ - --lang typescript --zod \ - --lang go --package payments \ - --lang cpp --mode modules --backend glaze \ - --out ./generated \ - schemas/pain.001.001.09.xsd +# 1. Generate all targets with their defaults +polyxml generate schemas/pain.001.001.09.xsd \ + --lang python --lang rust --lang csharp --lang java \ + --lang typescript --lang go --lang cpp --out ./generated + +# Target-specific backend, style, and enhancements +polyxml generate schemas/pain.001.001.09.xsd --lang java \ + --backend jackson --style pojo --feature builder --feature direct-codec \ + --package com.enterprise.banking --out ./generated/java # 2. Or build an entire enterprise project declaratively polyxml build --config polyxml.toml diff --git a/benchmarks/README.md b/benchmarks/README.md index 344bbaa9..fcb4e54a 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -11,6 +11,7 @@ benchmarks/ ├── README.md # This index: catalog, methodology, conventions ├── run_all.sh # Orchestrator: builds the extension, runs Rust + Python suites ├── __init__.py # Package marker for `python -m benchmarks.python` +├── cli/ # CLI startup and argument validation (hyperfine) ├── python/ # Python bindings vs the Python XML ecosystem (custom CLI suite) └── java/ # Java bindings vs JAXB, Jackson, and the Panama native binding (JMH) ``` @@ -24,7 +25,9 @@ equivalent) and never at this root. | Suite | Location | Tooling | How to run | | :--- | :--- | :--- | :--- | | Rust core engine | [`crates/polyxml-core/benches/`](../crates/polyxml-core/benches/) | [Criterion.rs](https://github.com/bheisler/criterion.rs) | `cargo bench --bench core_benchmarks` | +| Rust tag dispatch | [`crates/polyxml-core/benches/`](../crates/polyxml-core/benches/) | [Criterion.rs](https://github.com/bheisler/criterion.rs) + `perf stat` | `cargo bench --bench tag_dispatch` ([results](../docs/benchmarks/rust-phf-dispatch.md)) | | Python comparative | [`python/`](python/README.md) | Custom CLI suite | `python -m benchmarks.python` or `./benchmarks/run_all.sh` | +| CLI startup | [`cli/`](cli/README.md) | hyperfine | `./benchmarks/cli/benchmark.sh` | | Java four-runtime | [`java/`](java/README.md) | JMH (Maven) | See [`java/README.md`](java/README.md) | The Rust Criterion suite lives inside its crate because `cargo bench` requires @@ -60,6 +63,15 @@ Suite READMEs document their own tooling; these rules apply to all of them: claim; a single run on a shared machine is noise. 4. Published figures live in [`docs/benchmarks.md`](../docs/benchmarks.md) — update them from a full, idle-host run, not from a smoke. +5. **Run under a memory cap on shared/small hosts.** Heavy builds and bench + runs go through [`scripts/memcap.sh`](../scripts/memcap.sh), which caps + the run at 60% of available RAM in an isolated cgroup (kernel OOM-kills + only the runaway process instead of freezing the host; `ulimit` + fallback where systemd is unavailable). The suite entry points + (`run_all.sh`, `cli/benchmark.sh`, `scripts/perf_stat.sh`) already + re-exec through it; wrap any ad-hoc `cargo bench`/`--release` build the + same way. Tune with `POLYXML_MEMCAP_PCT`, opt out with + `POLYXML_MEMCAP_DISABLE=1`. ## Adding a suite diff --git a/benchmarks/cli/.gitignore b/benchmarks/cli/.gitignore new file mode 100644 index 00000000..b83d2226 --- /dev/null +++ b/benchmarks/cli/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/benchmarks/cli/README.md b/benchmarks/cli/README.md new file mode 100644 index 00000000..f85e8de3 --- /dev/null +++ b/benchmarks/cli/README.md @@ -0,0 +1,32 @@ +# CLI startup and argument validation + +Run from the repository root with Rust and `hyperfine` installed: + +```bash +./benchmarks/cli/benchmark.sh +``` + +The runner builds the release CLI, creates an empty temporary XSD, and measures +100 fresh processes per command after five warmups. `--shell=none` excludes +shell startup and avoids shell-calibration noise for sub-5ms commands. JSON +results are saved to `benchmarks/cli/target/cli.json` (gitignored). + +| Case | Work measured | +| --- | --- | +| `help` | Process startup and top-level help rendering | +| `generate-help` | Process startup and generation help rendering | +| `parse-and-validate` | Argument parsing, Java backend/style/features validation, and parsing an empty XSD in dry-run mode | + +A local Linux x86-64 release build measured on 2026-09-22 with hyperfine 1.19.0: + +| Case | Mean ± standard deviation | +| --- | --- | +| `help` | 1.4 ± 0.2 ms | +| `generate-help` | 1.5 ± 0.2 ms | +| `parse-and-validate` | 1.6 ± 0.2 ms | + +These measurements meet issue #50's 5ms target on this host. They use warm OS +caches: they do **not** establish cold-disk or post-reboot startup latency. +The parsing case includes tiny-schema I/O and parsing, so it is not an isolated +argument-parser microbenchmark. Results depend on hardware and system load; +rerun the script to compare changes on the same host. diff --git a/benchmarks/cli/benchmark.sh b/benchmarks/cli/benchmark.sh new file mode 100755 index 00000000..25e3da5b --- /dev/null +++ b/benchmarks/cli/benchmark.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Measure fresh-process CLI startup and argument validation with hyperfine. +set -euo pipefail +repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd) +self="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/$(basename -- "${BASH_SOURCE[0]}")" +cd "$repo_root" +# Release builds + hyperfine under a RAM cap (see scripts/memcap.sh); the +# LEVEL guard makes the re-exec idempotent. POLYXML_MEMCAP_DISABLE=1 opts out. +if [ -z "${POLYXML_MEMCAP_LEVEL:-}" ]; then + exec "$repo_root/scripts/memcap.sh" "$self" "$@" +fi +command -v hyperfine >/dev/null || { echo 'Install hyperfine to run this benchmark.' >&2; exit 1; } +cargo build --release -p polyxml-cli +scratch_dir=$(mktemp -d) +trap 'rm -rf "$scratch_dir"' EXIT +export POLYXML_CLI_BENCH_SCHEMA="$scratch_dir/minimal.xsd" +printf '%s\n' '' > "$POLYXML_CLI_BENCH_SCHEMA" +mkdir -p benchmarks/cli/target +hyperfine --shell=none --warmup 5 --runs 100 \ + --export-json benchmarks/cli/target/cli.json \ + --command-name help 'target/release/polyxml --help' \ + --command-name generate-help 'target/release/polyxml generate --help' \ + --command-name parse-and-validate "target/release/polyxml generate \"$POLYXML_CLI_BENCH_SCHEMA\" --lang java --backend jackson --style pojo --feature builder --feature direct-codec --dry-run" diff --git a/benchmarks/java/generate.py b/benchmarks/java/generate.py index 5dcb63dd..05b192c0 100644 --- a/benchmarks/java/generate.py +++ b/benchmarks/java/generate.py @@ -22,7 +22,7 @@ subprocess.run(["cargo", "build", "-p", "polyxml-cli"], cwd=repo, check=True) for style, package in [("pojo", "pojo"), ("record", "records")]: subprocess.run([str(binary), "generate", str(xsd), "--lang", "java", "--style", style, - "--builder", "--codec", "direct", "--backend", "jackson", + "--feature", "builder,direct-codec", "--backend", "jackson", "--package", f"io.polyxml.bench.{package}", "--out", str(target / "generated-sources/polyxml" / package)], check=True) helper = target / "generated-sources/polyxml/RecordMutation.java" args = ", ".join("status" if field == "status" else f"value.{field}()" for field in fields) @@ -30,5 +30,5 @@ 'public static io.polyxml.bench.records.Message withStatus(io.polyxml.bench.records.Message value, String status) {\n' f'return new io.polyxml.bench.records.Message({args});\n' + '}\n}\n') subprocess.run([str(binary), "generate", str(base / "cases.xsd"), "--lang", "java", "--style", "pojo", - "--builder", "--codec", "direct", "--backend", "jackson", + "--feature", "builder,direct-codec", "--backend", "jackson", "--package", "io.polyxml.bench.cases", "--out", str(target / "generated-sources/polyxml/cases")], check=True) diff --git a/benchmarks/run_all.sh b/benchmarks/run_all.sh index aba60c44..435b6a46 100755 --- a/benchmarks/run_all.sh +++ b/benchmarks/run_all.sh @@ -8,6 +8,14 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +# Cap the whole run at POLYXML_MEMCAP_PCT (default 60%) of available RAM via +# scripts/memcap.sh: release builds + Criterion can exhaust small hosts and +# freeze them. The LEVEL guard makes the re-exec idempotent (no loop) on +# hosts without a systemd user manager; POLYXML_MEMCAP_DISABLE=1 opts out. +if [ -z "${POLYXML_MEMCAP_LEVEL:-}" ]; then + exec "${ROOT_DIR}/scripts/memcap.sh" "${BASH_SOURCE[0]}" "$@" +fi + echo "======================================================================" echo "⚡ PolyXML Performance & Benchmarking Suite" echo "======================================================================" diff --git a/crates/polyxml-cli/README.md b/crates/polyxml-cli/README.md index 879bc117..cebad7e3 100644 --- a/crates/polyxml-cli/README.md +++ b/crates/polyxml-cli/README.md @@ -49,12 +49,12 @@ cargo install polyxml-cli | Target | Flag (`--lang`) | Generated Artifacts & Features | | :--- | :--- | :--- | | **Python** | `python` | Modern Python 3.12+ `@dataclass` or Pydantic v2 models, field constraints, zero-copy streaming codecs | -| **Rust** | `rust` | Zero-copy `Cow<'a, str>` & owned structs, automatic recursive boxing (`Box`), streaming serializers/deserializers, rkyv wire format (`--rkyv`) | +| **Rust** | `rust` | Zero-copy `Cow<'a, str>` & owned structs, automatic recursive boxing (`Box`), streaming serializers/deserializers, rkyv wire format (`--feature rkyv`) | | **C++** | `cpp` | Modern C++20/C++23 value types, C++20 Modules (`--mode modules`), Glaze reflection (`--backend glaze`), CMake/Meson export | -| **Java** | `java` | Java 21+ records or mutable JavaBeans (`--style pojo`), fluent builders (`--builder`), direct StAX codecs (`--codec direct`), Jackson XML/JSON annotations (`--backend jackson`) | +| **Java** | `java` | Java 21+ records or mutable JavaBeans (`--style pojo`), fluent builders (`--feature builder`), direct StAX codecs (`--feature direct-codec`), Jackson XML/JSON annotations (`--backend jackson`) | | **TypeScript** | `typescript` | TypeScript 5+ interfaces, discriminated unions, runtime validation schemas via Zod, Valibot, or TypeBox (`--backend`) | | **Go** | `go` | Idiomatic Go 1.22+ structs with `encoding/xml` tags, reflectionless EasyJSON (`--backend easyjson`) & ByteDance Sonic JIT (`--backend sonic`) | -| **C#** | `csharp` | Modern C# 12 / .NET 8+ mutable classes (`--style class`), records and record structs (`--record-kind struct`), compile-time Native AOT source generation (`--source-gen`) | +| **C#** | `csharp` | Modern C# 12 / .NET 8+ mutable classes (`--style class`), records and record structs (`--style record-struct`), compile-time Native AOT source generation (`--backend source-gen`) | --- @@ -78,7 +78,7 @@ polyxml generate --lang java --backend jackson --package com.enterprise.banking polyxml generate --lang cpp --mode modules --backend glaze --package enterprise::crm --out ./generated/cpp schemas/order.xsd # Generate zero-copy Rust models with codecs and rkyv wire format -polyxml generate --lang rust --zero-copy --codecs --rkyv --out ./generated/rust schemas/order.xsd +polyxml generate --lang rust --feature zero-copy --codecs --feature rkyv --out ./generated/rust schemas/order.xsd # Generate TypeScript with tree-shakeable Valibot schemas polyxml generate --lang ts --backend valibot --out ./generated/ts schemas/order.xsd @@ -87,7 +87,7 @@ polyxml generate --lang ts --backend valibot --out ./generated/ts schemas/order. polyxml generate --lang go --backend sonic --package crm --out ./generated/go schemas/order.xsd # Generate C# record structs with Native AOT source generation -polyxml generate --lang csharp --record-kind struct --source-gen --namespace Enterprise.Crm --out ./generated/csharp schemas/order.xsd +polyxml generate --lang csharp --style record-struct --backend source-gen --namespace Enterprise.Crm --out ./generated/csharp schemas/order.xsd # Multi-target compilation in a single invocation polyxml generate \ @@ -141,9 +141,8 @@ codecs = true [[generate]] target = "rust" output = "src/generated/rust" -zero_copy = true +features = ["zero-copy", "rkyv"] codecs = true -rkyv = true [[generate]] target = "java" @@ -172,8 +171,8 @@ backend = "sonic" target = "csharp" output = "src/generated/csharp" namespace = "Enterprise.Banking.Iso20022" -source_gen = true -record_kind = "struct" +backend = "source-gen" +style = "record-struct" ``` --- @@ -185,3 +184,10 @@ Explore full-scale repositories using PolyXML CLI manifests across all 7 languag - **[💳 FinTech & Banking](https://github.com/nth-bailey/polyxml-finance-examples)**: Instant Payments ↔ ISO 20022 pacs.008 XML - **[🚍 Smart Cities & Transit](https://github.com/nth-bailey/polyxml-transit-examples)**: Google GTFS-Realtime ↔ CEN SIRI v2.0 & NeTEx XML + +## Shell completion + +Run `polyxml completions bash`, `polyxml completions zsh`, or +`polyxml completions fish` to generate a shell script. Backend, style, and feature +suggestions are filtered by the selected language(s). See the +[compiler guide](../../docs/guides/compiler.md#shell-completion) for installation. diff --git a/crates/polyxml-cli/src/completions.rs b/crates/polyxml-cli/src/completions.rs new file mode 100644 index 00000000..8d51bce0 --- /dev/null +++ b/crates/polyxml-cli/src/completions.rs @@ -0,0 +1,158 @@ +//! Shell adapters use the validator itself to keep suggestions target-aware. +use super::{Cli, TargetEmitOptions}; +use clap::{CommandFactory, ValueEnum}; + +#[derive(Debug, Clone, Copy, ValueEnum)] +pub enum Shell { + Bash, + Zsh, + Fish, +} + +pub fn print_script(shell: Shell) { + print!( + "{}", + match shell { + Shell::Bash => include_str!("completions/bash"), + Shell::Zsh => include_str!("completions/zsh"), + Shell::Fish => include_str!("completions/fish"), + } + ); +} + +/// Tokens exclude the executable and the current incomplete word. Ignore +/// unrelated options, but retain every selected language and backend. +pub fn candidates(kind: &str, words: &[String]) -> Vec { + let mut languages = Vec::new(); + let mut backend = None; + let mut iter = words.iter(); + while let Some(word) = iter.next() { + match word.as_str() { + "--" => break, + "--lang" | "-l" => { + if let Some(value) = iter.next() { + let value = if value == "=" { + iter.next() + } else { + Some(value) + }; + if let Some(value) = value { + languages.push(value.as_str()); + } + } + } + "--backend" | "-b" => { + backend = iter + .next() + .and_then(|value| { + if value == "=" { + iter.next() + } else { + Some(value) + } + }) + .map(String::as_str); + } + _ => { + if let Some(value) = word + .strip_prefix("--lang=") + .or_else(|| word.strip_prefix("-l").filter(|v| !v.is_empty())) + { + languages.push(value); + } else if let Some(value) = word + .strip_prefix("--backend=") + .or_else(|| word.strip_prefix("-b").filter(|v| !v.is_empty())) + { + backend = Some(value); + } + } + } + } + if languages.is_empty() { + languages.push("python"); + } + let choices: &[&str] = match kind { + "lang" => &[ + "python", + "rust", + "cpp", + "java", + "typescript", + "go", + "csharp", + ], + "backend" => &[ + "dataclass", + "pydantic", + "interfaces", + "zod", + "valibot", + "typebox", + "standard", + "jackson", + "source-gen", + "glaze", + "easyjson", + "sonic", + ], + "style" => &[ + "dataclass", + "record", + "pojo", + "class", + "record-class", + "record-struct", + ], + "feature" => &[ + "zero-copy", + "rkyv", + "phf", + "builder", + "direct-codec", + "slots", + "kw-only", + ], + "command" => { + return Cli::command() + .get_subcommands() + .filter(|c| !c.is_hide_set()) + .map(|c| c.get_name().to_string()) + .collect() + } + "flag" => { + let command = Cli::command(); + let Some(subcommand) = words.first().and_then(|word| command.find_subcommand(word)) + else { + return vec!["--help".into(), "--version".into()]; + }; + return subcommand + .get_arguments() + .filter(|a| !a.is_hide_set()) + .filter_map(|a| a.get_long().map(|name| format!("--{name}"))) + .chain(["--help".into(), "--version".into()]) + .collect(); + } + _ => &[], + }; + choices + .iter() + .filter(|value| { + if kind == "lang" { + return true; + } + let features = vec![value.to_string()]; + let opts = TargetEmitOptions { + backend: if kind == "backend" { + Some(**value) + } else { + backend + }, + style: (kind == "style").then_some(**value), + features: if kind == "feature" { &features } else { &[] }, + ..Default::default() + }; + languages.iter().all(|lang| opts.resolve(lang).is_ok()) + }) + .map(|value| value.to_string()) + .collect() +} diff --git a/crates/polyxml-cli/src/completions/bash b/crates/polyxml-cli/src/completions/bash new file mode 100644 index 00000000..a8778791 --- /dev/null +++ b/crates/polyxml-cli/src/completions/bash @@ -0,0 +1,33 @@ +# Bash completion for PolyXML. Install with: source <(polyxml completions bash) +_polyxml() { + local cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" + local kind=flag prefix= candidates + local -a words=("${COMP_WORDS[@]:1:COMP_CWORD-1}") + COMPREPLY=() + if [[ "$prev" == = && "$COMP_CWORD" -ge 2 ]]; then prev="${COMP_WORDS[COMP_CWORD-2]}"; fi + if [[ "$cur" == = ]]; then cur=; fi + case "$prev" in + --lang|-l) kind=lang ;; + --backend|-b) kind=backend ;; + --style) kind=style ;; + --feature) kind=feature ;; + --out|--out-dir|-o|--config|-c|--schema|-s) return ;; + esac + case "$cur" in + --lang=*|--backend=*|--style=*|--feature=*) + kind="${cur%%=*}"; kind="${kind#--}" + prefix="${cur%%=*}="; cur="${cur#*=}" ;; + esac + if [[ "$kind" == feature && "$cur" == *,* ]]; then + prefix+="${cur%,*},"; cur="${cur##*,}" + fi + if (( COMP_CWORD == 1 )); then kind=command; fi + if [[ "$kind" == flag && "$cur" != -* ]]; then return; fi + candidates=$("${COMP_WORDS[0]}" __complete "$kind" -- "${words[@]}" 2>/dev/null) + local value + while IFS= read -r value; do + [[ -n "$value" && "$value" == "$cur"* ]] && COMPREPLY+=("$prefix$value") + done <<< "$candidates" + return 0 +} +complete -o default -F _polyxml polyxml diff --git a/crates/polyxml-cli/src/completions/fish b/crates/polyxml-cli/src/completions/fish new file mode 100644 index 00000000..798643d6 --- /dev/null +++ b/crates/polyxml-cli/src/completions/fish @@ -0,0 +1,22 @@ +# Fish completion for PolyXML. Save to ~/.config/fish/completions/polyxml.fish +function __polyxml_values + set -l words (commandline -opc) + command $words[1] __complete $argv[1] -- $words[2..-1] 2>/dev/null +end +complete -c polyxml -n 'not __fish_seen_subcommand_from generate build validate transcode completions' -f -a 'generate build validate transcode completions' +complete -c polyxml -n '__fish_seen_subcommand_from generate' -s l -l lang -r -f -a '(__polyxml_values lang)' +complete -c polyxml -n '__fish_seen_subcommand_from generate' -s b -l backend -r -f -a '(__polyxml_values backend)' +complete -c polyxml -n '__fish_seen_subcommand_from generate' -l style -r -f -a '(__polyxml_values style)' +complete -c polyxml -n '__fish_seen_subcommand_from generate' -l feature -r -f -a '(__polyxml_values feature)' +complete -c polyxml -n '__fish_seen_subcommand_from generate build' -l dry-run +complete -c polyxml -n '__fish_seen_subcommand_from generate build' -l format +complete -c polyxml -n '__fish_seen_subcommand_from generate build' -s c -l config -r +complete -c polyxml -n '__fish_seen_subcommand_from generate transcode' -s o -l out -r +complete -c polyxml -n '__fish_seen_subcommand_from generate' -s p -l package -r -f +complete -c polyxml -n '__fish_seen_subcommand_from generate' -s m -l mode -r -f -a 'header modules' +complete -c polyxml -n '__fish_seen_subcommand_from generate' -l strict-facets +complete -c polyxml -n '__fish_seen_subcommand_from generate' -l codecs -f -a 'true false' +complete -c polyxml -n '__fish_seen_subcommand_from generate' -l custom-header -r -f +complete -c polyxml -n '__fish_seen_subcommand_from completions' -f -a 'bash zsh fish' +complete -c polyxml -s h -l help +complete -c polyxml -s V -l version diff --git a/crates/polyxml-cli/src/completions/zsh b/crates/polyxml-cli/src/completions/zsh new file mode 100644 index 00000000..3cec7847 --- /dev/null +++ b/crates/polyxml-cli/src/completions/zsh @@ -0,0 +1,38 @@ +#compdef polyxml +# Save as _polyxml in a directory on $fpath before running compinit. +_polyxml_values() { + local kind=$1 + local -a values + values=("${(@f)$("${words[1]}" __complete "$kind" -- "${words[@]:1:$((CURRENT-2))}" 2>/dev/null)}") + compadd -a values +} +_polyxml() { + if (( CURRENT == 2 )); then + _polyxml_values command + return + fi + case "$words[2]" in + generate) + _arguments -s \ + '*'{-l,--lang}'[Target language]:language:_polyxml_values lang' \ + {-b,--backend}'[Serialization backend]:backend:_polyxml_values backend' \ + '--style[Type representation]:style:_polyxml_values style' \ + '*--feature[Opt-in enhancement]:feature:_polyxml_values feature' \ + {-o,--out}'[Output directory]:directory:_files -/' \ + {-c,--config}'[Workspace manifest]:file:_files' \ + {-p,--package}'[Package or namespace]:package:' \ + {-m,--mode}'[C++ packaging mode]:mode:(header modules)' \ + '--codecs[Emit codecs]:boolean:(true false)' \ + '--custom-header[Custom header]:text:' \ + '--strict-facets[Enforce facets]' '--dry-run[Validate without writing]' \ + '--format[Format generated code]' '--help[Show help]' \ + '*:schema:_files' ;; + *) + if [[ "$PREFIX" == -* ]]; then _polyxml_values flag; else _files; fi ;; + esac +} +if [[ "$funcstack[1]" == _polyxml ]]; then + _polyxml "$@" +else + compdef _polyxml polyxml +fi diff --git a/crates/polyxml-cli/src/config.rs b/crates/polyxml-cli/src/config.rs index 888c637e..e26e7fac 100644 --- a/crates/polyxml-cli/src/config.rs +++ b/crates/polyxml-cli/src/config.rs @@ -44,12 +44,18 @@ pub struct WorkspaceSection { } /// Target configuration from either `[[generate]]` or `[codegen.]`. +/// Unknown keys are rejected so removed legacy fields (`zod`, `source_gen`, +/// `record_kind`, `builder`, `codec`, `rkyv`) fail loudly instead of being +/// silently ignored. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct TargetConfig { pub target: String, pub output: String, pub enabled: Option, pub backend: Option, + #[serde(default)] + pub features: Vec, pub package: Option, pub namespace: Option, pub strict_facets: Option, @@ -63,21 +69,18 @@ pub struct TargetConfig { pub modules: Option, pub mode: Option, pub serializer: Option, - pub zod: Option, - pub source_gen: Option, - pub record_kind: Option, pub style: Option, - pub builder: Option, - pub codec: Option, - pub rkyv: Option, pub custom_header: Option, } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct CodegenTargetConfig { pub enabled: Option, pub output: Option, pub backend: Option, + #[serde(default)] + pub features: Vec, pub package: Option, pub namespace: Option, pub strict_facets: Option, @@ -91,13 +94,7 @@ pub struct CodegenTargetConfig { pub modules: Option, pub mode: Option, pub serializer: Option, - pub zod: Option, - pub source_gen: Option, - pub record_kind: Option, pub style: Option, - pub builder: Option, - pub codec: Option, - pub rkyv: Option, pub custom_header: Option, } @@ -151,6 +148,7 @@ impl WorkspaceManifest { output, enabled: cfg.enabled, backend: cfg.backend.clone(), + features: cfg.features.clone(), package: cfg.package.clone(), namespace: cfg.namespace.clone(), strict_facets: cfg.strict_facets, @@ -164,13 +162,7 @@ impl WorkspaceManifest { modules: cfg.modules, mode: cfg.mode.clone(), serializer: cfg.serializer.clone(), - zod: cfg.zod, - source_gen: cfg.source_gen, - record_kind: cfg.record_kind.clone(), style: cfg.style.clone(), - builder: cfg.builder, - codec: cfg.codec.clone(), - rkyv: cfg.rkyv, custom_header: cfg.custom_header.clone().or_else(|| ws_header.clone()), }); } diff --git a/crates/polyxml-cli/src/main.rs b/crates/polyxml-cli/src/main.rs index b72d6a7e..9c954be1 100644 --- a/crates/polyxml-cli/src/main.rs +++ b/crates/polyxml-cli/src/main.rs @@ -1,10 +1,13 @@ +mod completions; pub mod config; +mod options; +use options::target_options; use std::fs; use std::path::{Path, PathBuf}; use std::process::{self, Command}; -use clap::{Args, Parser, Subcommand}; +use clap::{Args, CommandFactory, Parser, Subcommand}; use config::WorkspaceManifest; use heck::AsPascalCase; use polyxml::codegen::cpp::{CppBackend, CppCodegen, CppMode, CppOptions}; @@ -42,6 +45,19 @@ pub enum Commands { /// Bidirectionally transcode XML ↔ JSON with zero-copy streaming Transcode(TranscodeArgs), + + /// Print a shell completion script with target-aware option suggestions + Completions { + #[arg(value_enum)] + shell: completions::Shell, + }, + + #[command(name = "__complete", hide = true)] + Complete { + kind: String, + #[arg(last = true)] + words: Vec, + }, } #[derive(Debug, Args)] @@ -58,6 +74,10 @@ pub struct GenerateArgs { #[arg(short = 'b', long = "backend", value_name = "BACKEND")] pub backend: Option, + /// Opt-in target enhancement (repeatable; see compiler guide for supported values) + #[arg(long = "feature", value_name = "NAME", value_delimiter = ',')] + pub features: Vec, + /// Package or namespace for generated code (e.g. 'com.example.models' for Java, 'polyxml::models' for C++) #[arg(short = 'p', long = "package", alias = "namespace", value_name = "PKG")] pub package: Option, @@ -66,7 +86,7 @@ pub struct GenerateArgs { #[arg(short = 'm', long = "mode", value_name = "MODE")] pub mode: Option, - /// Zero-copy mode for Rust models (borrow Cow<'a, str> instead of owned String) + /// Zero-copy mode for Rust models (borrow Cow<'a, str>); pass 'false' for owned String fields (default: true) #[arg(long = "zero-copy", default_missing_value = "true", num_args = 0..=1)] pub zero_copy: Option, @@ -74,34 +94,10 @@ pub struct GenerateArgs { #[arg(long = "codecs", default_missing_value = "true", num_args = 0..=1)] pub codecs: Option, - /// Emit runtime Zod validation schemas for TypeScript (default: false) - #[arg(long = "zod", default_missing_value = "true", num_args = 0..=1)] - pub zod: Option, - - /// C# System.Text.Json compile-time source generation context (default: false) - #[arg(long = "source-gen", default_missing_value = "true", num_args = 0..=1)] - pub source_gen: Option, - - /// C# record representation ('class' or 'struct', default: 'class') - #[arg(long = "record-kind", value_name = "KIND")] - pub record_kind: Option, - - /// Java/C# model style: record (default), pojo, or class - #[arg(long, value_parser = ["record", "pojo", "class"])] + /// Target model style (e.g. record, pojo, class, record-class, record-struct) + #[arg(long, value_name = "STYLE")] pub style: Option, - /// Emit fluent Java builders - #[arg(long, default_missing_value = "true", num_args = 0..=1)] - pub builder: Option, - - /// Java XML binding: annotation (default) or direct StAX companion codecs - #[arg(long, value_parser = ["annotation", "direct"])] - pub codec: Option, - - /// Rust rkyv zero-copy binary wire format serialization (default: false) - #[arg(long = "rkyv", default_missing_value = "true", num_args = 0..=1)] - pub rkyv: Option, - /// Output directory for generated source files #[arg(short = 'o', long = "out", alias = "out-dir", value_name = "DIR")] pub out: Option, @@ -193,9 +189,27 @@ fn main() { Commands::Build(args) => run_build(args), Commands::Validate(args) => run_validate(args), Commands::Transcode(args) => run_transcode(args), + Commands::Completions { shell } => { + completions::print_script(shell); + Ok(()) + } + Commands::Complete { kind, words } => { + for value in completions::candidates(&kind, &words) { + println!("{value}"); + } + Ok(()) + } }; if let Err(err) = result { + if err + .downcast_ref::() + .is_some_and(|error| error.kind() == std::io::ErrorKind::InvalidInput) + { + Cli::command() + .error(clap::error::ErrorKind::InvalidValue, err.to_string()) + .exit(); + } eprintln!("Error: {}", err); process::exit(1); } @@ -204,6 +218,20 @@ fn main() { fn run_generate(args: GenerateArgs) -> Result<(), Box> { // If no schemas are passed directly, check for polyxml.toml config if args.schemas.is_empty() { + if !args.lang.is_empty() + || args.backend.is_some() + || args.style.is_some() + || !args.features.is_empty() + || args.package.is_some() + || args.mode.is_some() + || args.zero_copy.is_some() + || args.codecs.is_some() + || args.out.is_some() + || args.custom_header.is_some() + || args.strict_facets + { + return Err("Generation options require explicit schema paths. For manifest builds, set target options in polyxml.toml.".into()); + } let config_path = args.config.unwrap_or_else(|| PathBuf::from("polyxml.toml")); if config_path.exists() { return run_build(BuildArgs { @@ -218,6 +246,33 @@ fn run_generate(args: GenerateArgs) -> Result<(), Box> { } } + let languages = if args.lang.is_empty() { + vec!["python".to_string()] + } else { + args.lang.clone() + }; + + let emit_opts = TargetEmitOptions { + backend: args.backend.as_deref(), + features: &args.features, + slots: None, + kw_only: None, + package: args.package.as_deref(), + mode: args.mode.as_deref(), + zero_copy: args.zero_copy, + codecs: args.codecs, + style: args.style.as_deref(), + builder: None, + codec: None, + rkyv: None, + phf: None, + custom_header: args.custom_header.as_deref(), + }; + let resolved_options = languages + .iter() + .map(|lang| emit_opts.resolve(lang)) + .collect::>>()?; + let mut parser = XsdParser::new(); let mut compiled_schemas = Vec::new(); @@ -237,15 +292,9 @@ fn run_generate(args: GenerateArgs) -> Result<(), Box> { return Ok(()); } - let languages = if args.lang.is_empty() { - vec!["python".to_string()] - } else { - args.lang - }; - let base_out = args.out.unwrap_or_else(|| PathBuf::from("generated")); - for lang in &languages { + for (lang, emit_opts) in languages.iter().zip(resolved_options) { let lang_out = if languages.len() > 1 { base_out.join(lang) } else { @@ -253,21 +302,6 @@ fn run_generate(args: GenerateArgs) -> Result<(), Box> { }; fs::create_dir_all(&lang_out)?; - let emit_opts = TargetEmitOptions { - backend: args.backend.as_deref(), - package: args.package.as_deref(), - mode: args.mode.as_deref(), - zero_copy: args.zero_copy, - codecs: args.codecs, - zod: args.zod, - source_gen: args.source_gen, - record_kind: args.record_kind.as_deref(), - style: args.style.as_deref(), - builder: args.builder, - codec: args.codec.as_deref(), - rkyv: args.rkyv, - custom_header: args.custom_header.as_deref(), - }; for (schema_path, ir) in &compiled_schemas { emit_target_code(lang, emit_opts, &lang_out, schema_path, ir)?; @@ -291,6 +325,11 @@ fn run_build(args: BuildArgs) -> Result<(), Box> { let manifest = WorkspaceManifest::from_file(&args.config)?; let base_dir = args.config.parent().unwrap_or_else(|| Path::new(".")); + let targets = manifest.resolved_targets(); + for target in &targets { + target_options(target).resolve(&target.target)?; + } + let schema_files = manifest.expand_schemas(base_dir)?; if schema_files.is_empty() { println!("No schema files matched workspace schema patterns."); @@ -307,7 +346,6 @@ fn run_build(args: BuildArgs) -> Result<(), Box> { compiled_schemas.push((schema_path.clone(), ir)); } - let targets = manifest.resolved_targets(); if targets.is_empty() { println!("No generation targets configured in manifest."); return Ok(()); @@ -344,29 +382,7 @@ fn run_build(args: BuildArgs) -> Result<(), Box> { target_dir.display() ); - let mode_str = target.mode.as_deref().or_else(|| { - if target.modules.unwrap_or(false) { - Some("modules") - } else { - None - } - }); - - let emit_opts = TargetEmitOptions { - backend: target.backend.as_deref(), - package: target.package.as_deref().or(target.namespace.as_deref()), - mode: mode_str, - zero_copy: target.zero_copy, - codecs: target.codecs, - zod: target.zod, - source_gen: target.source_gen, - record_kind: target.record_kind.as_deref(), - style: target.style.as_deref(), - builder: target.builder, - codec: target.codec.as_deref(), - rkyv: target.rkyv, - custom_header: target.custom_header.as_deref(), - }; + let emit_opts = target_options(target).resolve(&target.target)?; for (schema_path, ir) in &compiled_schemas { emit_target_code(&target.target, emit_opts, &target_dir, schema_path, ir)?; @@ -456,17 +472,18 @@ fn report_schema_ir(ir: &SchemaIR) { #[derive(Debug, Default, Clone, Copy)] pub struct TargetEmitOptions<'a> { pub backend: Option<&'a str>, + pub features: &'a [String], + pub slots: Option, + pub kw_only: Option, pub package: Option<&'a str>, pub mode: Option<&'a str>, pub zero_copy: Option, pub codecs: Option, - pub zod: Option, - pub source_gen: Option, - pub record_kind: Option<&'a str>, pub style: Option<&'a str>, pub builder: Option, pub codec: Option<&'a str>, pub rkyv: Option, + pub phf: Option, pub custom_header: Option<&'a str>, } @@ -477,33 +494,9 @@ fn emit_target_code( schema_path: &Path, ir: &SchemaIR, ) -> std::io::Result<()> { - let use_records = match opts.style.unwrap_or("record") { - "record" => true, - "pojo" | "class" => false, - value => { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - format!("Unknown style: {value}"), - )) - } - }; - let direct_codec = match opts.codec.unwrap_or("annotation") { - "annotation" => false, - "direct" => true, - value => { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - format!("Unknown codec: {value}"), - )) - } - }; + let use_records = !matches!(opts.style, Some("pojo" | "class" | "dataclass")); + let direct_codec = opts.codec == Some("direct"); let language = lang.to_lowercase(); - if (opts.style.is_some() && !matches!(language.as_str(), "java" | "csharp" | "cs" | "c#")) - || ((opts.builder.unwrap_or(false) || opts.codec.is_some()) && language != "java") - || (!use_records && opts.record_kind.is_some_and(|k| k != "class")) - { - return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "style is Java/C# only; builder and codec are Java only; mutable classes cannot use record structs")); - } match language.as_str() { "python" | "py" => { let py_backend = opts @@ -513,8 +506,8 @@ fn emit_target_code( let options = PythonOptions { backend: py_backend, - slots: true, - kw_only: true, + slots: opts.slots.unwrap_or(true), + kw_only: opts.kw_only.unwrap_or(true), pep695_aliases: true, emit_meta: true, emit_root_aliases: true, @@ -549,6 +542,7 @@ fn emit_target_code( emit_root_aliases: true, emit_codecs: opts.codecs.unwrap_or(true), emit_rkyv: opts.rkyv.unwrap_or(false), + phf: opts.phf.unwrap_or(false), custom_header: opts.custom_header.map(|s| s.to_string()), }; @@ -580,7 +574,9 @@ fn emit_target_code( let options = TypeScriptOptions { backend: ts_backend, - emit_zod: opts.zod.unwrap_or(false), + // Zod emission is selected via `--backend zod`; the removed + // legacy `--zod` switch was the only other way to set this. + emit_zod: false, use_interface: true, readonly_fields: false, emit_root_aliases: true, @@ -715,10 +711,11 @@ fn emit_target_code( .map(|s| s.to_string_lossy()) .unwrap_or_else(|| "Models".into()); - let record_kind = opts - .record_kind - .and_then(CSharpRecordKind::from_str_loose) - .unwrap_or(CSharpRecordKind::Class); + let record_kind = if matches!(opts.style, Some("record-struct")) { + CSharpRecordKind::Struct + } else { + CSharpRecordKind::Class + }; let options = CSharpOptions { namespace: ns.to_string(), @@ -729,7 +726,7 @@ fn emit_target_code( use_records, use_file_scoped_namespaces: true, emit_root_records: true, - emit_source_gen: opts.source_gen.unwrap_or(false), + emit_source_gen: opts.backend == Some("source-gen"), source_gen_context_name: format!("{}JsonContext", AsPascalCase(&file_stem)), custom_header: opts.custom_header.map(|s| s.to_string()), }; @@ -743,76 +740,11 @@ fn emit_target_code( } Ok(()) } - _ => emit_target_placeholder(lang, opts, out_dir, schema_path, ir), - } -} - -fn emit_target_placeholder( - lang: &str, - opts: TargetEmitOptions<'_>, - out_dir: &Path, - schema_path: &Path, - ir: &SchemaIR, -) -> std::io::Result<()> { - let file_stem = schema_path - .file_stem() - .map(|s| s.to_string_lossy()) - .unwrap_or_else(|| "models".into()); - - let (filename, header) = match lang.to_lowercase().as_str() { - "python" | "py" => ( - format!("{}.py", file_stem), - "# @generated by PolyXML Compiler (https://github.com/nth-bailey/PolyXML)\nfrom __future__ import annotations\n", - ), - "rust" | "rs" => ( - format!("{}.rs", file_stem), - "// @generated by PolyXML Compiler (https://github.com/nth-bailey/PolyXML)\nuse polyxml::ir::*;\n", - ), - "cpp" | "c++" => ( - format!("{}.hpp", file_stem), - "// @generated by PolyXML Compiler (https://github.com/nth-bailey/PolyXML)\n#pragma once\n", - ), - "java" => ( - format!("{}.java", heck::AsPascalCase(file_stem.as_ref())), - "// @generated by PolyXML Compiler (https://github.com/nth-bailey/PolyXML)\n", - ), - "ts" | "typescript" => ( - format!("{}.ts", file_stem), - "// @generated by PolyXML Compiler (https://github.com/nth-bailey/PolyXML)\n", - ), - "go" => ( - format!("{}.go", file_stem), - "// Code generated by PolyXML Compiler (https://github.com/nth-bailey/PolyXML). DO NOT EDIT.\n// @generated by PolyXML Compiler (https://github.com/nth-bailey/PolyXML)\npackage models\n", - ), - "csharp" | "c#" | "cs" => ( - format!("{}.cs", heck::AsPascalCase(file_stem.as_ref())), - "// \n// @generated by PolyXML Compiler (https://github.com/nth-bailey/PolyXML)\nnamespace Generated;\n", - ), - _ => ( - format!("{}.txt", file_stem), - "// @generated by PolyXML Compiler\n", - ), - }; - - let file_path = out_dir.join(filename); - let mut content = String::new(); - if let Some(custom) = opts.custom_header { - let trimmed = custom.trim(); - if !trimmed.is_empty() { - content.push_str(trimmed); - content.push_str("\n\n"); - } + _ => Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!("Unknown target: {lang}"), + )), } - content.push_str(header); - content.push_str(&format!( - "\n// Schema: {}\n// Target Namespace: {}\n// Total Types: {}\n", - schema_path.display(), - ir.target_namespace.as_deref().unwrap_or("None"), - ir.types.len() - )); - - fs::write(file_path, content)?; - Ok(()) } fn run_language_formatter(lang: &str, dir: &Path) { diff --git a/crates/polyxml-cli/src/options.rs b/crates/polyxml-cli/src/options.rs new file mode 100644 index 00000000..e54e9932 --- /dev/null +++ b/crates/polyxml-cli/src/options.rs @@ -0,0 +1,145 @@ +//! Shared validation and normalization for direct generation and both manifest forms. +use super::*; +use std::io::{Error, ErrorKind, Result}; + +fn invalid(message: impl Into) -> Error { + Error::new(ErrorKind::InvalidInput, message.into()) +} + +pub(crate) fn target_options(target: &config::TargetConfig) -> TargetEmitOptions<'_> { + TargetEmitOptions { + backend: target.backend.as_deref(), + features: &target.features, + slots: target.slots, + kw_only: target.kw_only, + package: target.package.as_deref().or(target.namespace.as_deref()), + mode: target + .mode + .as_deref() + .or_else(|| target.modules.filter(|v| *v).map(|_| "modules")), + zero_copy: target.zero_copy, + codecs: target.codecs, + style: target.style.as_deref(), + builder: None, + codec: None, + rkyv: None, + phf: None, + custom_header: target.custom_header.as_deref(), + } +} + +impl<'a> TargetEmitOptions<'a> { + pub(crate) fn resolve(mut self, lang: &str) -> Result { + let language = lang.to_lowercase(); + let target = match language.as_str() { + "python" | "py" => "python", + "rust" | "rs" => "rust", + "typescript" | "ts" => "typescript", + "c++" | "cpp" => "cpp", + "cs" | "c#" | "csharp" => "csharp", + "java" => "java", + "go" => "go", + _ => return Err(invalid(format!("Unknown target '{lang}'. Supported targets: python, rust, cpp, java, typescript, go, csharp."))), + }; + let supported_backends = match target { + "python" => "dataclass, pydantic", + "typescript" => "interfaces, zod, valibot, typebox", + "java" => "standard, jackson", + "csharp" => "standard, source-gen", + "cpp" => "standard, glaze", + "go" => "standard, easyjson, sonic", + _ => "standard", + }; + if let Some(backend) = self.backend { + let valid = match target { + "python" => PythonBackend::from_str_loose(backend).is_some(), + "typescript" => TypeScriptBackend::from_str_loose(backend).is_some(), + "java" => JavaBackend::from_str_loose(backend).is_some(), + "cpp" => CppBackend::from_str_loose(backend).is_some(), + "go" => GoBackend::from_str_loose(backend).is_some(), + "csharp" => matches!(backend, "standard" | "source-gen"), + _ => backend == "standard", + }; + if !valid { + return Err(invalid(format!("backend '{backend}' is not supported for target '{target}'. Supported backends: {supported_backends}."))); + } + } + + let supported_styles: &[&str] = match target { + "java" => &["record", "pojo", "class"], + "csharp" => &["record", "pojo", "class", "record-class", "record-struct"], + "python" => &["dataclass"], + _ => &[], + }; + if let Some(style) = self.style { + if !supported_styles.contains(&style) { + return Err(invalid(format!( + "style '{style}' is not supported for target '{target}'. Supported styles: {}.", + if supported_styles.is_empty() { + "none".into() + } else { + supported_styles.join(", ") + } + ))); + } + if target == "python" + && self.backend.is_some_and(|v| { + PythonBackend::from_str_loose(v) == Some(PythonBackend::Pydantic) + }) + { + return Err(invalid( + "style 'dataclass' requires the Python dataclass backend.", + )); + } + } + if let Some(mode) = self.mode { + if target != "cpp" || CppMode::from_str_loose(mode).is_none() { + return Err(invalid(format!("mode '{mode}' is not supported for target '{target}'; C++ supports header or modules."))); + } + } + if target != "python" && (self.slots.is_some() || self.kw_only.is_some()) { + return Err(invalid("slots and kw_only are Python-only options.")); + } + let supported_features: &[&str] = match target { + "rust" => &["zero-copy", "rkyv", "phf"], + "java" => &["builder", "direct-codec"], + "python" => &["slots", "kw-only"], + _ => &[], + }; + for feature in self.features { + if !supported_features.contains(&feature.as_str()) { + return Err(invalid(format!("feature '{feature}' is not supported for target '{target}'. Supported features: {}.", if supported_features.is_empty() { "none".into() } else { supported_features.join(", ") }))); + } + let option = match feature.as_str() { + "zero-copy" => &mut self.zero_copy, + "rkyv" => &mut self.rkyv, + "phf" => &mut self.phf, + "builder" => &mut self.builder, + "slots" => &mut self.slots, + "kw-only" => &mut self.kw_only, + "direct-codec" => { + self.codec = Some("direct"); + continue; + } + _ => unreachable!(), + }; + if *option == Some(false) { + return Err(invalid(format!( + "feature '{feature}' conflicts with an explicitly disabled option." + ))); + } + *option = Some(true); + } + if target == "python" + && self + .backend + .is_some_and(|v| PythonBackend::from_str_loose(v) == Some(PythonBackend::Pydantic)) + && (self.slots.is_some() || self.kw_only.is_some()) + { + return Err(invalid( + "slots and kw-only require the Python dataclass backend.", + )); + } + Ok(self) + } +} diff --git a/crates/polyxml-cli/tests/test_cli.rs b/crates/polyxml-cli/tests/test_cli.rs index d0298ab5..f76d613b 100644 --- a/crates/polyxml-cli/tests/test_cli.rs +++ b/crates/polyxml-cli/tests/test_cli.rs @@ -432,7 +432,8 @@ fn test_cli_typescript_generation() { "generate", "--lang", "ts", - "--zod", + "--backend", + "zod", "--out", zod_out.to_str().unwrap(), schema_file.to_str().unwrap(), @@ -1250,9 +1251,10 @@ fn test_cli_csharp_source_gen_and_record_struct() { schema_file.to_str().unwrap(), "--lang", "csharp", - "--source-gen", - "--record-kind", - "struct", + "--backend", + "source-gen", + "--style", + "record-struct", "-o", out_dir.to_str().unwrap(), ]) @@ -1391,7 +1393,7 @@ fn test_cli_go_easyjson_and_sonic_backend() { } #[test] -fn test_cli_rust_rkyv_flag() { +fn test_cli_rust_rkyv_feature() { let dir = tempdir().unwrap(); let schema_file = dir.path().join("event.xsd"); fs::write( @@ -1414,7 +1416,8 @@ fn test_cli_rust_rkyv_flag() { schema_file.to_str().unwrap(), "--lang", "rust", - "--rkyv", + "--feature", + "rkyv", "-o", out_dir.to_str().unwrap(), ]) @@ -1427,6 +1430,51 @@ fn test_cli_rust_rkyv_flag() { assert!(rs_content.contains("#[cfg_attr(feature = \"rkyv\", rkyv(check_bytes))]")); } +#[test] +fn test_cli_rust_phf_feature() { + let dir = tempdir().unwrap(); + let schema_file = dir.path().join("event.xsd"); + fs::write( + &schema_file, + r#" + + + + + + + "#, + ) + .unwrap(); + + let out_dir = dir.path().join("rust_out"); + let status = Command::new(env!("CARGO_BIN_EXE_polyxml")) + .args([ + "generate", + schema_file.to_str().unwrap(), + "--lang", + "rust", + "--feature", + "phf", + "-o", + out_dir.to_str().unwrap(), + ]) + .status() + .expect("Failed to execute generate with phf"); + assert!(status.success()); + + let rs_content = fs::read_to_string(out_dir.join("event.rs")).unwrap(); + assert!(rs_content.contains("enum __EventElementId")); + assert!( + rs_content.contains( + "static __EVENT_ELEMENT_DISPATCH: ::phf::Map<&'static str, __EventElementId>" + ), + "phf dispatch table missing:\n{rs_content}" + ); + assert!(rs_content.contains("(\"Id\", __EventElementId::Id)")); + assert!(rs_content.contains("Some(&__EventElementId::Id) => {")); +} + #[test] fn test_cli_build_polyxml_manifest_with_new_features() { let dir = tempdir().unwrap(); @@ -1455,8 +1503,8 @@ schemas = ["model.xsd"] [[generate]] target = "csharp" output = "cs" -source_gen = true -record_kind = "struct" +backend = "source-gen" +style = "record-struct" [[generate]] target = "ts" @@ -1471,7 +1519,7 @@ backend = "sonic" [[generate]] target = "rust" output = "rs" -rkyv = true +features = ["rkyv"] "#, ) .unwrap(); @@ -1628,9 +1676,8 @@ fn test_java_csharp_model_style_options_and_manifest() { "java", "--style", "pojo", - "--builder", - "--codec", - "direct", + "--feature", + "builder,direct-codec", "--out", java.to_str().unwrap(), ]) @@ -1647,16 +1694,9 @@ fn test_java_csharp_model_style_options_and_manifest() { assert!(java.join("ModelCodec.java").exists()); for args in [ vec!["--lang", "go", "--style", "pojo"], - vec!["--lang", "csharp", "--builder"], - vec![ - "--lang", - "csharp", - "--style", - "class", - "--record-kind", - "struct", - ], - vec!["--lang", "java", "--codec", "unknown"], + vec!["--lang", "csharp", "--feature", "builder"], + vec!["--lang", "cs", "--feature", "zero-copy"], + vec!["--lang", "java", "--backend", "unknown"], ] { let result = Command::new(env!("CARGO_BIN_EXE_polyxml")) .args([ @@ -1679,8 +1719,7 @@ schemas = ["model.xsd"] target = "java" output = "generated-java" style = "class" -builder = true -codec = "direct" +features = ["builder", "direct-codec"] [codegen.csharp] output = "generated-csharp" style = "pojo" @@ -1702,13 +1741,540 @@ style = "pojo" assert!(cs.contains("get; set;")); let parsed: WorkspaceManifest = r#"[codegen.java] style="pojo" -builder=true -codec="direct" +features=["builder", "direct-codec"] "# .parse() .unwrap(); let target = &parsed.resolved_targets()[0]; assert_eq!(target.style.as_deref(), Some("pojo")); - assert_eq!(target.builder, Some(true)); - assert_eq!(target.codec.as_deref(), Some("direct")); + assert_eq!( + target.features, + vec!["builder".to_string(), "direct-codec".to_string()] + ); +} + +#[test] +fn removed_legacy_flags_are_rejected() { + let dir = tempdir().unwrap(); + let schema = dir.path().join("model.xsd"); + fs::write(&schema, r#""#).unwrap(); + for args in [ + vec!["--zod"], + vec!["--source-gen"], + vec!["--record-kind", "struct"], + vec!["--rkyv"], + vec!["--builder"], + vec!["--codec", "direct"], + ] { + let result = Command::new(env!("CARGO_BIN_EXE_polyxml")) + .arg("generate") + .arg(&schema) + .arg("--out") + .arg(dir.path().join("out")) + .args(&args) + .output() + .unwrap(); + assert!(!result.status.success(), "{args:?}"); + let stderr = String::from_utf8_lossy(&result.stderr); + assert!(stderr.contains("unexpected argument"), "{args:?}: {stderr}"); + assert!(!dir.path().join("out").exists()); + } +} + +#[test] +fn unified_options_fail_before_schema_io_including_dry_run() { + let dir = tempdir().unwrap(); + let out = dir.path().join("output"); + for (flags, expected) in [ + ( + vec!["--lang", "python", "--backend", "jackson"], + "Supported backends: dataclass, pydantic", + ), + (vec!["--lang", "ts", "--backend", "zodd"], "backend 'zodd'"), + ( + vec!["--lang", "python", "--feature", "rkyv"], + "feature 'rkyv'", + ), + ( + vec!["--lang", "rust", "--feature", "builder"], + "feature 'builder'", + ), + ( + vec![ + "--lang", + "rust", + "--zero-copy=false", + "--feature", + "zero-copy", + ], + "conflicts", + ), + ( + vec![ + "--lang", + "python", + "--backend", + "pydantic", + "--style", + "dataclass", + ], + "requires the Python dataclass backend", + ), + ( + vec!["--lang", "python", "--style", "class"], + "Supported styles", + ), + ( + vec!["--lang", "cs", "--style", "struct"], + "Supported styles", + ), + ( + vec!["--lang", "java", "--lang", "python", "--feature", "builder"], + "feature 'builder'", + ), + (vec!["--lang", "typo"], "Unknown target"), + ] { + for dry_run in [false, true] { + let mut cmd = Command::new(env!("CARGO_BIN_EXE_polyxml")); + cmd.args(["generate", "missing.xsd"]) + .args(&flags) + .arg("--out") + .arg(&out); + if dry_run { + cmd.arg("--dry-run"); + } + let result = cmd.output().unwrap(); + let stderr = String::from_utf8_lossy(&result.stderr); + assert!(!result.status.success()); + assert!(stderr.contains(expected), "{flags:?}: {stderr}"); + assert!(!out.exists()); + } + } +} + +#[test] +fn unified_manifest_forms_generate_identical_output_and_validate_early() { + let dir = tempdir().unwrap(); + fs::write(dir.path().join("model.xsd"), r#""#).unwrap(); + let manifest = dir.path().join("polyxml.toml"); + fs::write( + &manifest, + r#" +[workspace] +schemas = ["model.xsd"] +[[generate]] +target = "java" +output = "array" +backend = "standard" +style = "pojo" +features = ["builder", "direct-codec"] +[codegen.java] +output = "table" +backend = "standard" +style = "pojo" +features = ["builder", "direct-codec"] +"#, + ) + .unwrap(); + let result = Command::new(env!("CARGO_BIN_EXE_polyxml")) + .args(["build", "--config"]) + .arg(&manifest) + .output() + .unwrap(); + assert!( + result.status.success(), + "{}", + String::from_utf8_lossy(&result.stderr) + ); + assert!(!String::from_utf8_lossy(&result.stderr).contains("deprecated")); + for file in ["Item.java", "ItemCodec.java"] { + assert_eq!( + fs::read(dir.path().join("array").join(file)).unwrap(), + fs::read(dir.path().join("table").join(file)).unwrap() + ); + } + let model = fs::read_to_string(dir.path().join("array/Item.java")).unwrap(); + assert!(model.contains("class Item")); + assert!(model.contains("builder()")); + for section in ["[[generate]]\ntarget = 'python'", "[codegen.python]"] { + fs::write(&manifest, format!("[workspace]\nschemas = ['missing.xsd']\n{section}\noutput = 'invalid'\nfeatures = ['builder']\n")).unwrap(); + for command in ["build", "generate"] { + let result = Command::new(env!("CARGO_BIN_EXE_polyxml")) + .args([command, "--dry-run", "--config"]) + .arg(&manifest) + .output() + .unwrap(); + assert!(!result.status.success()); + assert!(String::from_utf8_lossy(&result.stderr).contains("feature 'builder'")); + assert!(!dir.path().join("invalid").exists()); + } + } +} + +#[test] +fn unified_help_hides_legacy_flags() { + let result = Command::new(env!("CARGO_BIN_EXE_polyxml")) + .args(["generate", "--help"]) + .output() + .unwrap(); + let help = String::from_utf8_lossy(&result.stdout); + for flag in ["--backend", "--style", "--feature", "--zero-copy"] { + assert!(help.contains(flag)); + } + for flag in [ + "--zod", + "--source-gen", + "--rkyv", + "--builder", + "--record-kind", + ] { + assert!(!help.contains(flag)); + } +} + +#[test] +fn unified_supported_backends_and_styles_validate() { + let dir = tempdir().unwrap(); + let schema = dir.path().join("model.xsd"); + fs::write( + &schema, + r#""#, + ) + .unwrap(); + for (lang, backends, styles) in [ + ( + "python", + vec!["dataclass", "pydantic", "pydantic-v2"], + vec!["dataclass"], + ), + ("rust", vec!["standard"], vec![]), + ( + "ts", + vec!["interfaces", "zod", "valibot", "typebox", "none"], + vec![], + ), + ( + "java", + vec!["standard", "jackson"], + vec!["record", "pojo", "class"], + ), + ( + "c#", + vec!["standard", "source-gen"], + vec!["record", "record-class", "record-struct", "class", "pojo"], + ), + ("c++", vec!["standard", "glaze"], vec![]), + ("go", vec!["standard", "easyjson", "sonic"], vec![]), + ] { + for (flag, values) in [("--backend", backends), ("--style", styles)] { + for value in values { + let result = Command::new(env!("CARGO_BIN_EXE_polyxml")) + .arg("generate") + .arg(&schema) + .args(["--lang", lang, flag, value, "--dry-run"]) + .output() + .unwrap(); + assert!( + result.status.success(), + "{lang} {flag} {value}: {}", + String::from_utf8_lossy(&result.stderr) + ); + } + } + } +} + +#[test] +fn manifest_generation_does_not_ignore_cli_overrides() { + let dir = tempdir().unwrap(); + fs::write(dir.path().join("polyxml.toml"), "").unwrap(); + let result = Command::new(env!("CARGO_BIN_EXE_polyxml")) + .current_dir(dir.path()) + .args(["generate", "--feature", "builder"]) + .output() + .unwrap(); + assert!(!result.status.success()); + assert!(String::from_utf8_lossy(&result.stderr).contains("set target options in polyxml.toml")); +} + +#[test] +fn python_features_respect_manifest_false_values_and_backend() { + let dir = tempdir().unwrap(); + let schema = dir.path().join("model.xsd"); + fs::write(&schema, r#""#).unwrap(); + let manifest = dir.path().join("polyxml.toml"); + fs::write(&manifest, "[workspace]\nschemas=['model.xsd']\n[codegen.python]\noutput='generated'\nslots=false\nkw_only=false\n").unwrap(); + let result = Command::new(env!("CARGO_BIN_EXE_polyxml")) + .args(["build", "--config"]) + .arg(&manifest) + .output() + .unwrap(); + assert!(result.status.success()); + assert!(fs::read_to_string(dir.path().join("generated/model.py")) + .unwrap() + .contains("@dataclass(slots=False, kw_only=False)")); + for backend in ["dataclass", "pydantic"] { + let result = Command::new(env!("CARGO_BIN_EXE_polyxml")) + .arg("generate") + .arg(&schema) + .args([ + "--lang", + "py", + "--backend", + backend, + "--feature", + "slots", + "--feature", + "kw-only", + ]) + .arg("--out") + .arg(dir.path().join("features")) + .output() + .unwrap(); + assert_eq!(result.status.success(), backend == "dataclass"); + if backend == "dataclass" { + assert!(fs::read_to_string(dir.path().join("features/model.py")) + .unwrap() + .contains("@dataclass(slots=True, kw_only=True)")); + } else { + assert!(String::from_utf8_lossy(&result.stderr) + .contains("require the Python dataclass backend")); + } + } + fs::write( + &manifest, + "[workspace]\nschemas=['model.xsd']\n[codegen.python]\nslots=false\nfeatures=['slots']\n", + ) + .unwrap(); + let result = Command::new(env!("CARGO_BIN_EXE_polyxml")) + .args(["build", "--config"]) + .arg(&manifest) + .output() + .unwrap(); + assert!(!result.status.success()); + assert!(String::from_utf8_lossy(&result.stderr).contains("conflicts")); +} + +#[test] +fn completion_candidates_follow_target_validation() { + for (kind, words, expected) in [ + ("backend", vec!["generate"], vec!["dataclass", "pydantic"]), + ( + "backend", + vec!["generate", "--lang", "ts"], + vec!["interfaces", "zod", "valibot", "typebox", "standard"], + ), + ( + "backend", + vec!["generate", "--lang=java"], + vec!["standard", "jackson"], + ), + ( + "backend", + vec!["generate", "-lcs"], + vec!["standard", "source-gen"], + ), + ( + "backend", + vec!["generate", "-l", "java", "-l", "cpp"], + vec!["standard"], + ), + ( + "backend", + vec!["generate", "-l", "python", "-l", "java"], + vec![], + ), + ("backend", vec!["generate", "--lang", "unknown"], vec![]), + ( + "feature", + vec!["generate", "--lang", "rust"], + vec!["zero-copy", "rkyv", "phf"], + ), + ("feature", vec!["generate", "--backend=pydantic"], vec![]), + ( + "style", + vec!["generate", "--lang", "java"], + vec!["record", "pojo", "class"], + ), + ] { + let output = Command::new(env!("CARGO_BIN_EXE_polyxml")) + .args(["__complete", kind, "--"]) + .args(&words) + .output() + .unwrap(); + assert!(output.status.success()); + assert_eq!( + String::from_utf8_lossy(&output.stdout) + .lines() + .collect::>(), + expected, + "{kind}: {words:?}" + ); + } + let output = Command::new(env!("CARGO_BIN_EXE_polyxml")) + .args(["__complete", "flag", "--", "generate"]) + .output() + .unwrap(); + let flags = String::from_utf8_lossy(&output.stdout); + assert!(flags.contains("--feature")); + assert!(!flags.contains("--zod")); +} + +#[test] +fn bash_completion_filters_values_in_the_shell() { + let dir = tempdir().unwrap(); + let script = dir.path().join("polyxml.bash"); + let output = Command::new(env!("CARGO_BIN_EXE_polyxml")) + .args(["completions", "bash"]) + .output() + .unwrap(); + assert!(output.status.success()); + fs::write(&script, output.stdout).unwrap(); + for (words, expected) in [ + ( + vec!["generate", "--lang", "java", "--backend", ""], + vec!["standard", "jackson"], + ), + ( + vec!["generate", "--lang", "=", "ts", "--backend", "=", "v"], + vec!["valibot"], + ), + ( + vec!["generate", "--lang=ts", "--backend", "v"], + vec!["valibot"], + ), + ( + vec!["generate", "--lang", "rust", "--feature", "zero-copy,r"], + vec!["zero-copy,rkyv"], + ), + ( + vec!["generate", "-l", "cs", "--style=record-s"], + vec!["--style=record-struct"], + ), + ( + vec!["generate", "-l", "java", "-l", "go", "--backend", ""], + vec!["standard"], + ), + ] { + let output = Command::new("bash") + .args([ + "--noprofile", + "--norc", + "-c", + r#" +source "$1" +shift +COMP_WORDS=("$@") +COMP_CWORD=$((${#COMP_WORDS[@]} - 1)) +_polyxml +printf '%s\n' "${COMPREPLY[@]}" +"#, + "completion-test", + ]) + .arg(&script) + .arg(env!("CARGO_BIN_EXE_polyxml")) + .args(&words) + .output() + .unwrap(); + assert!( + output.status.success(), + "{}", + String::from_utf8_lossy(&output.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&output.stdout) + .lines() + .collect::>(), + expected, + "{words:?}" + ); + } +} + +#[test] +fn zsh_completion_filters_values_when_available() { + if Command::new("zsh").arg("--version").output().is_err() { + return; + } + let dir = tempdir().unwrap(); + let script = dir.path().join("_polyxml"); + let generated = Command::new(env!("CARGO_BIN_EXE_polyxml")) + .args(["completions", "zsh"]) + .output() + .unwrap(); + assert!(generated.status.success()); + fs::write(&script, generated.stdout).unwrap(); + let result = Command::new("zsh") + .args([ + "-f", + "-c", + r#" +fpath=("${1:h}" $fpath) +autoload -Uz _polyxml +compadd() { print -l -- "${values[@]}"; } +_arguments() { _polyxml_values backend; } +words=("$2" generate --lang java --backend '') +CURRENT=6 +_polyxml +"#, + "completion-test", + ]) + .arg(&script) + .arg(env!("CARGO_BIN_EXE_polyxml")) + .output() + .unwrap(); + assert!( + result.status.success(), + "{}", + String::from_utf8_lossy(&result.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&result.stdout) + .lines() + .collect::>(), + ["standard", "jackson"] + ); +} + +#[test] +fn fish_completion_filters_values_when_available() { + if Command::new("fish").arg("--version").output().is_err() { + return; + } + let dir = tempdir().unwrap(); + let script = dir.path().join("polyxml.fish"); + let generated = Command::new(env!("CARGO_BIN_EXE_polyxml")) + .args(["completions", "fish"]) + .output() + .unwrap(); + assert!(generated.status.success()); + fs::write(&script, generated.stdout).unwrap(); + let binary_dir = std::path::Path::new(env!("CARGO_BIN_EXE_polyxml")) + .parent() + .unwrap(); + let result = Command::new("fish") + .args([ + "--no-config", + "-c", + r#" +source $argv[1] +set -gx PATH $argv[2] $PATH +complete -C 'polyxml generate --lang java --backend ' +"#, + ]) + .arg(&script) + .arg(binary_dir) + .output() + .unwrap(); + assert!( + result.status.success(), + "{}", + String::from_utf8_lossy(&result.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&result.stdout) + .lines() + .collect::>(), + ["jackson", "standard"] + ); } diff --git a/crates/polyxml-core/Cargo.toml b/crates/polyxml-core/Cargo.toml index 4f0327d7..409ce052 100644 --- a/crates/polyxml-core/Cargo.toml +++ b/crates/polyxml-core/Cargo.toml @@ -20,12 +20,18 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" minijinja = { version = "2.8", features = ["builtins"] } heck = "0.5" +phf_codegen = "0.14.0" [dev-dependencies] criterion = { version = "0.8", default-features = false, features = ["cargo_bench_support"] } +phf = { version = "0.14.0", features = ["macros"] } pretty_assertions = "1.4" tempfile = "3" [[bench]] name = "core_benchmarks" harness = false + +[[bench]] +name = "tag_dispatch" +harness = false diff --git a/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs b/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs new file mode 100644 index 00000000..d87af7fe --- /dev/null +++ b/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs @@ -0,0 +1,6766 @@ +// GENERATED by scripts/gen_tag_dispatch_fixtures.py — do not edit. +// Tag lists, string-match baselines, and phf tables for the issue #49 +// tag_dispatch Criterion bench. Regenerate with: +// python3 scripts/gen_tag_dispatch_fixtures.py + +#![allow(clippy::all)] + +// --- Tier 16 (small) --- +pub const TAGS_16: [&str; 16] = [ + "AmountPositionDate", + "AuthenticationPriorityReport", + "CategoryRoutingReport", + "CurrencyMarginSub", + "InstructionClearingReport", + "InstructionInstruction1", + "ReconciliationMarginDate", + "ReconciliationReconciliationId", + "ReferenceChannel", + "ReferenceReconciliation2", + "SettlementAmountDate", + "StatusReconciliationIndicator", + "StatusSequence1", + "TransmissionSequence2", + "ValueDateClearingCode", + "VersionMarginId", +]; + +pub fn match_dispatch_16(tag: &str) -> Option { + match tag { + "AmountPositionDate" => Some(0), + "AuthenticationPriorityReport" => Some(1), + "CategoryRoutingReport" => Some(2), + "CurrencyMarginSub" => Some(3), + "InstructionClearingReport" => Some(4), + "InstructionInstruction1" => Some(5), + "ReconciliationMarginDate" => Some(6), + "ReconciliationReconciliationId" => Some(7), + "ReferenceChannel" => Some(8), + "ReferenceReconciliation2" => Some(9), + "SettlementAmountDate" => Some(10), + "StatusReconciliationIndicator" => Some(11), + "StatusSequence1" => Some(12), + "TransmissionSequence2" => Some(13), + "ValueDateClearingCode" => Some(14), + "VersionMarginId" => Some(15), + _ => None, + } +} + +pub static PHF_16: phf::Map<&'static str, u32> = phf::phf_map! { + "AmountPositionDate" => 0u32, + "AuthenticationPriorityReport" => 1u32, + "CategoryRoutingReport" => 2u32, + "CurrencyMarginSub" => 3u32, + "InstructionClearingReport" => 4u32, + "InstructionInstruction1" => 5u32, + "ReconciliationMarginDate" => 6u32, + "ReconciliationReconciliationId" => 7u32, + "ReferenceChannel" => 8u32, + "ReferenceReconciliation2" => 9u32, + "SettlementAmountDate" => 10u32, + "StatusReconciliationIndicator" => 11u32, + "StatusSequence1" => 12u32, + "TransmissionSequence2" => 13u32, + "ValueDateClearingCode" => 14u32, + "VersionMarginId" => 15u32, +}; + +// --- Tier 120 (medium) --- +pub const TAGS_120: [&str; 120] = [ + "AccountAccountSub", + "AdjustmentAdjustment", + "AdjustmentAuthenticationDate", + "AdjustmentBeneficiary", + "AdjustmentSettlementId", + "AdjustmentVersionIndicator", + "AmountAccount1", + "AmountCategoryType", + "AmountChannel", + "AmountClearingReport", + "AmountInstructionId", + "AmountSettlement2", + "AuthenticationAuthentication", + "AuthenticationMarginType", + "AuthenticationPositionIndicator", + "BeneficiaryPriorityIndicator", + "BeneficiaryStatementCount", + "BeneficiaryStatementId", + "CategoryAmount", + "CategoryChannel1", + "CategoryParty1", + "ChannelAccountType", + "ChannelChannel", + "ClearingBeneficiaryFlag", + "ClearingStatement1", + "CollateralAuthentication", + "CollateralCategory", + "CollateralPositionType", + "CollateralPriorityIndicator", + "CollateralReferenceCode", + "CollateralSettlement1", + "CollateralTransactionType", + "CollateralTransmissionReport", + "CollateralVersionSub", + "CurrencyAdjustmentIndicator", + "CurrencyChannelFlag", + "CurrencyMargin1", + "CurrencyPartyType", + "CurrencyPriorityType", + "CurrencyReference", + "CurrencyValueDateId", + "CurrencyVersionIndicator", + "IdentificationAdjustment", + "IdentificationBeneficiaryType", + "IdentificationRoutingReport", + "IdentificationSequenceCode", + "IdentificationSettlement1", + "IdentificationStatementSub", + "IdentificationVersionId", + "InstructionAdjustmentSub", + "InstructionCategory2", + "InstructionPositionId", + "InstructionPositionSub", + "InstructionPriority1", + "InstructionStatement", + "InstructionStatementFlag", + "MarginRoutingReport", + "MarginValueDateFlag", + "PartyAuthenticationId", + "PositionAdjustmentId", + "PositionCategory", + "PositionReconciliationType", + "PositionTransmissionType", + "PositionVersion1", + "PriorityChannelCode", + "PriorityStatementCount", + "PriorityStatus1", + "PriorityVersionCode", + "ReconciliationSettlementFlag", + "ReconciliationStatus", + "ReconciliationValueDateCount", + "ReferenceAdjustment", + "ReferenceClearing", + "ReferenceClearingSub", + "ReferencePartyId", + "ReferencePositionCode", + "ReferenceSequenceType", + "ReferenceVersion1", + "RemittanceCollateralReport", + "RemittancePartyIndicator", + "RemittancePriorityFlag", + "RemittanceSettlement", + "RemittanceStatus", + "RemittanceValueDateCount", + "RoutingAccountId", + "RoutingAdjustmentCount", + "RoutingCurrencyType", + "RoutingReferenceCount", + "RoutingStatusDate", + "SequenceAuthenticationIndicator", + "SequenceBeneficiaryFlag", + "SequenceIdentificationIndicator", + "SequenceMargin", + "SequencePartyFlag", + "SequencePrioritySub", + "SequenceStatusIndicator", + "SettlementAccount", + "SettlementAuthenticationIndicator", + "SettlementReconciliationFlag", + "StatusCurrency", + "StatusStatementCode", + "StatusTransmissionId", + "StatusVersion", + "TransactionCategoryReport", + "TransactionChannelReport", + "TransactionCollateral", + "TransactionPosition", + "TransmissionAdjustment2", + "TransmissionAuthentication1", + "TransmissionCollateralFlag", + "TransmissionMarginFlag", + "TransmissionPartyFlag", + "ValueDateAccountSub", + "ValueDateCollateralId", + "ValueDateCurrencySub", + "ValueDateMargin", + "ValueDateTransactionCode", + "VersionAmountReport", + "VersionCategory2", + "VersionRemittanceIndicator", +]; + +pub fn match_dispatch_120(tag: &str) -> Option { + match tag { + "AccountAccountSub" => Some(0), + "AdjustmentAdjustment" => Some(1), + "AdjustmentAuthenticationDate" => Some(2), + "AdjustmentBeneficiary" => Some(3), + "AdjustmentSettlementId" => Some(4), + "AdjustmentVersionIndicator" => Some(5), + "AmountAccount1" => Some(6), + "AmountCategoryType" => Some(7), + "AmountChannel" => Some(8), + "AmountClearingReport" => Some(9), + "AmountInstructionId" => Some(10), + "AmountSettlement2" => Some(11), + "AuthenticationAuthentication" => Some(12), + "AuthenticationMarginType" => Some(13), + "AuthenticationPositionIndicator" => Some(14), + "BeneficiaryPriorityIndicator" => Some(15), + "BeneficiaryStatementCount" => Some(16), + "BeneficiaryStatementId" => Some(17), + "CategoryAmount" => Some(18), + "CategoryChannel1" => Some(19), + "CategoryParty1" => Some(20), + "ChannelAccountType" => Some(21), + "ChannelChannel" => Some(22), + "ClearingBeneficiaryFlag" => Some(23), + "ClearingStatement1" => Some(24), + "CollateralAuthentication" => Some(25), + "CollateralCategory" => Some(26), + "CollateralPositionType" => Some(27), + "CollateralPriorityIndicator" => Some(28), + "CollateralReferenceCode" => Some(29), + "CollateralSettlement1" => Some(30), + "CollateralTransactionType" => Some(31), + "CollateralTransmissionReport" => Some(32), + "CollateralVersionSub" => Some(33), + "CurrencyAdjustmentIndicator" => Some(34), + "CurrencyChannelFlag" => Some(35), + "CurrencyMargin1" => Some(36), + "CurrencyPartyType" => Some(37), + "CurrencyPriorityType" => Some(38), + "CurrencyReference" => Some(39), + "CurrencyValueDateId" => Some(40), + "CurrencyVersionIndicator" => Some(41), + "IdentificationAdjustment" => Some(42), + "IdentificationBeneficiaryType" => Some(43), + "IdentificationRoutingReport" => Some(44), + "IdentificationSequenceCode" => Some(45), + "IdentificationSettlement1" => Some(46), + "IdentificationStatementSub" => Some(47), + "IdentificationVersionId" => Some(48), + "InstructionAdjustmentSub" => Some(49), + "InstructionCategory2" => Some(50), + "InstructionPositionId" => Some(51), + "InstructionPositionSub" => Some(52), + "InstructionPriority1" => Some(53), + "InstructionStatement" => Some(54), + "InstructionStatementFlag" => Some(55), + "MarginRoutingReport" => Some(56), + "MarginValueDateFlag" => Some(57), + "PartyAuthenticationId" => Some(58), + "PositionAdjustmentId" => Some(59), + "PositionCategory" => Some(60), + "PositionReconciliationType" => Some(61), + "PositionTransmissionType" => Some(62), + "PositionVersion1" => Some(63), + "PriorityChannelCode" => Some(64), + "PriorityStatementCount" => Some(65), + "PriorityStatus1" => Some(66), + "PriorityVersionCode" => Some(67), + "ReconciliationSettlementFlag" => Some(68), + "ReconciliationStatus" => Some(69), + "ReconciliationValueDateCount" => Some(70), + "ReferenceAdjustment" => Some(71), + "ReferenceClearing" => Some(72), + "ReferenceClearingSub" => Some(73), + "ReferencePartyId" => Some(74), + "ReferencePositionCode" => Some(75), + "ReferenceSequenceType" => Some(76), + "ReferenceVersion1" => Some(77), + "RemittanceCollateralReport" => Some(78), + "RemittancePartyIndicator" => Some(79), + "RemittancePriorityFlag" => Some(80), + "RemittanceSettlement" => Some(81), + "RemittanceStatus" => Some(82), + "RemittanceValueDateCount" => Some(83), + "RoutingAccountId" => Some(84), + "RoutingAdjustmentCount" => Some(85), + "RoutingCurrencyType" => Some(86), + "RoutingReferenceCount" => Some(87), + "RoutingStatusDate" => Some(88), + "SequenceAuthenticationIndicator" => Some(89), + "SequenceBeneficiaryFlag" => Some(90), + "SequenceIdentificationIndicator" => Some(91), + "SequenceMargin" => Some(92), + "SequencePartyFlag" => Some(93), + "SequencePrioritySub" => Some(94), + "SequenceStatusIndicator" => Some(95), + "SettlementAccount" => Some(96), + "SettlementAuthenticationIndicator" => Some(97), + "SettlementReconciliationFlag" => Some(98), + "StatusCurrency" => Some(99), + "StatusStatementCode" => Some(100), + "StatusTransmissionId" => Some(101), + "StatusVersion" => Some(102), + "TransactionCategoryReport" => Some(103), + "TransactionChannelReport" => Some(104), + "TransactionCollateral" => Some(105), + "TransactionPosition" => Some(106), + "TransmissionAdjustment2" => Some(107), + "TransmissionAuthentication1" => Some(108), + "TransmissionCollateralFlag" => Some(109), + "TransmissionMarginFlag" => Some(110), + "TransmissionPartyFlag" => Some(111), + "ValueDateAccountSub" => Some(112), + "ValueDateCollateralId" => Some(113), + "ValueDateCurrencySub" => Some(114), + "ValueDateMargin" => Some(115), + "ValueDateTransactionCode" => Some(116), + "VersionAmountReport" => Some(117), + "VersionCategory2" => Some(118), + "VersionRemittanceIndicator" => Some(119), + _ => None, + } +} + +pub static PHF_120: phf::Map<&'static str, u32> = phf::phf_map! { + "AccountAccountSub" => 0u32, + "AdjustmentAdjustment" => 1u32, + "AdjustmentAuthenticationDate" => 2u32, + "AdjustmentBeneficiary" => 3u32, + "AdjustmentSettlementId" => 4u32, + "AdjustmentVersionIndicator" => 5u32, + "AmountAccount1" => 6u32, + "AmountCategoryType" => 7u32, + "AmountChannel" => 8u32, + "AmountClearingReport" => 9u32, + "AmountInstructionId" => 10u32, + "AmountSettlement2" => 11u32, + "AuthenticationAuthentication" => 12u32, + "AuthenticationMarginType" => 13u32, + "AuthenticationPositionIndicator" => 14u32, + "BeneficiaryPriorityIndicator" => 15u32, + "BeneficiaryStatementCount" => 16u32, + "BeneficiaryStatementId" => 17u32, + "CategoryAmount" => 18u32, + "CategoryChannel1" => 19u32, + "CategoryParty1" => 20u32, + "ChannelAccountType" => 21u32, + "ChannelChannel" => 22u32, + "ClearingBeneficiaryFlag" => 23u32, + "ClearingStatement1" => 24u32, + "CollateralAuthentication" => 25u32, + "CollateralCategory" => 26u32, + "CollateralPositionType" => 27u32, + "CollateralPriorityIndicator" => 28u32, + "CollateralReferenceCode" => 29u32, + "CollateralSettlement1" => 30u32, + "CollateralTransactionType" => 31u32, + "CollateralTransmissionReport" => 32u32, + "CollateralVersionSub" => 33u32, + "CurrencyAdjustmentIndicator" => 34u32, + "CurrencyChannelFlag" => 35u32, + "CurrencyMargin1" => 36u32, + "CurrencyPartyType" => 37u32, + "CurrencyPriorityType" => 38u32, + "CurrencyReference" => 39u32, + "CurrencyValueDateId" => 40u32, + "CurrencyVersionIndicator" => 41u32, + "IdentificationAdjustment" => 42u32, + "IdentificationBeneficiaryType" => 43u32, + "IdentificationRoutingReport" => 44u32, + "IdentificationSequenceCode" => 45u32, + "IdentificationSettlement1" => 46u32, + "IdentificationStatementSub" => 47u32, + "IdentificationVersionId" => 48u32, + "InstructionAdjustmentSub" => 49u32, + "InstructionCategory2" => 50u32, + "InstructionPositionId" => 51u32, + "InstructionPositionSub" => 52u32, + "InstructionPriority1" => 53u32, + "InstructionStatement" => 54u32, + "InstructionStatementFlag" => 55u32, + "MarginRoutingReport" => 56u32, + "MarginValueDateFlag" => 57u32, + "PartyAuthenticationId" => 58u32, + "PositionAdjustmentId" => 59u32, + "PositionCategory" => 60u32, + "PositionReconciliationType" => 61u32, + "PositionTransmissionType" => 62u32, + "PositionVersion1" => 63u32, + "PriorityChannelCode" => 64u32, + "PriorityStatementCount" => 65u32, + "PriorityStatus1" => 66u32, + "PriorityVersionCode" => 67u32, + "ReconciliationSettlementFlag" => 68u32, + "ReconciliationStatus" => 69u32, + "ReconciliationValueDateCount" => 70u32, + "ReferenceAdjustment" => 71u32, + "ReferenceClearing" => 72u32, + "ReferenceClearingSub" => 73u32, + "ReferencePartyId" => 74u32, + "ReferencePositionCode" => 75u32, + "ReferenceSequenceType" => 76u32, + "ReferenceVersion1" => 77u32, + "RemittanceCollateralReport" => 78u32, + "RemittancePartyIndicator" => 79u32, + "RemittancePriorityFlag" => 80u32, + "RemittanceSettlement" => 81u32, + "RemittanceStatus" => 82u32, + "RemittanceValueDateCount" => 83u32, + "RoutingAccountId" => 84u32, + "RoutingAdjustmentCount" => 85u32, + "RoutingCurrencyType" => 86u32, + "RoutingReferenceCount" => 87u32, + "RoutingStatusDate" => 88u32, + "SequenceAuthenticationIndicator" => 89u32, + "SequenceBeneficiaryFlag" => 90u32, + "SequenceIdentificationIndicator" => 91u32, + "SequenceMargin" => 92u32, + "SequencePartyFlag" => 93u32, + "SequencePrioritySub" => 94u32, + "SequenceStatusIndicator" => 95u32, + "SettlementAccount" => 96u32, + "SettlementAuthenticationIndicator" => 97u32, + "SettlementReconciliationFlag" => 98u32, + "StatusCurrency" => 99u32, + "StatusStatementCode" => 100u32, + "StatusTransmissionId" => 101u32, + "StatusVersion" => 102u32, + "TransactionCategoryReport" => 103u32, + "TransactionChannelReport" => 104u32, + "TransactionCollateral" => 105u32, + "TransactionPosition" => 106u32, + "TransmissionAdjustment2" => 107u32, + "TransmissionAuthentication1" => 108u32, + "TransmissionCollateralFlag" => 109u32, + "TransmissionMarginFlag" => 110u32, + "TransmissionPartyFlag" => 111u32, + "ValueDateAccountSub" => 112u32, + "ValueDateCollateralId" => 113u32, + "ValueDateCurrencySub" => 114u32, + "ValueDateMargin" => 115u32, + "ValueDateTransactionCode" => 116u32, + "VersionAmountReport" => 117u32, + "VersionCategory2" => 118u32, + "VersionRemittanceIndicator" => 119u32, +}; + +// --- Tier 600 (large) --- +pub const TAGS_600: [&str; 600] = [ + "AccountAccount", + "AccountAccountId", + "AccountAmount", + "AccountAuthenticationReport", + "AccountCategoryCode", + "AccountClearingCount", + "AccountInstruction2", + "AccountInstructionDate", + "AccountMarginId", + "AccountMarginIndicator", + "AccountPartyCount", + "AccountRoutingDate", + "AccountSettlement1", + "AccountStatement", + "AccountTransactionFlag", + "AccountTransactionReport", + "AccountTransmission", + "AccountTransmissionCount", + "AdjustmentAccountId", + "AdjustmentAuthenticationIndicator", + "AdjustmentAuthenticationSub", + "AdjustmentCategoryId", + "AdjustmentCategorySub", + "AdjustmentIdentificationType", + "AdjustmentMarginFlag", + "AdjustmentPosition", + "AdjustmentPriorityFlag", + "AdjustmentReferenceFlag", + "AdjustmentRemittanceReport", + "AdjustmentRoutingCount", + "AdjustmentSequence1", + "AdjustmentSettlementDate", + "AdjustmentSettlementType", + "AdjustmentStatementDate", + "AdjustmentStatusFlag", + "AdjustmentTransactionType", + "AdjustmentTransmissionReport", + "AdjustmentValueDateDate", + "AdjustmentValueDateSub", + "AdjustmentVersionCount", + "AmountAmount", + "AmountAuthentication", + "AmountAuthenticationId", + "AmountBeneficiaryCount", + "AmountBeneficiaryReport", + "AmountCategoryReport", + "AmountChannelType", + "AmountClearing", + "AmountClearing1", + "AmountInstructionId", + "AmountMarginReport", + "AmountPriority2", + "AmountReconciliationType", + "AmountSettlementType", + "AmountStatementDate", + "AmountStatementIndicator", + "AmountTransmission", + "AmountTransmission2", + "AmountTransmissionDate", + "AmountValueDate", + "AmountValueDate1", + "AuthenticationAccountId", + "AuthenticationAdjustmentReport", + "AuthenticationAdjustmentSub", + "AuthenticationClearing", + "AuthenticationCurrencyDate", + "AuthenticationInstruction", + "AuthenticationMarginFlag", + "AuthenticationPartySub", + "AuthenticationPartyType", + "AuthenticationPosition1", + "AuthenticationPositionId", + "AuthenticationReferenceDate", + "AuthenticationRoutingReport", + "AuthenticationSettlementSub", + "AuthenticationStatus", + "AuthenticationTransaction", + "AuthenticationTransactionIndicator", + "AuthenticationTransactionType", + "AuthenticationValueDate2", + "AuthenticationValueDateReport", + "AuthenticationVersion", + "AuthenticationVersionCode", + "AuthenticationVersionIndicator", + "BeneficiaryAccountDate", + "BeneficiaryAmountReport", + "BeneficiaryAuthenticationIndicator", + "BeneficiaryChannelCount", + "BeneficiaryCurrency", + "BeneficiaryCurrencyFlag", + "BeneficiaryIdentification2", + "BeneficiaryIdentificationType", + "BeneficiaryInstruction", + "BeneficiaryPriorityCount", + "BeneficiaryRoutingCount", + "BeneficiaryRoutingId", + "BeneficiarySequenceReport", + "BeneficiarySequenceType", + "BeneficiaryStatementIndicator", + "BeneficiaryTransaction2", + "BeneficiaryValueDateCode", + "BeneficiaryVersionId", + "CategoryBeneficiaryFlag", + "CategoryCategory", + "CategoryCollateral", + "CategoryCurrency", + "CategoryCurrencyType", + "CategoryPartyDate", + "CategoryPositionCode", + "CategoryPositionIndicator", + "CategoryPriorityFlag", + "CategoryStatement", + "CategoryStatementId", + "CategoryStatusReport", + "CategoryTransaction1", + "CategoryTransactionCount", + "CategoryTransactionFlag", + "CategoryValueDateFlag", + "ChannelAccountCount", + "ChannelAdjustmentCount", + "ChannelAdjustmentSub", + "ChannelBeneficiaryDate", + "ChannelClearingFlag", + "ChannelCollateral", + "ChannelInstructionIndicator", + "ChannelPositionCount", + "ChannelPositionIndicator", + "ChannelPriorityCount", + "ChannelReconciliationCount", + "ChannelReconciliationReport", + "ChannelReferenceFlag", + "ChannelRemittance", + "ChannelRemittanceFlag", + "ChannelRemittanceId", + "ChannelRemittanceType", + "ChannelStatus", + "ChannelTransaction2", + "ChannelValueDateSub", + "ChannelVersion1", + "ChannelVersionId", + "ChannelVersionType", + "ClearingAccountReport", + "ClearingAdjustmentId", + "ClearingAmountFlag", + "ClearingBeneficiarySub", + "ClearingClearing2", + "ClearingIdentification1", + "ClearingInstructionFlag", + "ClearingPartyCount", + "ClearingPartyDate", + "ClearingPositionFlag", + "ClearingReference", + "ClearingSequence2", + "ClearingStatementId", + "ClearingTransactionCount", + "ClearingValueDate1", + "ClearingValueDateCount", + "CollateralCollateral1", + "CollateralInstructionCount", + "CollateralPrioritySub", + "CollateralReconciliationCode", + "CollateralReference1", + "CollateralReference2", + "CollateralSequenceType", + "CollateralStatusSub", + "CollateralValueDateCode", + "CollateralValueDateSub", + "CollateralVersionDate", + "CurrencyAccount", + "CurrencyAdjustmentIndicator", + "CurrencyBeneficiaryReport", + "CurrencyCategory2", + "CurrencyChannelType", + "CurrencyCollateralCode", + "CurrencyCollateralId", + "CurrencyIdentificationFlag", + "CurrencyIdentificationIndicator", + "CurrencyInstructionId", + "CurrencyMargin", + "CurrencyPriorityReport", + "CurrencyReconciliationId", + "CurrencyReference", + "CurrencySequenceReport", + "CurrencyStatementCode", + "CurrencyStatusCode", + "CurrencyStatusDate", + "CurrencyTransactionFlag", + "CurrencyTransactionReport", + "CurrencyValueDateCount", + "CurrencyValueDateSub", + "CurrencyVersionSub", + "IdentificationAdjustmentFlag", + "IdentificationChannelSub", + "IdentificationClearingDate", + "IdentificationCollateralDate", + "IdentificationCurrencyId", + "IdentificationMarginCount", + "IdentificationMarginReport", + "IdentificationMarginSub", + "IdentificationParty", + "IdentificationPartyType", + "IdentificationPriority1", + "IdentificationReconciliation", + "IdentificationReconciliationCode", + "IdentificationSettlementCount", + "IdentificationStatementFlag", + "IdentificationTransaction1", + "IdentificationTransactionDate", + "IdentificationTransmissionReport", + "InstructionAccountId", + "InstructionAdjustmentReport", + "InstructionAuthentication", + "InstructionBeneficiary", + "InstructionBeneficiary1", + "InstructionCategory1", + "InstructionChannel2", + "InstructionChannelSub", + "InstructionCurrency", + "InstructionCurrency2", + "InstructionInstructionCode", + "InstructionPriority", + "InstructionPriorityCount", + "InstructionReconciliation1", + "InstructionReconciliationIndicator", + "InstructionReference", + "InstructionReferenceDate", + "InstructionReferenceIndicator", + "InstructionRemittanceReport", + "InstructionRouting2", + "InstructionRoutingFlag", + "InstructionRoutingType", + "InstructionSequence2", + "InstructionSequenceId", + "InstructionStatusCount", + "InstructionTransmissionType", + "InstructionValueDateSub", + "InstructionVersion", + "MarginAccountReport", + "MarginAccountSub", + "MarginAmount", + "MarginAuthentication", + "MarginBeneficiaryType", + "MarginCategoryFlag", + "MarginCollateralReport", + "MarginMarginDate", + "MarginMarginIndicator", + "MarginPartyType", + "MarginPosition", + "MarginPositionCount", + "MarginPriorityId", + "MarginReference1", + "MarginRemittanceReport", + "MarginRouting", + "MarginSettlementCode", + "MarginTransactionCode", + "MarginTransmission", + "MarginValueDateId", + "PartyAccount1", + "PartyBeneficiary1", + "PartyBeneficiaryId", + "PartyCategory", + "PartyClearing", + "PartyClearingDate", + "PartyCurrencyDate", + "PartyPartyId", + "PartyPriorityCount", + "PartyReconciliationCount", + "PartyReconciliationType", + "PartyRouting", + "PartyRoutingId", + "PartyRoutingReport", + "PartySettlement1", + "PartyStatement", + "PartyStatusDate", + "PartyStatusIndicator", + "PartyTransaction2", + "PartyTransactionFlag", + "PartyTransactionReport", + "PartyTransmissionReport", + "PartyVersion1", + "PositionAdjustmentFlag", + "PositionAdjustmentSub", + "PositionAmountSub", + "PositionAuthenticationDate", + "PositionCollateralFlag", + "PositionIdentification", + "PositionIdentificationType", + "PositionInstruction", + "PositionInstructionIndicator", + "PositionMargin", + "PositionPartyFlag", + "PositionPositionFlag", + "PositionReconciliationCode", + "PositionReconciliationId", + "PositionReconciliationIndicator", + "PositionReconciliationReport", + "PositionReferenceCount", + "PositionReferenceDate", + "PositionRemittanceIndicator", + "PositionSequence2", + "PositionStatement", + "PositionStatusCode", + "PositionTransmissionId", + "PositionVersion2", + "PriorityAdjustmentSub", + "PriorityAmount2", + "PriorityAmountIndicator", + "PriorityBeneficiaryIndicator", + "PriorityCategoryId", + "PriorityChannel2", + "PriorityChannelFlag", + "PriorityChannelReport", + "PriorityClearing2", + "PriorityCollateralCode", + "PriorityCurrency", + "PriorityIdentificationIndicator", + "PriorityInstructionCode", + "PriorityMarginId", + "PriorityMarginSub", + "PriorityPartyId", + "PriorityPositionFlag", + "PriorityPriorityCount", + "PriorityReconciliationId", + "PriorityRemittanceCount", + "PriorityRemittanceFlag", + "PriorityRemittanceType", + "PriorityRouting1", + "PriorityRoutingCount", + "PriorityRoutingType", + "PrioritySequenceDate", + "PrioritySettlement", + "PrioritySettlementCode", + "PriorityStatus2", + "PriorityStatusCode", + "PriorityStatusDate", + "PriorityTransactionCount", + "PriorityTransmission2", + "PriorityTransmissionFlag", + "PriorityValueDate1", + "ReconciliationAdjustment1", + "ReconciliationAdjustmentSub", + "ReconciliationBeneficiary2", + "ReconciliationChannelFlag", + "ReconciliationCollateralType", + "ReconciliationCurrencyReport", + "ReconciliationIdentification1", + "ReconciliationInstructionType", + "ReconciliationParty", + "ReconciliationPartyDate", + "ReconciliationPartyType", + "ReconciliationPositionDate", + "ReconciliationPositionId", + "ReconciliationPriority1", + "ReconciliationReconciliationCode", + "ReconciliationReconciliationDate", + "ReconciliationRouting", + "ReconciliationRoutingCode", + "ReconciliationRoutingIndicator", + "ReconciliationSettlementSub", + "ReconciliationStatementReport", + "ReferenceAmountType", + "ReferenceBeneficiaryDate", + "ReferenceChannel", + "ReferenceClearing2", + "ReferenceClearingIndicator", + "ReferenceCurrency", + "ReferenceInstructionFlag", + "ReferencePartyCount", + "ReferencePartyType", + "ReferencePosition", + "ReferenceReconciliationType", + "ReferenceReferenceSub", + "ReferenceRemittance", + "ReferenceRemittance2", + "ReferenceRemittanceCount", + "ReferenceSequenceSub", + "ReferenceStatement", + "ReferenceStatus", + "ReferenceTransactionDate", + "ReferenceTransmissionSub", + "ReferenceVersion", + "RemittanceAdjustmentType", + "RemittanceCategoryFlag", + "RemittanceCategoryType", + "RemittanceCollateral2", + "RemittanceCollateralFlag", + "RemittanceIdentification1", + "RemittanceInstructionType", + "RemittanceMargin", + "RemittancePositionCount", + "RemittancePriority", + "RemittancePriorityCount", + "RemittanceRemittance", + "RemittanceSequenceDate", + "RemittanceStatusCount", + "RemittanceStatusIndicator", + "RemittanceTransmission2", + "RemittanceTransmissionCode", + "RemittanceTransmissionIndicator", + "RemittanceVersionDate", + "RoutingAccountDate", + "RoutingAmount1", + "RoutingAmountCode", + "RoutingAuthenticationIndicator", + "RoutingCategoryCode", + "RoutingCategoryType", + "RoutingClearingDate", + "RoutingCurrencySub", + "RoutingIdentificationCode", + "RoutingPartyType", + "RoutingPriority2", + "RoutingPriorityFlag", + "RoutingPriorityId", + "RoutingReconciliationSub", + "RoutingReferenceDate", + "RoutingRemittance", + "RoutingRemittance2", + "RoutingRouting", + "RoutingRoutingDate", + "RoutingRoutingId", + "RoutingRoutingIndicator", + "RoutingSequenceType", + "RoutingSettlement1", + "RoutingTransactionReport", + "RoutingTransmissionIndicator", + "RoutingValueDateCode", + "RoutingValueDateCount", + "RoutingVersionIndicator", + "RoutingVersionType", + "SequenceAccount2", + "SequenceAccountType", + "SequenceAdjustmentDate", + "SequenceAdjustmentIndicator", + "SequenceAmount1", + "SequenceAmountType", + "SequenceBeneficiary1", + "SequenceBeneficiaryCount", + "SequenceChannel", + "SequenceParty", + "SequencePartyId", + "SequenceReconciliationId", + "SequenceReconciliationReport", + "SequenceReferenceId", + "SequenceStatus", + "SequenceTransaction1", + "SequenceTransactionDate", + "SequenceTransactionFlag", + "SequenceTransmission", + "SequenceTransmissionDate", + "SettlementAccount", + "SettlementBeneficiaryDate", + "SettlementBeneficiaryType", + "SettlementCategoryCount", + "SettlementChannel", + "SettlementInstructionCode", + "SettlementPositionIndicator", + "SettlementReferenceReport", + "SettlementRemittanceIndicator", + "SettlementRoutingReport", + "SettlementSequenceReport", + "SettlementStatementType", + "SettlementStatusCount", + "SettlementTransmissionCode", + "SettlementTransmissionFlag", + "SettlementVersion", + "StatementAccount", + "StatementAuthentication", + "StatementAuthenticationFlag", + "StatementBeneficiary", + "StatementBeneficiarySub", + "StatementChannelReport", + "StatementMargin", + "StatementPositionType", + "StatementPriority2", + "StatementReference", + "StatementReferenceCode", + "StatementReferenceIndicator", + "StatementRemittanceFlag", + "StatementRoutingCode", + "StatementSequenceId", + "StatementSettlement", + "StatementSettlement2", + "StatementValueDateCount", + "StatementValueDateReport", + "StatusAccountType", + "StatusAmount2", + "StatusAuthentication", + "StatusAuthentication2", + "StatusAuthenticationId", + "StatusAuthenticationIndicator", + "StatusCategoryIndicator", + "StatusChannelFlag", + "StatusClearingReport", + "StatusCurrency2", + "StatusInstructionReport", + "StatusPartyIndicator", + "StatusPositionSub", + "StatusPriority1", + "StatusPriorityCount", + "StatusPriorityIndicator", + "StatusReconciliationReport", + "StatusRouting", + "StatusRoutingCode", + "StatusRoutingFlag", + "StatusTransmission", + "StatusTransmissionType", + "StatusValueDateSub", + "StatusVersionDate", + "TransactionAuthentication", + "TransactionAuthentication1", + "TransactionAuthenticationId", + "TransactionClearing", + "TransactionClearingCode", + "TransactionCollateralId", + "TransactionCurrencyIndicator", + "TransactionInstructionCode", + "TransactionPartySub", + "TransactionPosition", + "TransactionPriorityCode", + "TransactionReference2", + "TransactionRouting1", + "TransactionRoutingIndicator", + "TransactionStatementDate", + "TransactionTransactionId", + "TransactionTransmissionType", + "TransactionVersion1", + "TransactionVersionType", + "TransmissionAccountId", + "TransmissionAdjustmentSub", + "TransmissionAmountFlag", + "TransmissionBeneficiaryReport", + "TransmissionCategoryIndicator", + "TransmissionCollateral1", + "TransmissionIdentificationFlag", + "TransmissionInstructionId", + "TransmissionMargin2", + "TransmissionMarginDate", + "TransmissionPositionType", + "TransmissionPriorityCode", + "TransmissionPriorityIndicator", + "TransmissionReconciliation", + "TransmissionReconciliationCount", + "TransmissionReconciliationDate", + "TransmissionRemittanceSub", + "TransmissionRoutingCount", + "TransmissionRoutingId", + "TransmissionSequenceDate", + "TransmissionStatementId", + "TransmissionStatusCode", + "TransmissionTransmission", + "TransmissionTransmissionFlag", + "TransmissionVersion", + "ValueDateAccountFlag", + "ValueDateAdjustmentReport", + "ValueDateChannel1", + "ValueDateChannelFlag", + "ValueDateChannelId", + "ValueDateClearingDate", + "ValueDateClearingReport", + "ValueDateCurrencyId", + "ValueDateCurrencyType", + "ValueDateIdentification2", + "ValueDateIdentificationIndicator", + "ValueDateInstruction", + "ValueDateInstruction1", + "ValueDateMarginType", + "ValueDatePosition2", + "ValueDatePositionCount", + "ValueDateReconciliationCount", + "ValueDateReconciliationId", + "ValueDateReferenceFlag", + "ValueDateRemittanceCount", + "ValueDateRemittanceIndicator", + "ValueDateRoutingFlag", + "ValueDateSequenceId", + "ValueDateSettlementCount", + "ValueDateStatementDate", + "ValueDateTransmission", + "ValueDateTransmissionCode", + "ValueDateValueDateReport", + "ValueDateVersionReport", + "VersionAccount", + "VersionAdjustment", + "VersionAdjustment1", + "VersionAuthenticationCode", + "VersionBeneficiaryCount", + "VersionBeneficiaryFlag", + "VersionCategoryFlag", + "VersionClearingFlag", + "VersionMargin", + "VersionMarginFlag", + "VersionMarginReport", + "VersionReconciliation1", + "VersionReferenceDate", + "VersionReferenceIndicator", + "VersionRemittance", + "VersionStatusCode", + "VersionStatusType", + "VersionTransactionId", + "VersionTransmissionCount", +]; + +pub fn match_dispatch_600(tag: &str) -> Option { + match tag { + "AccountAccount" => Some(0), + "AccountAccountId" => Some(1), + "AccountAmount" => Some(2), + "AccountAuthenticationReport" => Some(3), + "AccountCategoryCode" => Some(4), + "AccountClearingCount" => Some(5), + "AccountInstruction2" => Some(6), + "AccountInstructionDate" => Some(7), + "AccountMarginId" => Some(8), + "AccountMarginIndicator" => Some(9), + "AccountPartyCount" => Some(10), + "AccountRoutingDate" => Some(11), + "AccountSettlement1" => Some(12), + "AccountStatement" => Some(13), + "AccountTransactionFlag" => Some(14), + "AccountTransactionReport" => Some(15), + "AccountTransmission" => Some(16), + "AccountTransmissionCount" => Some(17), + "AdjustmentAccountId" => Some(18), + "AdjustmentAuthenticationIndicator" => Some(19), + "AdjustmentAuthenticationSub" => Some(20), + "AdjustmentCategoryId" => Some(21), + "AdjustmentCategorySub" => Some(22), + "AdjustmentIdentificationType" => Some(23), + "AdjustmentMarginFlag" => Some(24), + "AdjustmentPosition" => Some(25), + "AdjustmentPriorityFlag" => Some(26), + "AdjustmentReferenceFlag" => Some(27), + "AdjustmentRemittanceReport" => Some(28), + "AdjustmentRoutingCount" => Some(29), + "AdjustmentSequence1" => Some(30), + "AdjustmentSettlementDate" => Some(31), + "AdjustmentSettlementType" => Some(32), + "AdjustmentStatementDate" => Some(33), + "AdjustmentStatusFlag" => Some(34), + "AdjustmentTransactionType" => Some(35), + "AdjustmentTransmissionReport" => Some(36), + "AdjustmentValueDateDate" => Some(37), + "AdjustmentValueDateSub" => Some(38), + "AdjustmentVersionCount" => Some(39), + "AmountAmount" => Some(40), + "AmountAuthentication" => Some(41), + "AmountAuthenticationId" => Some(42), + "AmountBeneficiaryCount" => Some(43), + "AmountBeneficiaryReport" => Some(44), + "AmountCategoryReport" => Some(45), + "AmountChannelType" => Some(46), + "AmountClearing" => Some(47), + "AmountClearing1" => Some(48), + "AmountInstructionId" => Some(49), + "AmountMarginReport" => Some(50), + "AmountPriority2" => Some(51), + "AmountReconciliationType" => Some(52), + "AmountSettlementType" => Some(53), + "AmountStatementDate" => Some(54), + "AmountStatementIndicator" => Some(55), + "AmountTransmission" => Some(56), + "AmountTransmission2" => Some(57), + "AmountTransmissionDate" => Some(58), + "AmountValueDate" => Some(59), + "AmountValueDate1" => Some(60), + "AuthenticationAccountId" => Some(61), + "AuthenticationAdjustmentReport" => Some(62), + "AuthenticationAdjustmentSub" => Some(63), + "AuthenticationClearing" => Some(64), + "AuthenticationCurrencyDate" => Some(65), + "AuthenticationInstruction" => Some(66), + "AuthenticationMarginFlag" => Some(67), + "AuthenticationPartySub" => Some(68), + "AuthenticationPartyType" => Some(69), + "AuthenticationPosition1" => Some(70), + "AuthenticationPositionId" => Some(71), + "AuthenticationReferenceDate" => Some(72), + "AuthenticationRoutingReport" => Some(73), + "AuthenticationSettlementSub" => Some(74), + "AuthenticationStatus" => Some(75), + "AuthenticationTransaction" => Some(76), + "AuthenticationTransactionIndicator" => Some(77), + "AuthenticationTransactionType" => Some(78), + "AuthenticationValueDate2" => Some(79), + "AuthenticationValueDateReport" => Some(80), + "AuthenticationVersion" => Some(81), + "AuthenticationVersionCode" => Some(82), + "AuthenticationVersionIndicator" => Some(83), + "BeneficiaryAccountDate" => Some(84), + "BeneficiaryAmountReport" => Some(85), + "BeneficiaryAuthenticationIndicator" => Some(86), + "BeneficiaryChannelCount" => Some(87), + "BeneficiaryCurrency" => Some(88), + "BeneficiaryCurrencyFlag" => Some(89), + "BeneficiaryIdentification2" => Some(90), + "BeneficiaryIdentificationType" => Some(91), + "BeneficiaryInstruction" => Some(92), + "BeneficiaryPriorityCount" => Some(93), + "BeneficiaryRoutingCount" => Some(94), + "BeneficiaryRoutingId" => Some(95), + "BeneficiarySequenceReport" => Some(96), + "BeneficiarySequenceType" => Some(97), + "BeneficiaryStatementIndicator" => Some(98), + "BeneficiaryTransaction2" => Some(99), + "BeneficiaryValueDateCode" => Some(100), + "BeneficiaryVersionId" => Some(101), + "CategoryBeneficiaryFlag" => Some(102), + "CategoryCategory" => Some(103), + "CategoryCollateral" => Some(104), + "CategoryCurrency" => Some(105), + "CategoryCurrencyType" => Some(106), + "CategoryPartyDate" => Some(107), + "CategoryPositionCode" => Some(108), + "CategoryPositionIndicator" => Some(109), + "CategoryPriorityFlag" => Some(110), + "CategoryStatement" => Some(111), + "CategoryStatementId" => Some(112), + "CategoryStatusReport" => Some(113), + "CategoryTransaction1" => Some(114), + "CategoryTransactionCount" => Some(115), + "CategoryTransactionFlag" => Some(116), + "CategoryValueDateFlag" => Some(117), + "ChannelAccountCount" => Some(118), + "ChannelAdjustmentCount" => Some(119), + "ChannelAdjustmentSub" => Some(120), + "ChannelBeneficiaryDate" => Some(121), + "ChannelClearingFlag" => Some(122), + "ChannelCollateral" => Some(123), + "ChannelInstructionIndicator" => Some(124), + "ChannelPositionCount" => Some(125), + "ChannelPositionIndicator" => Some(126), + "ChannelPriorityCount" => Some(127), + "ChannelReconciliationCount" => Some(128), + "ChannelReconciliationReport" => Some(129), + "ChannelReferenceFlag" => Some(130), + "ChannelRemittance" => Some(131), + "ChannelRemittanceFlag" => Some(132), + "ChannelRemittanceId" => Some(133), + "ChannelRemittanceType" => Some(134), + "ChannelStatus" => Some(135), + "ChannelTransaction2" => Some(136), + "ChannelValueDateSub" => Some(137), + "ChannelVersion1" => Some(138), + "ChannelVersionId" => Some(139), + "ChannelVersionType" => Some(140), + "ClearingAccountReport" => Some(141), + "ClearingAdjustmentId" => Some(142), + "ClearingAmountFlag" => Some(143), + "ClearingBeneficiarySub" => Some(144), + "ClearingClearing2" => Some(145), + "ClearingIdentification1" => Some(146), + "ClearingInstructionFlag" => Some(147), + "ClearingPartyCount" => Some(148), + "ClearingPartyDate" => Some(149), + "ClearingPositionFlag" => Some(150), + "ClearingReference" => Some(151), + "ClearingSequence2" => Some(152), + "ClearingStatementId" => Some(153), + "ClearingTransactionCount" => Some(154), + "ClearingValueDate1" => Some(155), + "ClearingValueDateCount" => Some(156), + "CollateralCollateral1" => Some(157), + "CollateralInstructionCount" => Some(158), + "CollateralPrioritySub" => Some(159), + "CollateralReconciliationCode" => Some(160), + "CollateralReference1" => Some(161), + "CollateralReference2" => Some(162), + "CollateralSequenceType" => Some(163), + "CollateralStatusSub" => Some(164), + "CollateralValueDateCode" => Some(165), + "CollateralValueDateSub" => Some(166), + "CollateralVersionDate" => Some(167), + "CurrencyAccount" => Some(168), + "CurrencyAdjustmentIndicator" => Some(169), + "CurrencyBeneficiaryReport" => Some(170), + "CurrencyCategory2" => Some(171), + "CurrencyChannelType" => Some(172), + "CurrencyCollateralCode" => Some(173), + "CurrencyCollateralId" => Some(174), + "CurrencyIdentificationFlag" => Some(175), + "CurrencyIdentificationIndicator" => Some(176), + "CurrencyInstructionId" => Some(177), + "CurrencyMargin" => Some(178), + "CurrencyPriorityReport" => Some(179), + "CurrencyReconciliationId" => Some(180), + "CurrencyReference" => Some(181), + "CurrencySequenceReport" => Some(182), + "CurrencyStatementCode" => Some(183), + "CurrencyStatusCode" => Some(184), + "CurrencyStatusDate" => Some(185), + "CurrencyTransactionFlag" => Some(186), + "CurrencyTransactionReport" => Some(187), + "CurrencyValueDateCount" => Some(188), + "CurrencyValueDateSub" => Some(189), + "CurrencyVersionSub" => Some(190), + "IdentificationAdjustmentFlag" => Some(191), + "IdentificationChannelSub" => Some(192), + "IdentificationClearingDate" => Some(193), + "IdentificationCollateralDate" => Some(194), + "IdentificationCurrencyId" => Some(195), + "IdentificationMarginCount" => Some(196), + "IdentificationMarginReport" => Some(197), + "IdentificationMarginSub" => Some(198), + "IdentificationParty" => Some(199), + "IdentificationPartyType" => Some(200), + "IdentificationPriority1" => Some(201), + "IdentificationReconciliation" => Some(202), + "IdentificationReconciliationCode" => Some(203), + "IdentificationSettlementCount" => Some(204), + "IdentificationStatementFlag" => Some(205), + "IdentificationTransaction1" => Some(206), + "IdentificationTransactionDate" => Some(207), + "IdentificationTransmissionReport" => Some(208), + "InstructionAccountId" => Some(209), + "InstructionAdjustmentReport" => Some(210), + "InstructionAuthentication" => Some(211), + "InstructionBeneficiary" => Some(212), + "InstructionBeneficiary1" => Some(213), + "InstructionCategory1" => Some(214), + "InstructionChannel2" => Some(215), + "InstructionChannelSub" => Some(216), + "InstructionCurrency" => Some(217), + "InstructionCurrency2" => Some(218), + "InstructionInstructionCode" => Some(219), + "InstructionPriority" => Some(220), + "InstructionPriorityCount" => Some(221), + "InstructionReconciliation1" => Some(222), + "InstructionReconciliationIndicator" => Some(223), + "InstructionReference" => Some(224), + "InstructionReferenceDate" => Some(225), + "InstructionReferenceIndicator" => Some(226), + "InstructionRemittanceReport" => Some(227), + "InstructionRouting2" => Some(228), + "InstructionRoutingFlag" => Some(229), + "InstructionRoutingType" => Some(230), + "InstructionSequence2" => Some(231), + "InstructionSequenceId" => Some(232), + "InstructionStatusCount" => Some(233), + "InstructionTransmissionType" => Some(234), + "InstructionValueDateSub" => Some(235), + "InstructionVersion" => Some(236), + "MarginAccountReport" => Some(237), + "MarginAccountSub" => Some(238), + "MarginAmount" => Some(239), + "MarginAuthentication" => Some(240), + "MarginBeneficiaryType" => Some(241), + "MarginCategoryFlag" => Some(242), + "MarginCollateralReport" => Some(243), + "MarginMarginDate" => Some(244), + "MarginMarginIndicator" => Some(245), + "MarginPartyType" => Some(246), + "MarginPosition" => Some(247), + "MarginPositionCount" => Some(248), + "MarginPriorityId" => Some(249), + "MarginReference1" => Some(250), + "MarginRemittanceReport" => Some(251), + "MarginRouting" => Some(252), + "MarginSettlementCode" => Some(253), + "MarginTransactionCode" => Some(254), + "MarginTransmission" => Some(255), + "MarginValueDateId" => Some(256), + "PartyAccount1" => Some(257), + "PartyBeneficiary1" => Some(258), + "PartyBeneficiaryId" => Some(259), + "PartyCategory" => Some(260), + "PartyClearing" => Some(261), + "PartyClearingDate" => Some(262), + "PartyCurrencyDate" => Some(263), + "PartyPartyId" => Some(264), + "PartyPriorityCount" => Some(265), + "PartyReconciliationCount" => Some(266), + "PartyReconciliationType" => Some(267), + "PartyRouting" => Some(268), + "PartyRoutingId" => Some(269), + "PartyRoutingReport" => Some(270), + "PartySettlement1" => Some(271), + "PartyStatement" => Some(272), + "PartyStatusDate" => Some(273), + "PartyStatusIndicator" => Some(274), + "PartyTransaction2" => Some(275), + "PartyTransactionFlag" => Some(276), + "PartyTransactionReport" => Some(277), + "PartyTransmissionReport" => Some(278), + "PartyVersion1" => Some(279), + "PositionAdjustmentFlag" => Some(280), + "PositionAdjustmentSub" => Some(281), + "PositionAmountSub" => Some(282), + "PositionAuthenticationDate" => Some(283), + "PositionCollateralFlag" => Some(284), + "PositionIdentification" => Some(285), + "PositionIdentificationType" => Some(286), + "PositionInstruction" => Some(287), + "PositionInstructionIndicator" => Some(288), + "PositionMargin" => Some(289), + "PositionPartyFlag" => Some(290), + "PositionPositionFlag" => Some(291), + "PositionReconciliationCode" => Some(292), + "PositionReconciliationId" => Some(293), + "PositionReconciliationIndicator" => Some(294), + "PositionReconciliationReport" => Some(295), + "PositionReferenceCount" => Some(296), + "PositionReferenceDate" => Some(297), + "PositionRemittanceIndicator" => Some(298), + "PositionSequence2" => Some(299), + "PositionStatement" => Some(300), + "PositionStatusCode" => Some(301), + "PositionTransmissionId" => Some(302), + "PositionVersion2" => Some(303), + "PriorityAdjustmentSub" => Some(304), + "PriorityAmount2" => Some(305), + "PriorityAmountIndicator" => Some(306), + "PriorityBeneficiaryIndicator" => Some(307), + "PriorityCategoryId" => Some(308), + "PriorityChannel2" => Some(309), + "PriorityChannelFlag" => Some(310), + "PriorityChannelReport" => Some(311), + "PriorityClearing2" => Some(312), + "PriorityCollateralCode" => Some(313), + "PriorityCurrency" => Some(314), + "PriorityIdentificationIndicator" => Some(315), + "PriorityInstructionCode" => Some(316), + "PriorityMarginId" => Some(317), + "PriorityMarginSub" => Some(318), + "PriorityPartyId" => Some(319), + "PriorityPositionFlag" => Some(320), + "PriorityPriorityCount" => Some(321), + "PriorityReconciliationId" => Some(322), + "PriorityRemittanceCount" => Some(323), + "PriorityRemittanceFlag" => Some(324), + "PriorityRemittanceType" => Some(325), + "PriorityRouting1" => Some(326), + "PriorityRoutingCount" => Some(327), + "PriorityRoutingType" => Some(328), + "PrioritySequenceDate" => Some(329), + "PrioritySettlement" => Some(330), + "PrioritySettlementCode" => Some(331), + "PriorityStatus2" => Some(332), + "PriorityStatusCode" => Some(333), + "PriorityStatusDate" => Some(334), + "PriorityTransactionCount" => Some(335), + "PriorityTransmission2" => Some(336), + "PriorityTransmissionFlag" => Some(337), + "PriorityValueDate1" => Some(338), + "ReconciliationAdjustment1" => Some(339), + "ReconciliationAdjustmentSub" => Some(340), + "ReconciliationBeneficiary2" => Some(341), + "ReconciliationChannelFlag" => Some(342), + "ReconciliationCollateralType" => Some(343), + "ReconciliationCurrencyReport" => Some(344), + "ReconciliationIdentification1" => Some(345), + "ReconciliationInstructionType" => Some(346), + "ReconciliationParty" => Some(347), + "ReconciliationPartyDate" => Some(348), + "ReconciliationPartyType" => Some(349), + "ReconciliationPositionDate" => Some(350), + "ReconciliationPositionId" => Some(351), + "ReconciliationPriority1" => Some(352), + "ReconciliationReconciliationCode" => Some(353), + "ReconciliationReconciliationDate" => Some(354), + "ReconciliationRouting" => Some(355), + "ReconciliationRoutingCode" => Some(356), + "ReconciliationRoutingIndicator" => Some(357), + "ReconciliationSettlementSub" => Some(358), + "ReconciliationStatementReport" => Some(359), + "ReferenceAmountType" => Some(360), + "ReferenceBeneficiaryDate" => Some(361), + "ReferenceChannel" => Some(362), + "ReferenceClearing2" => Some(363), + "ReferenceClearingIndicator" => Some(364), + "ReferenceCurrency" => Some(365), + "ReferenceInstructionFlag" => Some(366), + "ReferencePartyCount" => Some(367), + "ReferencePartyType" => Some(368), + "ReferencePosition" => Some(369), + "ReferenceReconciliationType" => Some(370), + "ReferenceReferenceSub" => Some(371), + "ReferenceRemittance" => Some(372), + "ReferenceRemittance2" => Some(373), + "ReferenceRemittanceCount" => Some(374), + "ReferenceSequenceSub" => Some(375), + "ReferenceStatement" => Some(376), + "ReferenceStatus" => Some(377), + "ReferenceTransactionDate" => Some(378), + "ReferenceTransmissionSub" => Some(379), + "ReferenceVersion" => Some(380), + "RemittanceAdjustmentType" => Some(381), + "RemittanceCategoryFlag" => Some(382), + "RemittanceCategoryType" => Some(383), + "RemittanceCollateral2" => Some(384), + "RemittanceCollateralFlag" => Some(385), + "RemittanceIdentification1" => Some(386), + "RemittanceInstructionType" => Some(387), + "RemittanceMargin" => Some(388), + "RemittancePositionCount" => Some(389), + "RemittancePriority" => Some(390), + "RemittancePriorityCount" => Some(391), + "RemittanceRemittance" => Some(392), + "RemittanceSequenceDate" => Some(393), + "RemittanceStatusCount" => Some(394), + "RemittanceStatusIndicator" => Some(395), + "RemittanceTransmission2" => Some(396), + "RemittanceTransmissionCode" => Some(397), + "RemittanceTransmissionIndicator" => Some(398), + "RemittanceVersionDate" => Some(399), + "RoutingAccountDate" => Some(400), + "RoutingAmount1" => Some(401), + "RoutingAmountCode" => Some(402), + "RoutingAuthenticationIndicator" => Some(403), + "RoutingCategoryCode" => Some(404), + "RoutingCategoryType" => Some(405), + "RoutingClearingDate" => Some(406), + "RoutingCurrencySub" => Some(407), + "RoutingIdentificationCode" => Some(408), + "RoutingPartyType" => Some(409), + "RoutingPriority2" => Some(410), + "RoutingPriorityFlag" => Some(411), + "RoutingPriorityId" => Some(412), + "RoutingReconciliationSub" => Some(413), + "RoutingReferenceDate" => Some(414), + "RoutingRemittance" => Some(415), + "RoutingRemittance2" => Some(416), + "RoutingRouting" => Some(417), + "RoutingRoutingDate" => Some(418), + "RoutingRoutingId" => Some(419), + "RoutingRoutingIndicator" => Some(420), + "RoutingSequenceType" => Some(421), + "RoutingSettlement1" => Some(422), + "RoutingTransactionReport" => Some(423), + "RoutingTransmissionIndicator" => Some(424), + "RoutingValueDateCode" => Some(425), + "RoutingValueDateCount" => Some(426), + "RoutingVersionIndicator" => Some(427), + "RoutingVersionType" => Some(428), + "SequenceAccount2" => Some(429), + "SequenceAccountType" => Some(430), + "SequenceAdjustmentDate" => Some(431), + "SequenceAdjustmentIndicator" => Some(432), + "SequenceAmount1" => Some(433), + "SequenceAmountType" => Some(434), + "SequenceBeneficiary1" => Some(435), + "SequenceBeneficiaryCount" => Some(436), + "SequenceChannel" => Some(437), + "SequenceParty" => Some(438), + "SequencePartyId" => Some(439), + "SequenceReconciliationId" => Some(440), + "SequenceReconciliationReport" => Some(441), + "SequenceReferenceId" => Some(442), + "SequenceStatus" => Some(443), + "SequenceTransaction1" => Some(444), + "SequenceTransactionDate" => Some(445), + "SequenceTransactionFlag" => Some(446), + "SequenceTransmission" => Some(447), + "SequenceTransmissionDate" => Some(448), + "SettlementAccount" => Some(449), + "SettlementBeneficiaryDate" => Some(450), + "SettlementBeneficiaryType" => Some(451), + "SettlementCategoryCount" => Some(452), + "SettlementChannel" => Some(453), + "SettlementInstructionCode" => Some(454), + "SettlementPositionIndicator" => Some(455), + "SettlementReferenceReport" => Some(456), + "SettlementRemittanceIndicator" => Some(457), + "SettlementRoutingReport" => Some(458), + "SettlementSequenceReport" => Some(459), + "SettlementStatementType" => Some(460), + "SettlementStatusCount" => Some(461), + "SettlementTransmissionCode" => Some(462), + "SettlementTransmissionFlag" => Some(463), + "SettlementVersion" => Some(464), + "StatementAccount" => Some(465), + "StatementAuthentication" => Some(466), + "StatementAuthenticationFlag" => Some(467), + "StatementBeneficiary" => Some(468), + "StatementBeneficiarySub" => Some(469), + "StatementChannelReport" => Some(470), + "StatementMargin" => Some(471), + "StatementPositionType" => Some(472), + "StatementPriority2" => Some(473), + "StatementReference" => Some(474), + "StatementReferenceCode" => Some(475), + "StatementReferenceIndicator" => Some(476), + "StatementRemittanceFlag" => Some(477), + "StatementRoutingCode" => Some(478), + "StatementSequenceId" => Some(479), + "StatementSettlement" => Some(480), + "StatementSettlement2" => Some(481), + "StatementValueDateCount" => Some(482), + "StatementValueDateReport" => Some(483), + "StatusAccountType" => Some(484), + "StatusAmount2" => Some(485), + "StatusAuthentication" => Some(486), + "StatusAuthentication2" => Some(487), + "StatusAuthenticationId" => Some(488), + "StatusAuthenticationIndicator" => Some(489), + "StatusCategoryIndicator" => Some(490), + "StatusChannelFlag" => Some(491), + "StatusClearingReport" => Some(492), + "StatusCurrency2" => Some(493), + "StatusInstructionReport" => Some(494), + "StatusPartyIndicator" => Some(495), + "StatusPositionSub" => Some(496), + "StatusPriority1" => Some(497), + "StatusPriorityCount" => Some(498), + "StatusPriorityIndicator" => Some(499), + "StatusReconciliationReport" => Some(500), + "StatusRouting" => Some(501), + "StatusRoutingCode" => Some(502), + "StatusRoutingFlag" => Some(503), + "StatusTransmission" => Some(504), + "StatusTransmissionType" => Some(505), + "StatusValueDateSub" => Some(506), + "StatusVersionDate" => Some(507), + "TransactionAuthentication" => Some(508), + "TransactionAuthentication1" => Some(509), + "TransactionAuthenticationId" => Some(510), + "TransactionClearing" => Some(511), + "TransactionClearingCode" => Some(512), + "TransactionCollateralId" => Some(513), + "TransactionCurrencyIndicator" => Some(514), + "TransactionInstructionCode" => Some(515), + "TransactionPartySub" => Some(516), + "TransactionPosition" => Some(517), + "TransactionPriorityCode" => Some(518), + "TransactionReference2" => Some(519), + "TransactionRouting1" => Some(520), + "TransactionRoutingIndicator" => Some(521), + "TransactionStatementDate" => Some(522), + "TransactionTransactionId" => Some(523), + "TransactionTransmissionType" => Some(524), + "TransactionVersion1" => Some(525), + "TransactionVersionType" => Some(526), + "TransmissionAccountId" => Some(527), + "TransmissionAdjustmentSub" => Some(528), + "TransmissionAmountFlag" => Some(529), + "TransmissionBeneficiaryReport" => Some(530), + "TransmissionCategoryIndicator" => Some(531), + "TransmissionCollateral1" => Some(532), + "TransmissionIdentificationFlag" => Some(533), + "TransmissionInstructionId" => Some(534), + "TransmissionMargin2" => Some(535), + "TransmissionMarginDate" => Some(536), + "TransmissionPositionType" => Some(537), + "TransmissionPriorityCode" => Some(538), + "TransmissionPriorityIndicator" => Some(539), + "TransmissionReconciliation" => Some(540), + "TransmissionReconciliationCount" => Some(541), + "TransmissionReconciliationDate" => Some(542), + "TransmissionRemittanceSub" => Some(543), + "TransmissionRoutingCount" => Some(544), + "TransmissionRoutingId" => Some(545), + "TransmissionSequenceDate" => Some(546), + "TransmissionStatementId" => Some(547), + "TransmissionStatusCode" => Some(548), + "TransmissionTransmission" => Some(549), + "TransmissionTransmissionFlag" => Some(550), + "TransmissionVersion" => Some(551), + "ValueDateAccountFlag" => Some(552), + "ValueDateAdjustmentReport" => Some(553), + "ValueDateChannel1" => Some(554), + "ValueDateChannelFlag" => Some(555), + "ValueDateChannelId" => Some(556), + "ValueDateClearingDate" => Some(557), + "ValueDateClearingReport" => Some(558), + "ValueDateCurrencyId" => Some(559), + "ValueDateCurrencyType" => Some(560), + "ValueDateIdentification2" => Some(561), + "ValueDateIdentificationIndicator" => Some(562), + "ValueDateInstruction" => Some(563), + "ValueDateInstruction1" => Some(564), + "ValueDateMarginType" => Some(565), + "ValueDatePosition2" => Some(566), + "ValueDatePositionCount" => Some(567), + "ValueDateReconciliationCount" => Some(568), + "ValueDateReconciliationId" => Some(569), + "ValueDateReferenceFlag" => Some(570), + "ValueDateRemittanceCount" => Some(571), + "ValueDateRemittanceIndicator" => Some(572), + "ValueDateRoutingFlag" => Some(573), + "ValueDateSequenceId" => Some(574), + "ValueDateSettlementCount" => Some(575), + "ValueDateStatementDate" => Some(576), + "ValueDateTransmission" => Some(577), + "ValueDateTransmissionCode" => Some(578), + "ValueDateValueDateReport" => Some(579), + "ValueDateVersionReport" => Some(580), + "VersionAccount" => Some(581), + "VersionAdjustment" => Some(582), + "VersionAdjustment1" => Some(583), + "VersionAuthenticationCode" => Some(584), + "VersionBeneficiaryCount" => Some(585), + "VersionBeneficiaryFlag" => Some(586), + "VersionCategoryFlag" => Some(587), + "VersionClearingFlag" => Some(588), + "VersionMargin" => Some(589), + "VersionMarginFlag" => Some(590), + "VersionMarginReport" => Some(591), + "VersionReconciliation1" => Some(592), + "VersionReferenceDate" => Some(593), + "VersionReferenceIndicator" => Some(594), + "VersionRemittance" => Some(595), + "VersionStatusCode" => Some(596), + "VersionStatusType" => Some(597), + "VersionTransactionId" => Some(598), + "VersionTransmissionCount" => Some(599), + _ => None, + } +} + +pub static PHF_600: phf::Map<&'static str, u32> = phf::phf_map! { + "AccountAccount" => 0u32, + "AccountAccountId" => 1u32, + "AccountAmount" => 2u32, + "AccountAuthenticationReport" => 3u32, + "AccountCategoryCode" => 4u32, + "AccountClearingCount" => 5u32, + "AccountInstruction2" => 6u32, + "AccountInstructionDate" => 7u32, + "AccountMarginId" => 8u32, + "AccountMarginIndicator" => 9u32, + "AccountPartyCount" => 10u32, + "AccountRoutingDate" => 11u32, + "AccountSettlement1" => 12u32, + "AccountStatement" => 13u32, + "AccountTransactionFlag" => 14u32, + "AccountTransactionReport" => 15u32, + "AccountTransmission" => 16u32, + "AccountTransmissionCount" => 17u32, + "AdjustmentAccountId" => 18u32, + "AdjustmentAuthenticationIndicator" => 19u32, + "AdjustmentAuthenticationSub" => 20u32, + "AdjustmentCategoryId" => 21u32, + "AdjustmentCategorySub" => 22u32, + "AdjustmentIdentificationType" => 23u32, + "AdjustmentMarginFlag" => 24u32, + "AdjustmentPosition" => 25u32, + "AdjustmentPriorityFlag" => 26u32, + "AdjustmentReferenceFlag" => 27u32, + "AdjustmentRemittanceReport" => 28u32, + "AdjustmentRoutingCount" => 29u32, + "AdjustmentSequence1" => 30u32, + "AdjustmentSettlementDate" => 31u32, + "AdjustmentSettlementType" => 32u32, + "AdjustmentStatementDate" => 33u32, + "AdjustmentStatusFlag" => 34u32, + "AdjustmentTransactionType" => 35u32, + "AdjustmentTransmissionReport" => 36u32, + "AdjustmentValueDateDate" => 37u32, + "AdjustmentValueDateSub" => 38u32, + "AdjustmentVersionCount" => 39u32, + "AmountAmount" => 40u32, + "AmountAuthentication" => 41u32, + "AmountAuthenticationId" => 42u32, + "AmountBeneficiaryCount" => 43u32, + "AmountBeneficiaryReport" => 44u32, + "AmountCategoryReport" => 45u32, + "AmountChannelType" => 46u32, + "AmountClearing" => 47u32, + "AmountClearing1" => 48u32, + "AmountInstructionId" => 49u32, + "AmountMarginReport" => 50u32, + "AmountPriority2" => 51u32, + "AmountReconciliationType" => 52u32, + "AmountSettlementType" => 53u32, + "AmountStatementDate" => 54u32, + "AmountStatementIndicator" => 55u32, + "AmountTransmission" => 56u32, + "AmountTransmission2" => 57u32, + "AmountTransmissionDate" => 58u32, + "AmountValueDate" => 59u32, + "AmountValueDate1" => 60u32, + "AuthenticationAccountId" => 61u32, + "AuthenticationAdjustmentReport" => 62u32, + "AuthenticationAdjustmentSub" => 63u32, + "AuthenticationClearing" => 64u32, + "AuthenticationCurrencyDate" => 65u32, + "AuthenticationInstruction" => 66u32, + "AuthenticationMarginFlag" => 67u32, + "AuthenticationPartySub" => 68u32, + "AuthenticationPartyType" => 69u32, + "AuthenticationPosition1" => 70u32, + "AuthenticationPositionId" => 71u32, + "AuthenticationReferenceDate" => 72u32, + "AuthenticationRoutingReport" => 73u32, + "AuthenticationSettlementSub" => 74u32, + "AuthenticationStatus" => 75u32, + "AuthenticationTransaction" => 76u32, + "AuthenticationTransactionIndicator" => 77u32, + "AuthenticationTransactionType" => 78u32, + "AuthenticationValueDate2" => 79u32, + "AuthenticationValueDateReport" => 80u32, + "AuthenticationVersion" => 81u32, + "AuthenticationVersionCode" => 82u32, + "AuthenticationVersionIndicator" => 83u32, + "BeneficiaryAccountDate" => 84u32, + "BeneficiaryAmountReport" => 85u32, + "BeneficiaryAuthenticationIndicator" => 86u32, + "BeneficiaryChannelCount" => 87u32, + "BeneficiaryCurrency" => 88u32, + "BeneficiaryCurrencyFlag" => 89u32, + "BeneficiaryIdentification2" => 90u32, + "BeneficiaryIdentificationType" => 91u32, + "BeneficiaryInstruction" => 92u32, + "BeneficiaryPriorityCount" => 93u32, + "BeneficiaryRoutingCount" => 94u32, + "BeneficiaryRoutingId" => 95u32, + "BeneficiarySequenceReport" => 96u32, + "BeneficiarySequenceType" => 97u32, + "BeneficiaryStatementIndicator" => 98u32, + "BeneficiaryTransaction2" => 99u32, + "BeneficiaryValueDateCode" => 100u32, + "BeneficiaryVersionId" => 101u32, + "CategoryBeneficiaryFlag" => 102u32, + "CategoryCategory" => 103u32, + "CategoryCollateral" => 104u32, + "CategoryCurrency" => 105u32, + "CategoryCurrencyType" => 106u32, + "CategoryPartyDate" => 107u32, + "CategoryPositionCode" => 108u32, + "CategoryPositionIndicator" => 109u32, + "CategoryPriorityFlag" => 110u32, + "CategoryStatement" => 111u32, + "CategoryStatementId" => 112u32, + "CategoryStatusReport" => 113u32, + "CategoryTransaction1" => 114u32, + "CategoryTransactionCount" => 115u32, + "CategoryTransactionFlag" => 116u32, + "CategoryValueDateFlag" => 117u32, + "ChannelAccountCount" => 118u32, + "ChannelAdjustmentCount" => 119u32, + "ChannelAdjustmentSub" => 120u32, + "ChannelBeneficiaryDate" => 121u32, + "ChannelClearingFlag" => 122u32, + "ChannelCollateral" => 123u32, + "ChannelInstructionIndicator" => 124u32, + "ChannelPositionCount" => 125u32, + "ChannelPositionIndicator" => 126u32, + "ChannelPriorityCount" => 127u32, + "ChannelReconciliationCount" => 128u32, + "ChannelReconciliationReport" => 129u32, + "ChannelReferenceFlag" => 130u32, + "ChannelRemittance" => 131u32, + "ChannelRemittanceFlag" => 132u32, + "ChannelRemittanceId" => 133u32, + "ChannelRemittanceType" => 134u32, + "ChannelStatus" => 135u32, + "ChannelTransaction2" => 136u32, + "ChannelValueDateSub" => 137u32, + "ChannelVersion1" => 138u32, + "ChannelVersionId" => 139u32, + "ChannelVersionType" => 140u32, + "ClearingAccountReport" => 141u32, + "ClearingAdjustmentId" => 142u32, + "ClearingAmountFlag" => 143u32, + "ClearingBeneficiarySub" => 144u32, + "ClearingClearing2" => 145u32, + "ClearingIdentification1" => 146u32, + "ClearingInstructionFlag" => 147u32, + "ClearingPartyCount" => 148u32, + "ClearingPartyDate" => 149u32, + "ClearingPositionFlag" => 150u32, + "ClearingReference" => 151u32, + "ClearingSequence2" => 152u32, + "ClearingStatementId" => 153u32, + "ClearingTransactionCount" => 154u32, + "ClearingValueDate1" => 155u32, + "ClearingValueDateCount" => 156u32, + "CollateralCollateral1" => 157u32, + "CollateralInstructionCount" => 158u32, + "CollateralPrioritySub" => 159u32, + "CollateralReconciliationCode" => 160u32, + "CollateralReference1" => 161u32, + "CollateralReference2" => 162u32, + "CollateralSequenceType" => 163u32, + "CollateralStatusSub" => 164u32, + "CollateralValueDateCode" => 165u32, + "CollateralValueDateSub" => 166u32, + "CollateralVersionDate" => 167u32, + "CurrencyAccount" => 168u32, + "CurrencyAdjustmentIndicator" => 169u32, + "CurrencyBeneficiaryReport" => 170u32, + "CurrencyCategory2" => 171u32, + "CurrencyChannelType" => 172u32, + "CurrencyCollateralCode" => 173u32, + "CurrencyCollateralId" => 174u32, + "CurrencyIdentificationFlag" => 175u32, + "CurrencyIdentificationIndicator" => 176u32, + "CurrencyInstructionId" => 177u32, + "CurrencyMargin" => 178u32, + "CurrencyPriorityReport" => 179u32, + "CurrencyReconciliationId" => 180u32, + "CurrencyReference" => 181u32, + "CurrencySequenceReport" => 182u32, + "CurrencyStatementCode" => 183u32, + "CurrencyStatusCode" => 184u32, + "CurrencyStatusDate" => 185u32, + "CurrencyTransactionFlag" => 186u32, + "CurrencyTransactionReport" => 187u32, + "CurrencyValueDateCount" => 188u32, + "CurrencyValueDateSub" => 189u32, + "CurrencyVersionSub" => 190u32, + "IdentificationAdjustmentFlag" => 191u32, + "IdentificationChannelSub" => 192u32, + "IdentificationClearingDate" => 193u32, + "IdentificationCollateralDate" => 194u32, + "IdentificationCurrencyId" => 195u32, + "IdentificationMarginCount" => 196u32, + "IdentificationMarginReport" => 197u32, + "IdentificationMarginSub" => 198u32, + "IdentificationParty" => 199u32, + "IdentificationPartyType" => 200u32, + "IdentificationPriority1" => 201u32, + "IdentificationReconciliation" => 202u32, + "IdentificationReconciliationCode" => 203u32, + "IdentificationSettlementCount" => 204u32, + "IdentificationStatementFlag" => 205u32, + "IdentificationTransaction1" => 206u32, + "IdentificationTransactionDate" => 207u32, + "IdentificationTransmissionReport" => 208u32, + "InstructionAccountId" => 209u32, + "InstructionAdjustmentReport" => 210u32, + "InstructionAuthentication" => 211u32, + "InstructionBeneficiary" => 212u32, + "InstructionBeneficiary1" => 213u32, + "InstructionCategory1" => 214u32, + "InstructionChannel2" => 215u32, + "InstructionChannelSub" => 216u32, + "InstructionCurrency" => 217u32, + "InstructionCurrency2" => 218u32, + "InstructionInstructionCode" => 219u32, + "InstructionPriority" => 220u32, + "InstructionPriorityCount" => 221u32, + "InstructionReconciliation1" => 222u32, + "InstructionReconciliationIndicator" => 223u32, + "InstructionReference" => 224u32, + "InstructionReferenceDate" => 225u32, + "InstructionReferenceIndicator" => 226u32, + "InstructionRemittanceReport" => 227u32, + "InstructionRouting2" => 228u32, + "InstructionRoutingFlag" => 229u32, + "InstructionRoutingType" => 230u32, + "InstructionSequence2" => 231u32, + "InstructionSequenceId" => 232u32, + "InstructionStatusCount" => 233u32, + "InstructionTransmissionType" => 234u32, + "InstructionValueDateSub" => 235u32, + "InstructionVersion" => 236u32, + "MarginAccountReport" => 237u32, + "MarginAccountSub" => 238u32, + "MarginAmount" => 239u32, + "MarginAuthentication" => 240u32, + "MarginBeneficiaryType" => 241u32, + "MarginCategoryFlag" => 242u32, + "MarginCollateralReport" => 243u32, + "MarginMarginDate" => 244u32, + "MarginMarginIndicator" => 245u32, + "MarginPartyType" => 246u32, + "MarginPosition" => 247u32, + "MarginPositionCount" => 248u32, + "MarginPriorityId" => 249u32, + "MarginReference1" => 250u32, + "MarginRemittanceReport" => 251u32, + "MarginRouting" => 252u32, + "MarginSettlementCode" => 253u32, + "MarginTransactionCode" => 254u32, + "MarginTransmission" => 255u32, + "MarginValueDateId" => 256u32, + "PartyAccount1" => 257u32, + "PartyBeneficiary1" => 258u32, + "PartyBeneficiaryId" => 259u32, + "PartyCategory" => 260u32, + "PartyClearing" => 261u32, + "PartyClearingDate" => 262u32, + "PartyCurrencyDate" => 263u32, + "PartyPartyId" => 264u32, + "PartyPriorityCount" => 265u32, + "PartyReconciliationCount" => 266u32, + "PartyReconciliationType" => 267u32, + "PartyRouting" => 268u32, + "PartyRoutingId" => 269u32, + "PartyRoutingReport" => 270u32, + "PartySettlement1" => 271u32, + "PartyStatement" => 272u32, + "PartyStatusDate" => 273u32, + "PartyStatusIndicator" => 274u32, + "PartyTransaction2" => 275u32, + "PartyTransactionFlag" => 276u32, + "PartyTransactionReport" => 277u32, + "PartyTransmissionReport" => 278u32, + "PartyVersion1" => 279u32, + "PositionAdjustmentFlag" => 280u32, + "PositionAdjustmentSub" => 281u32, + "PositionAmountSub" => 282u32, + "PositionAuthenticationDate" => 283u32, + "PositionCollateralFlag" => 284u32, + "PositionIdentification" => 285u32, + "PositionIdentificationType" => 286u32, + "PositionInstruction" => 287u32, + "PositionInstructionIndicator" => 288u32, + "PositionMargin" => 289u32, + "PositionPartyFlag" => 290u32, + "PositionPositionFlag" => 291u32, + "PositionReconciliationCode" => 292u32, + "PositionReconciliationId" => 293u32, + "PositionReconciliationIndicator" => 294u32, + "PositionReconciliationReport" => 295u32, + "PositionReferenceCount" => 296u32, + "PositionReferenceDate" => 297u32, + "PositionRemittanceIndicator" => 298u32, + "PositionSequence2" => 299u32, + "PositionStatement" => 300u32, + "PositionStatusCode" => 301u32, + "PositionTransmissionId" => 302u32, + "PositionVersion2" => 303u32, + "PriorityAdjustmentSub" => 304u32, + "PriorityAmount2" => 305u32, + "PriorityAmountIndicator" => 306u32, + "PriorityBeneficiaryIndicator" => 307u32, + "PriorityCategoryId" => 308u32, + "PriorityChannel2" => 309u32, + "PriorityChannelFlag" => 310u32, + "PriorityChannelReport" => 311u32, + "PriorityClearing2" => 312u32, + "PriorityCollateralCode" => 313u32, + "PriorityCurrency" => 314u32, + "PriorityIdentificationIndicator" => 315u32, + "PriorityInstructionCode" => 316u32, + "PriorityMarginId" => 317u32, + "PriorityMarginSub" => 318u32, + "PriorityPartyId" => 319u32, + "PriorityPositionFlag" => 320u32, + "PriorityPriorityCount" => 321u32, + "PriorityReconciliationId" => 322u32, + "PriorityRemittanceCount" => 323u32, + "PriorityRemittanceFlag" => 324u32, + "PriorityRemittanceType" => 325u32, + "PriorityRouting1" => 326u32, + "PriorityRoutingCount" => 327u32, + "PriorityRoutingType" => 328u32, + "PrioritySequenceDate" => 329u32, + "PrioritySettlement" => 330u32, + "PrioritySettlementCode" => 331u32, + "PriorityStatus2" => 332u32, + "PriorityStatusCode" => 333u32, + "PriorityStatusDate" => 334u32, + "PriorityTransactionCount" => 335u32, + "PriorityTransmission2" => 336u32, + "PriorityTransmissionFlag" => 337u32, + "PriorityValueDate1" => 338u32, + "ReconciliationAdjustment1" => 339u32, + "ReconciliationAdjustmentSub" => 340u32, + "ReconciliationBeneficiary2" => 341u32, + "ReconciliationChannelFlag" => 342u32, + "ReconciliationCollateralType" => 343u32, + "ReconciliationCurrencyReport" => 344u32, + "ReconciliationIdentification1" => 345u32, + "ReconciliationInstructionType" => 346u32, + "ReconciliationParty" => 347u32, + "ReconciliationPartyDate" => 348u32, + "ReconciliationPartyType" => 349u32, + "ReconciliationPositionDate" => 350u32, + "ReconciliationPositionId" => 351u32, + "ReconciliationPriority1" => 352u32, + "ReconciliationReconciliationCode" => 353u32, + "ReconciliationReconciliationDate" => 354u32, + "ReconciliationRouting" => 355u32, + "ReconciliationRoutingCode" => 356u32, + "ReconciliationRoutingIndicator" => 357u32, + "ReconciliationSettlementSub" => 358u32, + "ReconciliationStatementReport" => 359u32, + "ReferenceAmountType" => 360u32, + "ReferenceBeneficiaryDate" => 361u32, + "ReferenceChannel" => 362u32, + "ReferenceClearing2" => 363u32, + "ReferenceClearingIndicator" => 364u32, + "ReferenceCurrency" => 365u32, + "ReferenceInstructionFlag" => 366u32, + "ReferencePartyCount" => 367u32, + "ReferencePartyType" => 368u32, + "ReferencePosition" => 369u32, + "ReferenceReconciliationType" => 370u32, + "ReferenceReferenceSub" => 371u32, + "ReferenceRemittance" => 372u32, + "ReferenceRemittance2" => 373u32, + "ReferenceRemittanceCount" => 374u32, + "ReferenceSequenceSub" => 375u32, + "ReferenceStatement" => 376u32, + "ReferenceStatus" => 377u32, + "ReferenceTransactionDate" => 378u32, + "ReferenceTransmissionSub" => 379u32, + "ReferenceVersion" => 380u32, + "RemittanceAdjustmentType" => 381u32, + "RemittanceCategoryFlag" => 382u32, + "RemittanceCategoryType" => 383u32, + "RemittanceCollateral2" => 384u32, + "RemittanceCollateralFlag" => 385u32, + "RemittanceIdentification1" => 386u32, + "RemittanceInstructionType" => 387u32, + "RemittanceMargin" => 388u32, + "RemittancePositionCount" => 389u32, + "RemittancePriority" => 390u32, + "RemittancePriorityCount" => 391u32, + "RemittanceRemittance" => 392u32, + "RemittanceSequenceDate" => 393u32, + "RemittanceStatusCount" => 394u32, + "RemittanceStatusIndicator" => 395u32, + "RemittanceTransmission2" => 396u32, + "RemittanceTransmissionCode" => 397u32, + "RemittanceTransmissionIndicator" => 398u32, + "RemittanceVersionDate" => 399u32, + "RoutingAccountDate" => 400u32, + "RoutingAmount1" => 401u32, + "RoutingAmountCode" => 402u32, + "RoutingAuthenticationIndicator" => 403u32, + "RoutingCategoryCode" => 404u32, + "RoutingCategoryType" => 405u32, + "RoutingClearingDate" => 406u32, + "RoutingCurrencySub" => 407u32, + "RoutingIdentificationCode" => 408u32, + "RoutingPartyType" => 409u32, + "RoutingPriority2" => 410u32, + "RoutingPriorityFlag" => 411u32, + "RoutingPriorityId" => 412u32, + "RoutingReconciliationSub" => 413u32, + "RoutingReferenceDate" => 414u32, + "RoutingRemittance" => 415u32, + "RoutingRemittance2" => 416u32, + "RoutingRouting" => 417u32, + "RoutingRoutingDate" => 418u32, + "RoutingRoutingId" => 419u32, + "RoutingRoutingIndicator" => 420u32, + "RoutingSequenceType" => 421u32, + "RoutingSettlement1" => 422u32, + "RoutingTransactionReport" => 423u32, + "RoutingTransmissionIndicator" => 424u32, + "RoutingValueDateCode" => 425u32, + "RoutingValueDateCount" => 426u32, + "RoutingVersionIndicator" => 427u32, + "RoutingVersionType" => 428u32, + "SequenceAccount2" => 429u32, + "SequenceAccountType" => 430u32, + "SequenceAdjustmentDate" => 431u32, + "SequenceAdjustmentIndicator" => 432u32, + "SequenceAmount1" => 433u32, + "SequenceAmountType" => 434u32, + "SequenceBeneficiary1" => 435u32, + "SequenceBeneficiaryCount" => 436u32, + "SequenceChannel" => 437u32, + "SequenceParty" => 438u32, + "SequencePartyId" => 439u32, + "SequenceReconciliationId" => 440u32, + "SequenceReconciliationReport" => 441u32, + "SequenceReferenceId" => 442u32, + "SequenceStatus" => 443u32, + "SequenceTransaction1" => 444u32, + "SequenceTransactionDate" => 445u32, + "SequenceTransactionFlag" => 446u32, + "SequenceTransmission" => 447u32, + "SequenceTransmissionDate" => 448u32, + "SettlementAccount" => 449u32, + "SettlementBeneficiaryDate" => 450u32, + "SettlementBeneficiaryType" => 451u32, + "SettlementCategoryCount" => 452u32, + "SettlementChannel" => 453u32, + "SettlementInstructionCode" => 454u32, + "SettlementPositionIndicator" => 455u32, + "SettlementReferenceReport" => 456u32, + "SettlementRemittanceIndicator" => 457u32, + "SettlementRoutingReport" => 458u32, + "SettlementSequenceReport" => 459u32, + "SettlementStatementType" => 460u32, + "SettlementStatusCount" => 461u32, + "SettlementTransmissionCode" => 462u32, + "SettlementTransmissionFlag" => 463u32, + "SettlementVersion" => 464u32, + "StatementAccount" => 465u32, + "StatementAuthentication" => 466u32, + "StatementAuthenticationFlag" => 467u32, + "StatementBeneficiary" => 468u32, + "StatementBeneficiarySub" => 469u32, + "StatementChannelReport" => 470u32, + "StatementMargin" => 471u32, + "StatementPositionType" => 472u32, + "StatementPriority2" => 473u32, + "StatementReference" => 474u32, + "StatementReferenceCode" => 475u32, + "StatementReferenceIndicator" => 476u32, + "StatementRemittanceFlag" => 477u32, + "StatementRoutingCode" => 478u32, + "StatementSequenceId" => 479u32, + "StatementSettlement" => 480u32, + "StatementSettlement2" => 481u32, + "StatementValueDateCount" => 482u32, + "StatementValueDateReport" => 483u32, + "StatusAccountType" => 484u32, + "StatusAmount2" => 485u32, + "StatusAuthentication" => 486u32, + "StatusAuthentication2" => 487u32, + "StatusAuthenticationId" => 488u32, + "StatusAuthenticationIndicator" => 489u32, + "StatusCategoryIndicator" => 490u32, + "StatusChannelFlag" => 491u32, + "StatusClearingReport" => 492u32, + "StatusCurrency2" => 493u32, + "StatusInstructionReport" => 494u32, + "StatusPartyIndicator" => 495u32, + "StatusPositionSub" => 496u32, + "StatusPriority1" => 497u32, + "StatusPriorityCount" => 498u32, + "StatusPriorityIndicator" => 499u32, + "StatusReconciliationReport" => 500u32, + "StatusRouting" => 501u32, + "StatusRoutingCode" => 502u32, + "StatusRoutingFlag" => 503u32, + "StatusTransmission" => 504u32, + "StatusTransmissionType" => 505u32, + "StatusValueDateSub" => 506u32, + "StatusVersionDate" => 507u32, + "TransactionAuthentication" => 508u32, + "TransactionAuthentication1" => 509u32, + "TransactionAuthenticationId" => 510u32, + "TransactionClearing" => 511u32, + "TransactionClearingCode" => 512u32, + "TransactionCollateralId" => 513u32, + "TransactionCurrencyIndicator" => 514u32, + "TransactionInstructionCode" => 515u32, + "TransactionPartySub" => 516u32, + "TransactionPosition" => 517u32, + "TransactionPriorityCode" => 518u32, + "TransactionReference2" => 519u32, + "TransactionRouting1" => 520u32, + "TransactionRoutingIndicator" => 521u32, + "TransactionStatementDate" => 522u32, + "TransactionTransactionId" => 523u32, + "TransactionTransmissionType" => 524u32, + "TransactionVersion1" => 525u32, + "TransactionVersionType" => 526u32, + "TransmissionAccountId" => 527u32, + "TransmissionAdjustmentSub" => 528u32, + "TransmissionAmountFlag" => 529u32, + "TransmissionBeneficiaryReport" => 530u32, + "TransmissionCategoryIndicator" => 531u32, + "TransmissionCollateral1" => 532u32, + "TransmissionIdentificationFlag" => 533u32, + "TransmissionInstructionId" => 534u32, + "TransmissionMargin2" => 535u32, + "TransmissionMarginDate" => 536u32, + "TransmissionPositionType" => 537u32, + "TransmissionPriorityCode" => 538u32, + "TransmissionPriorityIndicator" => 539u32, + "TransmissionReconciliation" => 540u32, + "TransmissionReconciliationCount" => 541u32, + "TransmissionReconciliationDate" => 542u32, + "TransmissionRemittanceSub" => 543u32, + "TransmissionRoutingCount" => 544u32, + "TransmissionRoutingId" => 545u32, + "TransmissionSequenceDate" => 546u32, + "TransmissionStatementId" => 547u32, + "TransmissionStatusCode" => 548u32, + "TransmissionTransmission" => 549u32, + "TransmissionTransmissionFlag" => 550u32, + "TransmissionVersion" => 551u32, + "ValueDateAccountFlag" => 552u32, + "ValueDateAdjustmentReport" => 553u32, + "ValueDateChannel1" => 554u32, + "ValueDateChannelFlag" => 555u32, + "ValueDateChannelId" => 556u32, + "ValueDateClearingDate" => 557u32, + "ValueDateClearingReport" => 558u32, + "ValueDateCurrencyId" => 559u32, + "ValueDateCurrencyType" => 560u32, + "ValueDateIdentification2" => 561u32, + "ValueDateIdentificationIndicator" => 562u32, + "ValueDateInstruction" => 563u32, + "ValueDateInstruction1" => 564u32, + "ValueDateMarginType" => 565u32, + "ValueDatePosition2" => 566u32, + "ValueDatePositionCount" => 567u32, + "ValueDateReconciliationCount" => 568u32, + "ValueDateReconciliationId" => 569u32, + "ValueDateReferenceFlag" => 570u32, + "ValueDateRemittanceCount" => 571u32, + "ValueDateRemittanceIndicator" => 572u32, + "ValueDateRoutingFlag" => 573u32, + "ValueDateSequenceId" => 574u32, + "ValueDateSettlementCount" => 575u32, + "ValueDateStatementDate" => 576u32, + "ValueDateTransmission" => 577u32, + "ValueDateTransmissionCode" => 578u32, + "ValueDateValueDateReport" => 579u32, + "ValueDateVersionReport" => 580u32, + "VersionAccount" => 581u32, + "VersionAdjustment" => 582u32, + "VersionAdjustment1" => 583u32, + "VersionAuthenticationCode" => 584u32, + "VersionBeneficiaryCount" => 585u32, + "VersionBeneficiaryFlag" => 586u32, + "VersionCategoryFlag" => 587u32, + "VersionClearingFlag" => 588u32, + "VersionMargin" => 589u32, + "VersionMarginFlag" => 590u32, + "VersionMarginReport" => 591u32, + "VersionReconciliation1" => 592u32, + "VersionReferenceDate" => 593u32, + "VersionReferenceIndicator" => 594u32, + "VersionRemittance" => 595u32, + "VersionStatusCode" => 596u32, + "VersionStatusType" => 597u32, + "VersionTransactionId" => 598u32, + "VersionTransmissionCount" => 599u32, +}; + +// --- Tier 1500 (large) --- +pub const TAGS_1500: [&str; 1500] = [ + "AccountAccount", + "AccountAccount1", + "AccountAdjustment2", + "AccountAmountDate", + "AccountAmountFlag", + "AccountAmountType", + "AccountAuthenticationType", + "AccountBeneficiaryCode", + "AccountBeneficiaryCount", + "AccountBeneficiaryFlag", + "AccountCategory", + "AccountCategoryDate", + "AccountCategoryFlag", + "AccountCategoryIndicator", + "AccountChannel", + "AccountChannelCount", + "AccountChannelId", + "AccountChannelIndicator", + "AccountClearing1", + "AccountCollateral2", + "AccountCollateralFlag", + "AccountCollateralReport", + "AccountCurrency1", + "AccountCurrencyFlag", + "AccountCurrencyId", + "AccountCurrencyIndicator", + "AccountCurrencySub", + "AccountIdentification", + "AccountIdentification1", + "AccountInstructionReport", + "AccountMarginFlag", + "AccountMarginType", + "AccountParty1", + "AccountPartyCount", + "AccountPartyFlag", + "AccountPartyIndicator", + "AccountPartyType", + "AccountPosition", + "AccountPriorityDate", + "AccountReconciliation", + "AccountReconciliationType", + "AccountReferenceId", + "AccountReferenceIndicator", + "AccountReferenceType", + "AccountRemittanceId", + "AccountRemittanceIndicator", + "AccountRoutingCount", + "AccountRoutingIndicator", + "AccountRoutingType", + "AccountSequence", + "AccountSequence1", + "AccountSequence2", + "AccountSettlementDate", + "AccountSettlementType", + "AccountStatement", + "AccountStatementCount", + "AccountStatusFlag", + "AccountStatusType", + "AccountTransaction", + "AccountTransactionCode", + "AccountTransactionSub", + "AccountTransactionType", + "AccountTransmission", + "AccountTransmission1", + "AccountVersion", + "AccountVersionSub", + "AccountVersionType", + "AdjustmentAccount", + "AdjustmentAccount1", + "AdjustmentAccountCode", + "AdjustmentAdjustment2", + "AdjustmentAdjustmentIndicator", + "AdjustmentAmount", + "AdjustmentAuthentication", + "AdjustmentAuthenticationFlag", + "AdjustmentBeneficiaryCount", + "AdjustmentBeneficiaryId", + "AdjustmentCategory1", + "AdjustmentChannelFlag", + "AdjustmentChannelType", + "AdjustmentClearingDate", + "AdjustmentClearingId", + "AdjustmentCollateralIndicator", + "AdjustmentCurrency", + "AdjustmentCurrencyId", + "AdjustmentCurrencyReport", + "AdjustmentIdentificationReport", + "AdjustmentInstructionDate", + "AdjustmentInstructionType", + "AdjustmentMarginCode", + "AdjustmentMarginReport", + "AdjustmentPosition2", + "AdjustmentPositionCount", + "AdjustmentPositionDate", + "AdjustmentReconciliation", + "AdjustmentReconciliationCount", + "AdjustmentReference1", + "AdjustmentRemittance", + "AdjustmentRemittance1", + "AdjustmentRemittanceFlag", + "AdjustmentRoutingIndicator", + "AdjustmentSequence", + "AdjustmentSequenceReport", + "AdjustmentSettlementFlag", + "AdjustmentStatement", + "AdjustmentStatementCode", + "AdjustmentStatementFlag", + "AdjustmentStatementId", + "AdjustmentStatementIndicator", + "AdjustmentStatusDate", + "AdjustmentStatusType", + "AdjustmentTransactionDate", + "AdjustmentTransmissionCode", + "AdjustmentTransmissionCount", + "AdjustmentTransmissionId", + "AdjustmentTransmissionSub", + "AdjustmentValueDateFlag", + "AdjustmentVersionType", + "AmountAccount1", + "AmountAccountCode", + "AmountAccountFlag", + "AmountAdjustment", + "AmountAdjustmentCode", + "AmountAdjustmentFlag", + "AmountAuthentication", + "AmountAuthenticationId", + "AmountAuthenticationIndicator", + "AmountChannel", + "AmountChannelCode", + "AmountChannelDate", + "AmountCollateralFlag", + "AmountCurrency", + "AmountCurrencyCount", + "AmountIdentificationIndicator", + "AmountIdentificationSub", + "AmountInstructionFlag", + "AmountInstructionReport", + "AmountPartyId", + "AmountPartyIndicator", + "AmountPriorityReport", + "AmountReconciliationIndicator", + "AmountReconciliationSub", + "AmountReference", + "AmountReferenceIndicator", + "AmountRemittance", + "AmountRemittance1", + "AmountRemittance2", + "AmountRemittanceFlag", + "AmountRemittanceReport", + "AmountSequenceDate", + "AmountSequenceReport", + "AmountSettlementDate", + "AmountStatementIndicator", + "AmountStatusFlag", + "AmountStatusIndicator", + "AmountTransaction", + "AmountTransactionCount", + "AmountTransmission2", + "AmountTransmissionCode", + "AmountTransmissionIndicator", + "AmountValueDate", + "AmountValueDate1", + "AmountVersionDate", + "AuthenticationAccountDate", + "AuthenticationAccountId", + "AuthenticationAccountType", + "AuthenticationAdjustmentType", + "AuthenticationAmountFlag", + "AuthenticationAuthentication1", + "AuthenticationBeneficiaryCode", + "AuthenticationBeneficiaryDate", + "AuthenticationBeneficiaryId", + "AuthenticationBeneficiaryIndicator", + "AuthenticationBeneficiarySub", + "AuthenticationCategory1", + "AuthenticationCategoryDate", + "AuthenticationCategoryFlag", + "AuthenticationChannel1", + "AuthenticationChannel2", + "AuthenticationChannelFlag", + "AuthenticationClearing", + "AuthenticationClearingIndicator", + "AuthenticationClearingReport", + "AuthenticationCollateralType", + "AuthenticationCurrencyIndicator", + "AuthenticationCurrencyType", + "AuthenticationIdentificationFlag", + "AuthenticationIdentificationReport", + "AuthenticationInstruction", + "AuthenticationInstructionId", + "AuthenticationMarginCount", + "AuthenticationPartyCode", + "AuthenticationPosition1", + "AuthenticationPositionFlag", + "AuthenticationPositionReport", + "AuthenticationPriorityCode", + "AuthenticationPriorityIndicator", + "AuthenticationReconciliationIndicator", + "AuthenticationReference2", + "AuthenticationReferenceFlag", + "AuthenticationReferenceId", + "AuthenticationRemittance1", + "AuthenticationRemittanceFlag", + "AuthenticationRemittanceIndicator", + "AuthenticationRemittanceType", + "AuthenticationRouting", + "AuthenticationRouting1", + "AuthenticationSequenceCode", + "AuthenticationSequenceId", + "AuthenticationSequenceIndicator", + "AuthenticationSequenceSub", + "AuthenticationSettlement", + "AuthenticationSettlement1", + "AuthenticationSettlementId", + "AuthenticationSettlementSub", + "AuthenticationStatementCount", + "AuthenticationStatementId", + "AuthenticationStatusId", + "AuthenticationTransaction1", + "AuthenticationTransactionDate", + "AuthenticationTransactionSub", + "AuthenticationTransactionType", + "AuthenticationTransmissionCode", + "AuthenticationTransmissionIndicator", + "AuthenticationTransmissionReport", + "AuthenticationValueDate2", + "AuthenticationVersion1", + "BeneficiaryAccount1", + "BeneficiaryAccountReport", + "BeneficiaryAccountType", + "BeneficiaryAdjustmentCount", + "BeneficiaryAdjustmentDate", + "BeneficiaryAdjustmentType", + "BeneficiaryAmountType", + "BeneficiaryAuthenticationCode", + "BeneficiaryAuthenticationSub", + "BeneficiaryAuthenticationType", + "BeneficiaryBeneficiary", + "BeneficiaryBeneficiary2", + "BeneficiaryBeneficiaryCode", + "BeneficiaryBeneficiaryCount", + "BeneficiaryBeneficiaryIndicator", + "BeneficiaryBeneficiaryReport", + "BeneficiaryCategory", + "BeneficiaryCategoryCode", + "BeneficiaryChannel2", + "BeneficiaryChannelCount", + "BeneficiaryChannelId", + "BeneficiaryCollateralCount", + "BeneficiaryCollateralFlag", + "BeneficiaryCollateralIndicator", + "BeneficiaryCurrencyType", + "BeneficiaryIdentification1", + "BeneficiaryIdentification2", + "BeneficiaryIdentificationReport", + "BeneficiaryInstructionReport", + "BeneficiaryInstructionSub", + "BeneficiaryMarginId", + "BeneficiaryPartyCode", + "BeneficiaryPartyFlag", + "BeneficiaryPartyIndicator", + "BeneficiaryPosition", + "BeneficiaryPriorityType", + "BeneficiaryReference1", + "BeneficiaryReferenceDate", + "BeneficiaryReferenceFlag", + "BeneficiaryReferenceReport", + "BeneficiaryRemittance", + "BeneficiaryRemittance2", + "BeneficiaryRemittanceCount", + "BeneficiaryRemittanceSub", + "BeneficiaryRoutingIndicator", + "BeneficiarySequenceId", + "BeneficiarySettlementIndicator", + "BeneficiaryStatus2", + "BeneficiaryStatusCount", + "BeneficiaryStatusDate", + "BeneficiaryTransactionSub", + "BeneficiaryTransmission", + "BeneficiaryTransmission1", + "BeneficiaryTransmission2", + "BeneficiaryTransmissionFlag", + "BeneficiaryTransmissionIndicator", + "BeneficiaryValueDate", + "BeneficiaryValueDate1", + "BeneficiaryVersionCount", + "BeneficiaryVersionFlag", + "CategoryAccount1", + "CategoryAccountDate", + "CategoryAccountFlag", + "CategoryAccountType", + "CategoryAdjustment1", + "CategoryAdjustmentDate", + "CategoryAmount", + "CategoryAmountIndicator", + "CategoryAuthentication1", + "CategoryAuthentication2", + "CategoryAuthenticationIndicator", + "CategoryAuthenticationType", + "CategoryBeneficiaryCode", + "CategoryBeneficiaryIndicator", + "CategoryChannel", + "CategoryChannelIndicator", + "CategoryClearingFlag", + "CategoryClearingType", + "CategoryCollateral1", + "CategoryCollateralFlag", + "CategoryCollateralType", + "CategoryCurrency", + "CategoryCurrency1", + "CategoryCurrencyReport", + "CategoryCurrencySub", + "CategoryCurrencyType", + "CategoryIdentification1", + "CategoryIdentificationSub", + "CategoryIdentificationType", + "CategoryInstruction", + "CategoryInstructionCount", + "CategoryInstructionIndicator", + "CategoryMargin", + "CategoryPartySub", + "CategoryPositionId", + "CategoryPositionIndicator", + "CategoryPositionReport", + "CategoryPriority1", + "CategoryPriorityId", + "CategoryReconciliation1", + "CategoryReconciliation2", + "CategoryReconciliationId", + "CategoryReconciliationSub", + "CategoryReferenceDate", + "CategoryRemittance1", + "CategoryRemittanceDate", + "CategoryRemittanceFlag", + "CategoryRemittanceReport", + "CategoryRouting", + "CategoryRoutingIndicator", + "CategorySequenceCount", + "CategorySequenceIndicator", + "CategorySettlement", + "CategoryStatusCount", + "CategoryStatusReport", + "CategoryTransaction", + "CategoryTransactionDate", + "CategoryTransmission", + "CategoryTransmissionCode", + "CategoryValueDateIndicator", + "CategoryVersion", + "CategoryVersion1", + "ChannelAccount2", + "ChannelAccountCode", + "ChannelAccountFlag", + "ChannelAdjustment", + "ChannelAdjustmentIndicator", + "ChannelAmount", + "ChannelAmountIndicator", + "ChannelAmountReport", + "ChannelAuthentication", + "ChannelAuthenticationReport", + "ChannelAuthenticationType", + "ChannelBeneficiary1", + "ChannelBeneficiaryDate", + "ChannelCategoryId", + "ChannelCategoryType", + "ChannelChannelCode", + "ChannelClearingDate", + "ChannelCollateralCount", + "ChannelCollateralReport", + "ChannelCollateralSub", + "ChannelIdentification", + "ChannelIdentificationFlag", + "ChannelIdentificationId", + "ChannelInstructionFlag", + "ChannelMargin2", + "ChannelMarginCount", + "ChannelMarginDate", + "ChannelMarginIndicator", + "ChannelParty", + "ChannelParty2", + "ChannelPosition", + "ChannelPositionId", + "ChannelPositionIndicator", + "ChannelPriority", + "ChannelPriority2", + "ChannelPriorityCount", + "ChannelReconciliation", + "ChannelReference", + "ChannelReferenceCode", + "ChannelReferenceFlag", + "ChannelRemittance", + "ChannelRemittanceFlag", + "ChannelRemittanceId", + "ChannelRoutingId", + "ChannelRoutingReport", + "ChannelRoutingSub", + "ChannelSettlement", + "ChannelSettlementSub", + "ChannelStatement1", + "ChannelStatementId", + "ChannelStatus1", + "ChannelTransactionCount", + "ChannelTransactionFlag", + "ChannelTransmissionIndicator", + "ChannelValueDate1", + "ChannelValueDateId", + "ChannelValueDateSub", + "ChannelValueDateType", + "ClearingAccount", + "ClearingAccountFlag", + "ClearingAdjustment", + "ClearingAdjustmentDate", + "ClearingAmountCount", + "ClearingAuthenticationDate", + "ClearingBeneficiaryId", + "ClearingBeneficiaryReport", + "ClearingClearingDate", + "ClearingClearingId", + "ClearingClearingReport", + "ClearingCollateral", + "ClearingCollateral2", + "ClearingCollateralCount", + "ClearingIdentificationType", + "ClearingInstructionCount", + "ClearingInstructionIndicator", + "ClearingMarginFlag", + "ClearingParty", + "ClearingPartyCount", + "ClearingPartyId", + "ClearingPartyIndicator", + "ClearingPosition", + "ClearingReconciliation", + "ClearingReconciliationSub", + "ClearingReferenceCode", + "ClearingReferenceSub", + "ClearingRemittanceReport", + "ClearingRouting", + "ClearingRoutingCount", + "ClearingSequenceCount", + "ClearingSequenceId", + "ClearingSettlement", + "ClearingStatementType", + "ClearingStatus2", + "ClearingStatusFlag", + "ClearingStatusId", + "ClearingTransactionIndicator", + "ClearingTransmissionCount", + "ClearingTransmissionReport", + "ClearingTransmissionType", + "ClearingValueDateCode", + "ClearingValueDateDate", + "CollateralAccount2", + "CollateralAccountCount", + "CollateralAdjustment2", + "CollateralAdjustmentDate", + "CollateralAdjustmentSub", + "CollateralAmountType", + "CollateralAuthentication2", + "CollateralAuthenticationCode", + "CollateralBeneficiary", + "CollateralBeneficiaryCode", + "CollateralBeneficiaryIndicator", + "CollateralCategory", + "CollateralCategory2", + "CollateralCategoryType", + "CollateralChannelCode", + "CollateralChannelCount", + "CollateralChannelDate", + "CollateralChannelReport", + "CollateralChannelSub", + "CollateralClearingDate", + "CollateralCollateralCode", + "CollateralCollateralCount", + "CollateralCollateralFlag", + "CollateralIdentification", + "CollateralInstructionCode", + "CollateralInstructionFlag", + "CollateralInstructionSub", + "CollateralMarginId", + "CollateralParty", + "CollateralPartyReport", + "CollateralPositionCount", + "CollateralPositionSub", + "CollateralPositionType", + "CollateralPriority2", + "CollateralPriorityDate", + "CollateralPriorityReport", + "CollateralReconciliation", + "CollateralReconciliation2", + "CollateralReconciliationCount", + "CollateralReconciliationDate", + "CollateralReference", + "CollateralReference2", + "CollateralReferenceType", + "CollateralRoutingReport", + "CollateralSequence2", + "CollateralSequenceCount", + "CollateralSettlementCode", + "CollateralSettlementDate", + "CollateralStatement", + "CollateralStatementReport", + "CollateralStatus", + "CollateralStatus2", + "CollateralStatusIndicator", + "CollateralTransaction2", + "CollateralTransmission2", + "CollateralValueDate2", + "CollateralValueDateCode", + "CollateralVersionIndicator", + "CollateralVersionType", + "CurrencyAccountId", + "CurrencyAccountSub", + "CurrencyAmountIndicator", + "CurrencyAuthentication", + "CurrencyBeneficiaryIndicator", + "CurrencyCategoryDate", + "CurrencyChannel2", + "CurrencyChannelCount", + "CurrencyChannelId", + "CurrencyClearing", + "CurrencyCollateralCount", + "CurrencyCollateralType", + "CurrencyCurrency", + "CurrencyCurrency1", + "CurrencyInstruction1", + "CurrencyInstructionSub", + "CurrencyMarginDate", + "CurrencyMarginId", + "CurrencyPartyCount", + "CurrencyPartyId", + "CurrencyPartySub", + "CurrencyPosition", + "CurrencyPositionCount", + "CurrencyPositionSub", + "CurrencyPriority", + "CurrencyReference2", + "CurrencyReferenceIndicator", + "CurrencyRouting", + "CurrencyRouting2", + "CurrencyRoutingId", + "CurrencySequenceCode", + "CurrencySequenceId", + "CurrencySequenceReport", + "CurrencySettlementType", + "CurrencyStatementSub", + "CurrencyStatusCode", + "CurrencyStatusType", + "CurrencyTransactionIndicator", + "CurrencyTransmissionDate", + "CurrencyValueDateIndicator", + "CurrencyVersionDate", + "IdentificationAccountIndicator", + "IdentificationAdjustmentCount", + "IdentificationAdjustmentIndicator", + "IdentificationAdjustmentReport", + "IdentificationAdjustmentType", + "IdentificationAmount", + "IdentificationAmountCount", + "IdentificationAmountId", + "IdentificationAmountSub", + "IdentificationAuthenticationId", + "IdentificationAuthenticationIndicator", + "IdentificationCategoryId", + "IdentificationClearing", + "IdentificationCollateral2", + "IdentificationCurrencyIndicator", + "IdentificationCurrencyType", + "IdentificationIdentificationCount", + "IdentificationIdentificationDate", + "IdentificationIdentificationSub", + "IdentificationInstructionDate", + "IdentificationInstructionId", + "IdentificationInstructionIndicator", + "IdentificationInstructionType", + "IdentificationMarginIndicator", + "IdentificationPartyFlag", + "IdentificationPartyType", + "IdentificationPositionType", + "IdentificationPriorityDate", + "IdentificationReconciliationCount", + "IdentificationReference1", + "IdentificationReferenceCode", + "IdentificationRouting", + "IdentificationSequence", + "IdentificationSequenceCode", + "IdentificationSequenceIndicator", + "IdentificationSettlement1", + "IdentificationSettlementId", + "IdentificationSettlementSub", + "IdentificationStatementFlag", + "IdentificationStatementIndicator", + "IdentificationStatementReport", + "IdentificationStatusCode", + "IdentificationStatusReport", + "IdentificationTransaction", + "IdentificationTransaction1", + "IdentificationTransactionType", + "IdentificationTransmission1", + "IdentificationValueDate", + "IdentificationValueDateCount", + "IdentificationValueDateDate", + "IdentificationValueDateId", + "IdentificationValueDateIndicator", + "IdentificationValueDateType", + "IdentificationVersion1", + "IdentificationVersionReport", + "InstructionAccount", + "InstructionAccountCode", + "InstructionAdjustment", + "InstructionAdjustmentId", + "InstructionAuthentication2", + "InstructionAuthenticationIndicator", + "InstructionBeneficiary", + "InstructionBeneficiaryDate", + "InstructionBeneficiaryReport", + "InstructionBeneficiaryType", + "InstructionCategoryCode", + "InstructionChannelIndicator", + "InstructionCurrency1", + "InstructionCurrencyCount", + "InstructionCurrencyId", + "InstructionIdentification", + "InstructionIdentificationType", + "InstructionInstruction2", + "InstructionInstructionIndicator", + "InstructionInstructionSub", + "InstructionMarginCode", + "InstructionParty", + "InstructionPositionCode", + "InstructionPositionType", + "InstructionPriority", + "InstructionPriorityFlag", + "InstructionPriorityId", + "InstructionReconciliation2", + "InstructionReconciliationType", + "InstructionReferenceIndicator", + "InstructionRemittanceCount", + "InstructionRemittanceId", + "InstructionSequenceFlag", + "InstructionSettlementType", + "InstructionStatusCode", + "InstructionStatusType", + "InstructionTransactionSub", + "InstructionTransmissionCode", + "InstructionTransmissionType", + "InstructionValueDate", + "InstructionValueDate1", + "InstructionValueDateIndicator", + "InstructionVersion2", + "MarginAccountIndicator", + "MarginAdjustmentDate", + "MarginAmount2", + "MarginAmountReport", + "MarginAuthentication2", + "MarginBeneficiary", + "MarginBeneficiaryReport", + "MarginBeneficiaryType", + "MarginCategory2", + "MarginChannel2", + "MarginClearing", + "MarginClearingCount", + "MarginCollateral1", + "MarginCurrencyCode", + "MarginIdentification", + "MarginIdentificationType", + "MarginInstruction1", + "MarginInstructionIndicator", + "MarginMarginDate", + "MarginMarginId", + "MarginPartyIndicator", + "MarginPositionCode", + "MarginPriorityDate", + "MarginPriorityFlag", + "MarginPrioritySub", + "MarginReconciliation", + "MarginReconciliation1", + "MarginReconciliationFlag", + "MarginReconciliationId", + "MarginReference1", + "MarginReferenceFlag", + "MarginReferenceId", + "MarginRemittance", + "MarginRemittanceType", + "MarginRouting", + "MarginRoutingType", + "MarginSequenceFlag", + "MarginSequenceId", + "MarginStatement", + "MarginStatementCount", + "MarginStatementReport", + "MarginStatusId", + "MarginTransactionCount", + "MarginTransactionFlag", + "MarginTransmissionId", + "MarginTransmissionSub", + "MarginValueDate2", + "PartyAccountReport", + "PartyAccountSub", + "PartyAccountType", + "PartyAdjustmentCode", + "PartyAdjustmentIndicator", + "PartyAdjustmentReport", + "PartyAmount1", + "PartyAmountDate", + "PartyAmountFlag", + "PartyAuthentication1", + "PartyCategoryCount", + "PartyChannel", + "PartyChannel2", + "PartyChannelSub", + "PartyCollateralReport", + "PartyCollateralSub", + "PartyCurrency", + "PartyCurrencyCode", + "PartyCurrencyType", + "PartyIdentificationIndicator", + "PartyInstructionDate", + "PartyInstructionId", + "PartyInstructionReport", + "PartyMarginType", + "PartyPosition2", + "PartyPositionId", + "PartyPositionType", + "PartyPriority", + "PartyPriority1", + "PartyPriorityCount", + "PartyReconciliationCode", + "PartyReconciliationFlag", + "PartyReconciliationId", + "PartyReferenceIndicator", + "PartyReferenceSub", + "PartyRemittanceType", + "PartyRouting1", + "PartyRouting2", + "PartyRoutingId", + "PartySequenceIndicator", + "PartySequenceReport", + "PartySettlement1", + "PartySettlementCount", + "PartyStatement", + "PartyStatementId", + "PartyStatementIndicator", + "PartyStatusId", + "PartyTransactionIndicator", + "PartyValueDateCount", + "PartyValueDateType", + "PartyVersion", + "PositionAccount", + "PositionAccount1", + "PositionAdjustmentCode", + "PositionAdjustmentDate", + "PositionAmount", + "PositionAmount1", + "PositionAmountSub", + "PositionAuthenticationFlag", + "PositionBeneficiaryFlag", + "PositionBeneficiaryReport", + "PositionCategorySub", + "PositionChannelFlag", + "PositionClearingReport", + "PositionClearingType", + "PositionCollateralCode", + "PositionCurrency2", + "PositionCurrencyReport", + "PositionCurrencySub", + "PositionIdentification1", + "PositionIdentificationCode", + "PositionInstruction", + "PositionInstructionReport", + "PositionMarginCode", + "PositionMarginCount", + "PositionMarginFlag", + "PositionMarginType", + "PositionPartyFlag", + "PositionPartyIndicator", + "PositionPositionReport", + "PositionPriorityFlag", + "PositionPriorityIndicator", + "PositionReconciliation1", + "PositionReconciliationReport", + "PositionReferenceSub", + "PositionRemittanceIndicator", + "PositionRoutingCount", + "PositionRoutingIndicator", + "PositionSequence", + "PositionSequenceCode", + "PositionSequenceDate", + "PositionSequenceIndicator", + "PositionStatement1", + "PositionStatementCount", + "PositionStatusSub", + "PositionTransmission", + "PositionTransmissionDate", + "PositionTransmissionType", + "PositionValueDate1", + "PositionValueDateFlag", + "PositionValueDateIndicator", + "PositionValueDateReport", + "PositionValueDateSub", + "PositionValueDateType", + "PriorityAccount1", + "PriorityAccountDate", + "PriorityAccountIndicator", + "PriorityAdjustment", + "PriorityAdjustment1", + "PriorityAdjustment2", + "PriorityAdjustmentCode", + "PriorityAdjustmentFlag", + "PriorityAdjustmentReport", + "PriorityAmount", + "PriorityAmountSub", + "PriorityAuthentication", + "PriorityAuthenticationDate", + "PriorityBeneficiaryCount", + "PriorityBeneficiaryIndicator", + "PriorityBeneficiaryType", + "PriorityChannelCount", + "PriorityChannelReport", + "PriorityChannelType", + "PriorityClearingType", + "PriorityCurrency", + "PriorityCurrencyId", + "PriorityCurrencyType", + "PriorityIdentificationDate", + "PriorityIdentificationId", + "PriorityInstruction2", + "PriorityInstructionCode", + "PriorityInstructionFlag", + "PriorityMargin2", + "PriorityMarginCount", + "PriorityParty", + "PriorityParty1", + "PriorityPartyDate", + "PriorityPartyFlag", + "PriorityPosition", + "PriorityPositionId", + "PriorityPositionType", + "PriorityPriority", + "PriorityReconciliation2", + "PriorityReconciliationCode", + "PriorityReconciliationFlag", + "PriorityReconciliationReport", + "PriorityReference1", + "PriorityReferenceCount", + "PriorityReferenceDate", + "PriorityReferenceId", + "PriorityRemittanceCode", + "PriorityRemittanceCount", + "PriorityRemittanceFlag", + "PriorityRoutingFlag", + "PrioritySequenceCount", + "PrioritySettlement", + "PriorityStatementCount", + "PriorityStatementFlag", + "PriorityStatementId", + "PriorityStatusId", + "PriorityStatusSub", + "PriorityTransactionCount", + "PriorityTransactionIndicator", + "PriorityTransmission", + "PriorityValueDate1", + "PriorityValueDate2", + "PriorityValueDateIndicator", + "PriorityVersion", + "PriorityVersionIndicator", + "PriorityVersionType", + "ReconciliationAccountReport", + "ReconciliationAdjustment", + "ReconciliationAmountCount", + "ReconciliationAuthenticationDate", + "ReconciliationBeneficiary1", + "ReconciliationBeneficiaryDate", + "ReconciliationChannel", + "ReconciliationChannelFlag", + "ReconciliationChannelSub", + "ReconciliationClearing1", + "ReconciliationClearingId", + "ReconciliationCurrency", + "ReconciliationIdentificationId", + "ReconciliationInstruction2", + "ReconciliationInstructionFlag", + "ReconciliationInstructionSub", + "ReconciliationMarginId", + "ReconciliationParty", + "ReconciliationPartyIndicator", + "ReconciliationPositionCount", + "ReconciliationPriority", + "ReconciliationPriority2", + "ReconciliationReconciliation1", + "ReconciliationReconciliationCount", + "ReconciliationReconciliationSub", + "ReconciliationReferenceCount", + "ReconciliationReferenceIndicator", + "ReconciliationRemittance", + "ReconciliationRemittanceId", + "ReconciliationRemittanceIndicator", + "ReconciliationRemittanceSub", + "ReconciliationSequenceCode", + "ReconciliationSequenceCount", + "ReconciliationStatusCount", + "ReconciliationStatusReport", + "ReconciliationTransactionDate", + "ReconciliationTransactionId", + "ReconciliationTransactionSub", + "ReconciliationTransactionType", + "ReconciliationTransmissionCount", + "ReconciliationTransmissionDate", + "ReconciliationValueDate2", + "ReconciliationValueDateType", + "ReconciliationVersion", + "ReconciliationVersionIndicator", + "ReconciliationVersionReport", + "ReferenceAccount", + "ReferenceAccount2", + "ReferenceAccountId", + "ReferenceAccountReport", + "ReferenceAmountCode", + "ReferenceAmountType", + "ReferenceAuthenticationSub", + "ReferenceBeneficiaryIndicator", + "ReferenceCategory2", + "ReferenceCategoryDate", + "ReferenceCategoryIndicator", + "ReferenceChannelReport", + "ReferenceClearing", + "ReferenceClearingId", + "ReferenceClearingType", + "ReferenceCollateral", + "ReferenceCollateral1", + "ReferenceCollateral2", + "ReferenceCurrency", + "ReferenceCurrencyCode", + "ReferenceIdentification", + "ReferenceIdentification1", + "ReferenceIdentificationCount", + "ReferenceIdentificationDate", + "ReferenceIdentificationReport", + "ReferenceInstructionDate", + "ReferenceInstructionFlag", + "ReferenceInstructionType", + "ReferenceMargin", + "ReferenceParty", + "ReferencePartySub", + "ReferencePositionId", + "ReferencePositionType", + "ReferencePriority", + "ReferencePriority1", + "ReferencePriorityReport", + "ReferenceReconciliation", + "ReferenceReconciliation1", + "ReferenceReconciliation2", + "ReferenceReferenceDate", + "ReferenceReferenceFlag", + "ReferenceRouting", + "ReferenceRoutingCount", + "ReferenceRoutingDate", + "ReferenceRoutingIndicator", + "ReferenceRoutingSub", + "ReferenceSequence", + "ReferenceSequenceId", + "ReferenceStatementDate", + "ReferenceTransactionDate", + "ReferenceTransactionSub", + "ReferenceTransmission", + "ReferenceTransmissionCode", + "ReferenceTransmissionCount", + "ReferenceValueDate2", + "ReferenceValueDateCode", + "ReferenceVersion1", + "ReferenceVersion2", + "RemittanceAccount", + "RemittanceAdjustmentDate", + "RemittanceAmountCode", + "RemittanceAmountDate", + "RemittanceAmountFlag", + "RemittanceAmountReport", + "RemittanceAuthentication", + "RemittanceAuthentication2", + "RemittanceAuthenticationCode", + "RemittanceAuthenticationDate", + "RemittanceBeneficiaryCount", + "RemittanceCategoryDate", + "RemittanceCategoryFlag", + "RemittanceChannel1", + "RemittanceClearing1", + "RemittanceClearingFlag", + "RemittanceClearingType", + "RemittanceCollateral", + "RemittanceCollateralCode", + "RemittanceCollateralSub", + "RemittanceCurrencyIndicator", + "RemittanceIdentificationReport", + "RemittanceIdentificationSub", + "RemittanceInstruction", + "RemittanceInstructionIndicator", + "RemittanceMarginId", + "RemittanceMarginSub", + "RemittancePositionId", + "RemittancePositionSub", + "RemittancePriorityReport", + "RemittanceReconciliation1", + "RemittanceReference", + "RemittanceReference1", + "RemittanceReferenceDate", + "RemittanceRemittance1", + "RemittanceRemittanceCount", + "RemittanceRemittanceDate", + "RemittanceRemittanceReport", + "RemittanceRemittanceType", + "RemittanceRouting", + "RemittanceRouting1", + "RemittanceRoutingSub", + "RemittanceSequenceReport", + "RemittanceSettlementFlag", + "RemittanceSettlementReport", + "RemittanceSettlementType", + "RemittanceStatementSub", + "RemittanceStatementType", + "RemittanceStatus2", + "RemittanceTransactionCount", + "RemittanceTransactionReport", + "RemittanceTransmission2", + "RemittanceTransmissionCode", + "RemittanceTransmissionFlag", + "RemittanceValueDateCount", + "RemittanceVersionDate", + "RemittanceVersionFlag", + "RemittanceVersionIndicator", + "RemittanceVersionSub", + "RoutingAccount", + "RoutingAccountIndicator", + "RoutingAdjustmentFlag", + "RoutingAdjustmentSub", + "RoutingAmountSub", + "RoutingAmountType", + "RoutingAuthentication", + "RoutingAuthenticationFlag", + "RoutingBeneficiaryDate", + "RoutingBeneficiaryIndicator", + "RoutingCategoryCount", + "RoutingCategorySub", + "RoutingChannel", + "RoutingChannel2", + "RoutingChannelId", + "RoutingClearing", + "RoutingClearingCode", + "RoutingCollateralIndicator", + "RoutingCurrency", + "RoutingCurrencyCount", + "RoutingCurrencyId", + "RoutingCurrencyReport", + "RoutingIdentificationSub", + "RoutingInstruction", + "RoutingInstructionId", + "RoutingInstructionIndicator", + "RoutingInstructionType", + "RoutingMarginType", + "RoutingParty2", + "RoutingPartyCode", + "RoutingPartyId", + "RoutingPartyIndicator", + "RoutingPartyReport", + "RoutingPartyType", + "RoutingPosition1", + "RoutingPositionFlag", + "RoutingPositionSub", + "RoutingPriorityId", + "RoutingReconciliation2", + "RoutingReconciliationCode", + "RoutingReconciliationId", + "RoutingReferenceReport", + "RoutingRouting", + "RoutingRoutingCode", + "RoutingSequenceReport", + "RoutingSequenceSub", + "RoutingSettlement1", + "RoutingStatement2", + "RoutingStatementCount", + "RoutingStatementDate", + "RoutingStatementId", + "RoutingTransaction1", + "RoutingTransactionDate", + "RoutingTransactionIndicator", + "RoutingTransmissionId", + "RoutingTransmissionSub", + "RoutingValueDate1", + "RoutingVersionDate", + "RoutingVersionReport", + "SequenceAccount2", + "SequenceAccountId", + "SequenceAdjustment", + "SequenceAmountCount", + "SequenceAmountReport", + "SequenceAuthentication2", + "SequenceAuthenticationDate", + "SequenceAuthenticationType", + "SequenceCategoryDate", + "SequenceCategorySub", + "SequenceChannelDate", + "SequenceClearingDate", + "SequenceClearingId", + "SequenceClearingIndicator", + "SequenceCollateralReport", + "SequenceCurrency", + "SequenceIdentificationDate", + "SequenceIdentificationId", + "SequenceInstructionSub", + "SequenceMargin", + "SequenceMarginCount", + "SequencePositionId", + "SequencePriorityCount", + "SequencePrioritySub", + "SequenceReconciliationDate", + "SequenceReference", + "SequenceReferenceIndicator", + "SequenceRemittanceCount", + "SequenceRemittanceFlag", + "SequenceRemittanceIndicator", + "SequenceRouting2", + "SequenceSettlement", + "SequenceSettlementIndicator", + "SequenceStatement", + "SequenceStatementType", + "SequenceStatusSub", + "SequenceTransactionFlag", + "SequenceTransactionSub", + "SequenceTransactionType", + "SequenceValueDateReport", + "SequenceValueDateSub", + "SequenceVersion2", + "SequenceVersionIndicator", + "SettlementAccount", + "SettlementAdjustment1", + "SettlementAdjustment2", + "SettlementAdjustmentFlag", + "SettlementAdjustmentReport", + "SettlementAmount1", + "SettlementAmount2", + "SettlementAmountIndicator", + "SettlementAuthentication2", + "SettlementAuthenticationCount", + "SettlementBeneficiaryFlag", + "SettlementBeneficiaryType", + "SettlementCategoryCode", + "SettlementChannel", + "SettlementChannelCode", + "SettlementClearingReport", + "SettlementCollateral2", + "SettlementCollateralCode", + "SettlementCollateralDate", + "SettlementCollateralSub", + "SettlementCurrency", + "SettlementCurrency2", + "SettlementIdentificationCount", + "SettlementIdentificationIndicator", + "SettlementInstruction", + "SettlementInstructionDate", + "SettlementMargin2", + "SettlementMarginId", + "SettlementMarginSub", + "SettlementPartyCode", + "SettlementPartyFlag", + "SettlementPartyType", + "SettlementPositionType", + "SettlementPriorityCount", + "SettlementPriorityFlag", + "SettlementPriorityIndicator", + "SettlementPriorityReport", + "SettlementReconciliation1", + "SettlementReconciliationSub", + "SettlementReferenceReport", + "SettlementRemittanceSub", + "SettlementSequence2", + "SettlementSequenceSub", + "SettlementSequenceType", + "SettlementSettlementId", + "SettlementSettlementReport", + "SettlementStatementCode", + "SettlementStatus1", + "SettlementTransaction", + "SettlementTransactionReport", + "SettlementTransmission", + "SettlementValueDate", + "SettlementValueDate2", + "SettlementValueDateFlag", + "SettlementValueDateSub", + "StatementAccount2", + "StatementAdjustmentDate", + "StatementAdjustmentIndicator", + "StatementAdjustmentReport", + "StatementAdjustmentSub", + "StatementAdjustmentType", + "StatementAmount", + "StatementAmount2", + "StatementAmountCode", + "StatementBeneficiaryCode", + "StatementBeneficiaryFlag", + "StatementBeneficiaryId", + "StatementBeneficiaryType", + "StatementCategory", + "StatementChannel1", + "StatementChannelCode", + "StatementChannelId", + "StatementCollateral", + "StatementCollateralSub", + "StatementCurrencyCode", + "StatementCurrencyType", + "StatementIdentification", + "StatementIdentificationReport", + "StatementInstructionReport", + "StatementMargin1", + "StatementMarginCount", + "StatementMarginReport", + "StatementMarginType", + "StatementParty", + "StatementPosition", + "StatementPositionDate", + "StatementReconciliationIndicator", + "StatementReference2", + "StatementReferenceCode", + "StatementReferenceSub", + "StatementRouting", + "StatementRoutingCode", + "StatementRoutingId", + "StatementRoutingIndicator", + "StatementRoutingType", + "StatementSequenceCode", + "StatementSequenceCount", + "StatementSequenceSub", + "StatementSettlementReport", + "StatementStatement2", + "StatementStatementId", + "StatementStatus2", + "StatementTransaction1", + "StatementTransactionIndicator", + "StatementTransactionSub", + "StatementTransmissionReport", + "StatementVersion1", + "StatementVersionCount", + "StatementVersionDate", + "StatementVersionType", + "StatusAccountId", + "StatusAccountIndicator", + "StatusAccountType", + "StatusAdjustmentCode", + "StatusAdjustmentDate", + "StatusAdjustmentFlag", + "StatusAmount", + "StatusAmount1", + "StatusAmountCode", + "StatusAmountDate", + "StatusAmountReport", + "StatusAuthenticationFlag", + "StatusAuthenticationReport", + "StatusBeneficiary", + "StatusCategory", + "StatusCategory2", + "StatusChannel", + "StatusChannel1", + "StatusChannelFlag", + "StatusChannelType", + "StatusClearing", + "StatusCollateral2", + "StatusCollateralCount", + "StatusCollateralDate", + "StatusCollateralFlag", + "StatusCollateralSub", + "StatusCurrencyDate", + "StatusCurrencySub", + "StatusCurrencyType", + "StatusIdentificationId", + "StatusInstructionCount", + "StatusInstructionIndicator", + "StatusMargin2", + "StatusMarginCode", + "StatusMarginId", + "StatusPartyReport", + "StatusPositionCount", + "StatusPositionFlag", + "StatusPositionIndicator", + "StatusPriority", + "StatusPriorityReport", + "StatusReconciliationDate", + "StatusReconciliationId", + "StatusReconciliationType", + "StatusRemittanceCount", + "StatusRemittanceDate", + "StatusRouting", + "StatusSequenceCode", + "StatusSettlementIndicator", + "StatusTransactionCode", + "StatusTransactionCount", + "StatusTransactionFlag", + "StatusTransactionReport", + "StatusTransmissionCount", + "StatusTransmissionType", + "StatusValueDate2", + "StatusValueDateCode", + "StatusValueDateCount", + "StatusVersion1", + "TransactionAccount", + "TransactionAccountCount", + "TransactionAdjustment1", + "TransactionAdjustmentCode", + "TransactionAdjustmentFlag", + "TransactionAmount", + "TransactionAmountCount", + "TransactionAmountIndicator", + "TransactionAuthenticationIndicator", + "TransactionBeneficiary2", + "TransactionBeneficiarySub", + "TransactionCategoryDate", + "TransactionCategoryFlag", + "TransactionChannelSub", + "TransactionChannelType", + "TransactionClearing", + "TransactionClearingReport", + "TransactionClearingSub", + "TransactionCollateral", + "TransactionCollateralFlag", + "TransactionCollateralId", + "TransactionIdentificationIndicator", + "TransactionInstruction", + "TransactionInstructionCode", + "TransactionInstructionSub", + "TransactionMargin2", + "TransactionMarginCount", + "TransactionPosition2", + "TransactionPositionIndicator", + "TransactionPriorityId", + "TransactionReconciliationCount", + "TransactionRemittanceFlag", + "TransactionRemittanceReport", + "TransactionRemittanceType", + "TransactionRoutingFlag", + "TransactionSequenceFlag", + "TransactionSequenceId", + "TransactionSequenceSub", + "TransactionSequenceType", + "TransactionSettlementDate", + "TransactionSettlementFlag", + "TransactionStatement", + "TransactionStatementIndicator", + "TransactionStatusType", + "TransactionTransactionDate", + "TransactionTransmissionCount", + "TransactionVersionCode", + "TransactionVersionType", + "TransmissionAdjustment1", + "TransmissionAdjustmentDate", + "TransmissionAdjustmentFlag", + "TransmissionAdjustmentType", + "TransmissionCategory", + "TransmissionCategoryFlag", + "TransmissionCategoryId", + "TransmissionChannel", + "TransmissionClearingId", + "TransmissionCollateralDate", + "TransmissionCurrencyIndicator", + "TransmissionCurrencyReport", + "TransmissionIdentification1", + "TransmissionInstruction", + "TransmissionInstruction2", + "TransmissionInstructionFlag", + "TransmissionInstructionType", + "TransmissionMargin2", + "TransmissionMarginDate", + "TransmissionPartyCode", + "TransmissionPartyDate", + "TransmissionPositionReport", + "TransmissionReconciliation2", + "TransmissionReconciliationSub", + "TransmissionReference1", + "TransmissionReferenceIndicator", + "TransmissionRemittanceDate", + "TransmissionRemittanceReport", + "TransmissionRoutingCount", + "TransmissionRoutingSub", + "TransmissionSequenceDate", + "TransmissionSettlementCount", + "TransmissionSettlementFlag", + "TransmissionStatement", + "TransmissionStatement2", + "TransmissionStatementCount", + "TransmissionStatusCode", + "TransmissionTransaction", + "TransmissionTransmissionDate", + "TransmissionValueDate", + "TransmissionValueDateId", + "TransmissionValueDateIndicator", + "ValueDateAccount2", + "ValueDateAccountCount", + "ValueDateAdjustment", + "ValueDateAdjustmentCount", + "ValueDateAdjustmentId", + "ValueDateAdjustmentReport", + "ValueDateAdjustmentType", + "ValueDateAmountCode", + "ValueDateAmountCount", + "ValueDateBeneficiary", + "ValueDateBeneficiarySub", + "ValueDateClearingDate", + "ValueDateCurrency", + "ValueDateIdentification2", + "ValueDateIdentificationFlag", + "ValueDateIdentificationIndicator", + "ValueDateIdentificationReport", + "ValueDateMargin", + "ValueDatePartyId", + "ValueDatePosition", + "ValueDatePositionType", + "ValueDatePriority", + "ValueDatePriority2", + "ValueDatePriorityCount", + "ValueDatePriorityReport", + "ValueDateReconciliationIndicator", + "ValueDateReferenceType", + "ValueDateRemittance2", + "ValueDateRemittanceReport", + "ValueDateRoutingReport", + "ValueDateSequence", + "ValueDateSequence1", + "ValueDateSequenceCount", + "ValueDateSequenceType", + "ValueDateSettlement2", + "ValueDateSettlementCount", + "ValueDateStatementCode", + "ValueDateStatementReport", + "ValueDateStatementSub", + "ValueDateStatusCode", + "ValueDateStatusType", + "ValueDateTransaction2", + "ValueDateTransactionCount", + "ValueDateTransactionId", + "ValueDateTransactionReport", + "ValueDateValueDateCode", + "ValueDateValueDateIndicator", + "ValueDateValueDateReport", + "ValueDateVersionFlag", + "ValueDateVersionSub", + "VersionAccount", + "VersionAccountCode", + "VersionAccountCount", + "VersionAccountIndicator", + "VersionAdjustmentIndicator", + "VersionAdjustmentSub", + "VersionAmount", + "VersionAmount2", + "VersionAmountCode", + "VersionAuthentication1", + "VersionAuthenticationCode", + "VersionBeneficiary1", + "VersionBeneficiary2", + "VersionBeneficiaryIndicator", + "VersionCategory2", + "VersionChannel", + "VersionChannelDate", + "VersionChannelFlag", + "VersionClearing", + "VersionClearingCode", + "VersionClearingCount", + "VersionClearingFlag", + "VersionClearingSub", + "VersionCollateralFlag", + "VersionCurrencyCode", + "VersionCurrencyFlag", + "VersionCurrencyIndicator", + "VersionIdentificationCount", + "VersionIdentificationDate", + "VersionIdentificationFlag", + "VersionIdentificationIndicator", + "VersionInstructionFlag", + "VersionMargin1", + "VersionMarginId", + "VersionPartyId", + "VersionPosition1", + "VersionPosition2", + "VersionPositionReport", + "VersionPriority2", + "VersionPriorityCode", + "VersionReconciliationCount", + "VersionReconciliationId", + "VersionReconciliationSub", + "VersionReference2", + "VersionReferenceCode", + "VersionRemittanceIndicator", + "VersionRoutingReport", + "VersionSequence2", + "VersionSequenceFlag", + "VersionSequenceType", + "VersionSettlement", + "VersionSettlementType", + "VersionStatement", + "VersionStatementFlag", + "VersionStatementId", + "VersionStatusIndicator", + "VersionTransactionCode", + "VersionTransactionCount", + "VersionTransmissionType", + "VersionValueDateCount", + "VersionValueDateFlag", +]; + +pub fn match_dispatch_1500(tag: &str) -> Option { + match tag { + "AccountAccount" => Some(0), + "AccountAccount1" => Some(1), + "AccountAdjustment2" => Some(2), + "AccountAmountDate" => Some(3), + "AccountAmountFlag" => Some(4), + "AccountAmountType" => Some(5), + "AccountAuthenticationType" => Some(6), + "AccountBeneficiaryCode" => Some(7), + "AccountBeneficiaryCount" => Some(8), + "AccountBeneficiaryFlag" => Some(9), + "AccountCategory" => Some(10), + "AccountCategoryDate" => Some(11), + "AccountCategoryFlag" => Some(12), + "AccountCategoryIndicator" => Some(13), + "AccountChannel" => Some(14), + "AccountChannelCount" => Some(15), + "AccountChannelId" => Some(16), + "AccountChannelIndicator" => Some(17), + "AccountClearing1" => Some(18), + "AccountCollateral2" => Some(19), + "AccountCollateralFlag" => Some(20), + "AccountCollateralReport" => Some(21), + "AccountCurrency1" => Some(22), + "AccountCurrencyFlag" => Some(23), + "AccountCurrencyId" => Some(24), + "AccountCurrencyIndicator" => Some(25), + "AccountCurrencySub" => Some(26), + "AccountIdentification" => Some(27), + "AccountIdentification1" => Some(28), + "AccountInstructionReport" => Some(29), + "AccountMarginFlag" => Some(30), + "AccountMarginType" => Some(31), + "AccountParty1" => Some(32), + "AccountPartyCount" => Some(33), + "AccountPartyFlag" => Some(34), + "AccountPartyIndicator" => Some(35), + "AccountPartyType" => Some(36), + "AccountPosition" => Some(37), + "AccountPriorityDate" => Some(38), + "AccountReconciliation" => Some(39), + "AccountReconciliationType" => Some(40), + "AccountReferenceId" => Some(41), + "AccountReferenceIndicator" => Some(42), + "AccountReferenceType" => Some(43), + "AccountRemittanceId" => Some(44), + "AccountRemittanceIndicator" => Some(45), + "AccountRoutingCount" => Some(46), + "AccountRoutingIndicator" => Some(47), + "AccountRoutingType" => Some(48), + "AccountSequence" => Some(49), + "AccountSequence1" => Some(50), + "AccountSequence2" => Some(51), + "AccountSettlementDate" => Some(52), + "AccountSettlementType" => Some(53), + "AccountStatement" => Some(54), + "AccountStatementCount" => Some(55), + "AccountStatusFlag" => Some(56), + "AccountStatusType" => Some(57), + "AccountTransaction" => Some(58), + "AccountTransactionCode" => Some(59), + "AccountTransactionSub" => Some(60), + "AccountTransactionType" => Some(61), + "AccountTransmission" => Some(62), + "AccountTransmission1" => Some(63), + "AccountVersion" => Some(64), + "AccountVersionSub" => Some(65), + "AccountVersionType" => Some(66), + "AdjustmentAccount" => Some(67), + "AdjustmentAccount1" => Some(68), + "AdjustmentAccountCode" => Some(69), + "AdjustmentAdjustment2" => Some(70), + "AdjustmentAdjustmentIndicator" => Some(71), + "AdjustmentAmount" => Some(72), + "AdjustmentAuthentication" => Some(73), + "AdjustmentAuthenticationFlag" => Some(74), + "AdjustmentBeneficiaryCount" => Some(75), + "AdjustmentBeneficiaryId" => Some(76), + "AdjustmentCategory1" => Some(77), + "AdjustmentChannelFlag" => Some(78), + "AdjustmentChannelType" => Some(79), + "AdjustmentClearingDate" => Some(80), + "AdjustmentClearingId" => Some(81), + "AdjustmentCollateralIndicator" => Some(82), + "AdjustmentCurrency" => Some(83), + "AdjustmentCurrencyId" => Some(84), + "AdjustmentCurrencyReport" => Some(85), + "AdjustmentIdentificationReport" => Some(86), + "AdjustmentInstructionDate" => Some(87), + "AdjustmentInstructionType" => Some(88), + "AdjustmentMarginCode" => Some(89), + "AdjustmentMarginReport" => Some(90), + "AdjustmentPosition2" => Some(91), + "AdjustmentPositionCount" => Some(92), + "AdjustmentPositionDate" => Some(93), + "AdjustmentReconciliation" => Some(94), + "AdjustmentReconciliationCount" => Some(95), + "AdjustmentReference1" => Some(96), + "AdjustmentRemittance" => Some(97), + "AdjustmentRemittance1" => Some(98), + "AdjustmentRemittanceFlag" => Some(99), + "AdjustmentRoutingIndicator" => Some(100), + "AdjustmentSequence" => Some(101), + "AdjustmentSequenceReport" => Some(102), + "AdjustmentSettlementFlag" => Some(103), + "AdjustmentStatement" => Some(104), + "AdjustmentStatementCode" => Some(105), + "AdjustmentStatementFlag" => Some(106), + "AdjustmentStatementId" => Some(107), + "AdjustmentStatementIndicator" => Some(108), + "AdjustmentStatusDate" => Some(109), + "AdjustmentStatusType" => Some(110), + "AdjustmentTransactionDate" => Some(111), + "AdjustmentTransmissionCode" => Some(112), + "AdjustmentTransmissionCount" => Some(113), + "AdjustmentTransmissionId" => Some(114), + "AdjustmentTransmissionSub" => Some(115), + "AdjustmentValueDateFlag" => Some(116), + "AdjustmentVersionType" => Some(117), + "AmountAccount1" => Some(118), + "AmountAccountCode" => Some(119), + "AmountAccountFlag" => Some(120), + "AmountAdjustment" => Some(121), + "AmountAdjustmentCode" => Some(122), + "AmountAdjustmentFlag" => Some(123), + "AmountAuthentication" => Some(124), + "AmountAuthenticationId" => Some(125), + "AmountAuthenticationIndicator" => Some(126), + "AmountChannel" => Some(127), + "AmountChannelCode" => Some(128), + "AmountChannelDate" => Some(129), + "AmountCollateralFlag" => Some(130), + "AmountCurrency" => Some(131), + "AmountCurrencyCount" => Some(132), + "AmountIdentificationIndicator" => Some(133), + "AmountIdentificationSub" => Some(134), + "AmountInstructionFlag" => Some(135), + "AmountInstructionReport" => Some(136), + "AmountPartyId" => Some(137), + "AmountPartyIndicator" => Some(138), + "AmountPriorityReport" => Some(139), + "AmountReconciliationIndicator" => Some(140), + "AmountReconciliationSub" => Some(141), + "AmountReference" => Some(142), + "AmountReferenceIndicator" => Some(143), + "AmountRemittance" => Some(144), + "AmountRemittance1" => Some(145), + "AmountRemittance2" => Some(146), + "AmountRemittanceFlag" => Some(147), + "AmountRemittanceReport" => Some(148), + "AmountSequenceDate" => Some(149), + "AmountSequenceReport" => Some(150), + "AmountSettlementDate" => Some(151), + "AmountStatementIndicator" => Some(152), + "AmountStatusFlag" => Some(153), + "AmountStatusIndicator" => Some(154), + "AmountTransaction" => Some(155), + "AmountTransactionCount" => Some(156), + "AmountTransmission2" => Some(157), + "AmountTransmissionCode" => Some(158), + "AmountTransmissionIndicator" => Some(159), + "AmountValueDate" => Some(160), + "AmountValueDate1" => Some(161), + "AmountVersionDate" => Some(162), + "AuthenticationAccountDate" => Some(163), + "AuthenticationAccountId" => Some(164), + "AuthenticationAccountType" => Some(165), + "AuthenticationAdjustmentType" => Some(166), + "AuthenticationAmountFlag" => Some(167), + "AuthenticationAuthentication1" => Some(168), + "AuthenticationBeneficiaryCode" => Some(169), + "AuthenticationBeneficiaryDate" => Some(170), + "AuthenticationBeneficiaryId" => Some(171), + "AuthenticationBeneficiaryIndicator" => Some(172), + "AuthenticationBeneficiarySub" => Some(173), + "AuthenticationCategory1" => Some(174), + "AuthenticationCategoryDate" => Some(175), + "AuthenticationCategoryFlag" => Some(176), + "AuthenticationChannel1" => Some(177), + "AuthenticationChannel2" => Some(178), + "AuthenticationChannelFlag" => Some(179), + "AuthenticationClearing" => Some(180), + "AuthenticationClearingIndicator" => Some(181), + "AuthenticationClearingReport" => Some(182), + "AuthenticationCollateralType" => Some(183), + "AuthenticationCurrencyIndicator" => Some(184), + "AuthenticationCurrencyType" => Some(185), + "AuthenticationIdentificationFlag" => Some(186), + "AuthenticationIdentificationReport" => Some(187), + "AuthenticationInstruction" => Some(188), + "AuthenticationInstructionId" => Some(189), + "AuthenticationMarginCount" => Some(190), + "AuthenticationPartyCode" => Some(191), + "AuthenticationPosition1" => Some(192), + "AuthenticationPositionFlag" => Some(193), + "AuthenticationPositionReport" => Some(194), + "AuthenticationPriorityCode" => Some(195), + "AuthenticationPriorityIndicator" => Some(196), + "AuthenticationReconciliationIndicator" => Some(197), + "AuthenticationReference2" => Some(198), + "AuthenticationReferenceFlag" => Some(199), + "AuthenticationReferenceId" => Some(200), + "AuthenticationRemittance1" => Some(201), + "AuthenticationRemittanceFlag" => Some(202), + "AuthenticationRemittanceIndicator" => Some(203), + "AuthenticationRemittanceType" => Some(204), + "AuthenticationRouting" => Some(205), + "AuthenticationRouting1" => Some(206), + "AuthenticationSequenceCode" => Some(207), + "AuthenticationSequenceId" => Some(208), + "AuthenticationSequenceIndicator" => Some(209), + "AuthenticationSequenceSub" => Some(210), + "AuthenticationSettlement" => Some(211), + "AuthenticationSettlement1" => Some(212), + "AuthenticationSettlementId" => Some(213), + "AuthenticationSettlementSub" => Some(214), + "AuthenticationStatementCount" => Some(215), + "AuthenticationStatementId" => Some(216), + "AuthenticationStatusId" => Some(217), + "AuthenticationTransaction1" => Some(218), + "AuthenticationTransactionDate" => Some(219), + "AuthenticationTransactionSub" => Some(220), + "AuthenticationTransactionType" => Some(221), + "AuthenticationTransmissionCode" => Some(222), + "AuthenticationTransmissionIndicator" => Some(223), + "AuthenticationTransmissionReport" => Some(224), + "AuthenticationValueDate2" => Some(225), + "AuthenticationVersion1" => Some(226), + "BeneficiaryAccount1" => Some(227), + "BeneficiaryAccountReport" => Some(228), + "BeneficiaryAccountType" => Some(229), + "BeneficiaryAdjustmentCount" => Some(230), + "BeneficiaryAdjustmentDate" => Some(231), + "BeneficiaryAdjustmentType" => Some(232), + "BeneficiaryAmountType" => Some(233), + "BeneficiaryAuthenticationCode" => Some(234), + "BeneficiaryAuthenticationSub" => Some(235), + "BeneficiaryAuthenticationType" => Some(236), + "BeneficiaryBeneficiary" => Some(237), + "BeneficiaryBeneficiary2" => Some(238), + "BeneficiaryBeneficiaryCode" => Some(239), + "BeneficiaryBeneficiaryCount" => Some(240), + "BeneficiaryBeneficiaryIndicator" => Some(241), + "BeneficiaryBeneficiaryReport" => Some(242), + "BeneficiaryCategory" => Some(243), + "BeneficiaryCategoryCode" => Some(244), + "BeneficiaryChannel2" => Some(245), + "BeneficiaryChannelCount" => Some(246), + "BeneficiaryChannelId" => Some(247), + "BeneficiaryCollateralCount" => Some(248), + "BeneficiaryCollateralFlag" => Some(249), + "BeneficiaryCollateralIndicator" => Some(250), + "BeneficiaryCurrencyType" => Some(251), + "BeneficiaryIdentification1" => Some(252), + "BeneficiaryIdentification2" => Some(253), + "BeneficiaryIdentificationReport" => Some(254), + "BeneficiaryInstructionReport" => Some(255), + "BeneficiaryInstructionSub" => Some(256), + "BeneficiaryMarginId" => Some(257), + "BeneficiaryPartyCode" => Some(258), + "BeneficiaryPartyFlag" => Some(259), + "BeneficiaryPartyIndicator" => Some(260), + "BeneficiaryPosition" => Some(261), + "BeneficiaryPriorityType" => Some(262), + "BeneficiaryReference1" => Some(263), + "BeneficiaryReferenceDate" => Some(264), + "BeneficiaryReferenceFlag" => Some(265), + "BeneficiaryReferenceReport" => Some(266), + "BeneficiaryRemittance" => Some(267), + "BeneficiaryRemittance2" => Some(268), + "BeneficiaryRemittanceCount" => Some(269), + "BeneficiaryRemittanceSub" => Some(270), + "BeneficiaryRoutingIndicator" => Some(271), + "BeneficiarySequenceId" => Some(272), + "BeneficiarySettlementIndicator" => Some(273), + "BeneficiaryStatus2" => Some(274), + "BeneficiaryStatusCount" => Some(275), + "BeneficiaryStatusDate" => Some(276), + "BeneficiaryTransactionSub" => Some(277), + "BeneficiaryTransmission" => Some(278), + "BeneficiaryTransmission1" => Some(279), + "BeneficiaryTransmission2" => Some(280), + "BeneficiaryTransmissionFlag" => Some(281), + "BeneficiaryTransmissionIndicator" => Some(282), + "BeneficiaryValueDate" => Some(283), + "BeneficiaryValueDate1" => Some(284), + "BeneficiaryVersionCount" => Some(285), + "BeneficiaryVersionFlag" => Some(286), + "CategoryAccount1" => Some(287), + "CategoryAccountDate" => Some(288), + "CategoryAccountFlag" => Some(289), + "CategoryAccountType" => Some(290), + "CategoryAdjustment1" => Some(291), + "CategoryAdjustmentDate" => Some(292), + "CategoryAmount" => Some(293), + "CategoryAmountIndicator" => Some(294), + "CategoryAuthentication1" => Some(295), + "CategoryAuthentication2" => Some(296), + "CategoryAuthenticationIndicator" => Some(297), + "CategoryAuthenticationType" => Some(298), + "CategoryBeneficiaryCode" => Some(299), + "CategoryBeneficiaryIndicator" => Some(300), + "CategoryChannel" => Some(301), + "CategoryChannelIndicator" => Some(302), + "CategoryClearingFlag" => Some(303), + "CategoryClearingType" => Some(304), + "CategoryCollateral1" => Some(305), + "CategoryCollateralFlag" => Some(306), + "CategoryCollateralType" => Some(307), + "CategoryCurrency" => Some(308), + "CategoryCurrency1" => Some(309), + "CategoryCurrencyReport" => Some(310), + "CategoryCurrencySub" => Some(311), + "CategoryCurrencyType" => Some(312), + "CategoryIdentification1" => Some(313), + "CategoryIdentificationSub" => Some(314), + "CategoryIdentificationType" => Some(315), + "CategoryInstruction" => Some(316), + "CategoryInstructionCount" => Some(317), + "CategoryInstructionIndicator" => Some(318), + "CategoryMargin" => Some(319), + "CategoryPartySub" => Some(320), + "CategoryPositionId" => Some(321), + "CategoryPositionIndicator" => Some(322), + "CategoryPositionReport" => Some(323), + "CategoryPriority1" => Some(324), + "CategoryPriorityId" => Some(325), + "CategoryReconciliation1" => Some(326), + "CategoryReconciliation2" => Some(327), + "CategoryReconciliationId" => Some(328), + "CategoryReconciliationSub" => Some(329), + "CategoryReferenceDate" => Some(330), + "CategoryRemittance1" => Some(331), + "CategoryRemittanceDate" => Some(332), + "CategoryRemittanceFlag" => Some(333), + "CategoryRemittanceReport" => Some(334), + "CategoryRouting" => Some(335), + "CategoryRoutingIndicator" => Some(336), + "CategorySequenceCount" => Some(337), + "CategorySequenceIndicator" => Some(338), + "CategorySettlement" => Some(339), + "CategoryStatusCount" => Some(340), + "CategoryStatusReport" => Some(341), + "CategoryTransaction" => Some(342), + "CategoryTransactionDate" => Some(343), + "CategoryTransmission" => Some(344), + "CategoryTransmissionCode" => Some(345), + "CategoryValueDateIndicator" => Some(346), + "CategoryVersion" => Some(347), + "CategoryVersion1" => Some(348), + "ChannelAccount2" => Some(349), + "ChannelAccountCode" => Some(350), + "ChannelAccountFlag" => Some(351), + "ChannelAdjustment" => Some(352), + "ChannelAdjustmentIndicator" => Some(353), + "ChannelAmount" => Some(354), + "ChannelAmountIndicator" => Some(355), + "ChannelAmountReport" => Some(356), + "ChannelAuthentication" => Some(357), + "ChannelAuthenticationReport" => Some(358), + "ChannelAuthenticationType" => Some(359), + "ChannelBeneficiary1" => Some(360), + "ChannelBeneficiaryDate" => Some(361), + "ChannelCategoryId" => Some(362), + "ChannelCategoryType" => Some(363), + "ChannelChannelCode" => Some(364), + "ChannelClearingDate" => Some(365), + "ChannelCollateralCount" => Some(366), + "ChannelCollateralReport" => Some(367), + "ChannelCollateralSub" => Some(368), + "ChannelIdentification" => Some(369), + "ChannelIdentificationFlag" => Some(370), + "ChannelIdentificationId" => Some(371), + "ChannelInstructionFlag" => Some(372), + "ChannelMargin2" => Some(373), + "ChannelMarginCount" => Some(374), + "ChannelMarginDate" => Some(375), + "ChannelMarginIndicator" => Some(376), + "ChannelParty" => Some(377), + "ChannelParty2" => Some(378), + "ChannelPosition" => Some(379), + "ChannelPositionId" => Some(380), + "ChannelPositionIndicator" => Some(381), + "ChannelPriority" => Some(382), + "ChannelPriority2" => Some(383), + "ChannelPriorityCount" => Some(384), + "ChannelReconciliation" => Some(385), + "ChannelReference" => Some(386), + "ChannelReferenceCode" => Some(387), + "ChannelReferenceFlag" => Some(388), + "ChannelRemittance" => Some(389), + "ChannelRemittanceFlag" => Some(390), + "ChannelRemittanceId" => Some(391), + "ChannelRoutingId" => Some(392), + "ChannelRoutingReport" => Some(393), + "ChannelRoutingSub" => Some(394), + "ChannelSettlement" => Some(395), + "ChannelSettlementSub" => Some(396), + "ChannelStatement1" => Some(397), + "ChannelStatementId" => Some(398), + "ChannelStatus1" => Some(399), + "ChannelTransactionCount" => Some(400), + "ChannelTransactionFlag" => Some(401), + "ChannelTransmissionIndicator" => Some(402), + "ChannelValueDate1" => Some(403), + "ChannelValueDateId" => Some(404), + "ChannelValueDateSub" => Some(405), + "ChannelValueDateType" => Some(406), + "ClearingAccount" => Some(407), + "ClearingAccountFlag" => Some(408), + "ClearingAdjustment" => Some(409), + "ClearingAdjustmentDate" => Some(410), + "ClearingAmountCount" => Some(411), + "ClearingAuthenticationDate" => Some(412), + "ClearingBeneficiaryId" => Some(413), + "ClearingBeneficiaryReport" => Some(414), + "ClearingClearingDate" => Some(415), + "ClearingClearingId" => Some(416), + "ClearingClearingReport" => Some(417), + "ClearingCollateral" => Some(418), + "ClearingCollateral2" => Some(419), + "ClearingCollateralCount" => Some(420), + "ClearingIdentificationType" => Some(421), + "ClearingInstructionCount" => Some(422), + "ClearingInstructionIndicator" => Some(423), + "ClearingMarginFlag" => Some(424), + "ClearingParty" => Some(425), + "ClearingPartyCount" => Some(426), + "ClearingPartyId" => Some(427), + "ClearingPartyIndicator" => Some(428), + "ClearingPosition" => Some(429), + "ClearingReconciliation" => Some(430), + "ClearingReconciliationSub" => Some(431), + "ClearingReferenceCode" => Some(432), + "ClearingReferenceSub" => Some(433), + "ClearingRemittanceReport" => Some(434), + "ClearingRouting" => Some(435), + "ClearingRoutingCount" => Some(436), + "ClearingSequenceCount" => Some(437), + "ClearingSequenceId" => Some(438), + "ClearingSettlement" => Some(439), + "ClearingStatementType" => Some(440), + "ClearingStatus2" => Some(441), + "ClearingStatusFlag" => Some(442), + "ClearingStatusId" => Some(443), + "ClearingTransactionIndicator" => Some(444), + "ClearingTransmissionCount" => Some(445), + "ClearingTransmissionReport" => Some(446), + "ClearingTransmissionType" => Some(447), + "ClearingValueDateCode" => Some(448), + "ClearingValueDateDate" => Some(449), + "CollateralAccount2" => Some(450), + "CollateralAccountCount" => Some(451), + "CollateralAdjustment2" => Some(452), + "CollateralAdjustmentDate" => Some(453), + "CollateralAdjustmentSub" => Some(454), + "CollateralAmountType" => Some(455), + "CollateralAuthentication2" => Some(456), + "CollateralAuthenticationCode" => Some(457), + "CollateralBeneficiary" => Some(458), + "CollateralBeneficiaryCode" => Some(459), + "CollateralBeneficiaryIndicator" => Some(460), + "CollateralCategory" => Some(461), + "CollateralCategory2" => Some(462), + "CollateralCategoryType" => Some(463), + "CollateralChannelCode" => Some(464), + "CollateralChannelCount" => Some(465), + "CollateralChannelDate" => Some(466), + "CollateralChannelReport" => Some(467), + "CollateralChannelSub" => Some(468), + "CollateralClearingDate" => Some(469), + "CollateralCollateralCode" => Some(470), + "CollateralCollateralCount" => Some(471), + "CollateralCollateralFlag" => Some(472), + "CollateralIdentification" => Some(473), + "CollateralInstructionCode" => Some(474), + "CollateralInstructionFlag" => Some(475), + "CollateralInstructionSub" => Some(476), + "CollateralMarginId" => Some(477), + "CollateralParty" => Some(478), + "CollateralPartyReport" => Some(479), + "CollateralPositionCount" => Some(480), + "CollateralPositionSub" => Some(481), + "CollateralPositionType" => Some(482), + "CollateralPriority2" => Some(483), + "CollateralPriorityDate" => Some(484), + "CollateralPriorityReport" => Some(485), + "CollateralReconciliation" => Some(486), + "CollateralReconciliation2" => Some(487), + "CollateralReconciliationCount" => Some(488), + "CollateralReconciliationDate" => Some(489), + "CollateralReference" => Some(490), + "CollateralReference2" => Some(491), + "CollateralReferenceType" => Some(492), + "CollateralRoutingReport" => Some(493), + "CollateralSequence2" => Some(494), + "CollateralSequenceCount" => Some(495), + "CollateralSettlementCode" => Some(496), + "CollateralSettlementDate" => Some(497), + "CollateralStatement" => Some(498), + "CollateralStatementReport" => Some(499), + "CollateralStatus" => Some(500), + "CollateralStatus2" => Some(501), + "CollateralStatusIndicator" => Some(502), + "CollateralTransaction2" => Some(503), + "CollateralTransmission2" => Some(504), + "CollateralValueDate2" => Some(505), + "CollateralValueDateCode" => Some(506), + "CollateralVersionIndicator" => Some(507), + "CollateralVersionType" => Some(508), + "CurrencyAccountId" => Some(509), + "CurrencyAccountSub" => Some(510), + "CurrencyAmountIndicator" => Some(511), + "CurrencyAuthentication" => Some(512), + "CurrencyBeneficiaryIndicator" => Some(513), + "CurrencyCategoryDate" => Some(514), + "CurrencyChannel2" => Some(515), + "CurrencyChannelCount" => Some(516), + "CurrencyChannelId" => Some(517), + "CurrencyClearing" => Some(518), + "CurrencyCollateralCount" => Some(519), + "CurrencyCollateralType" => Some(520), + "CurrencyCurrency" => Some(521), + "CurrencyCurrency1" => Some(522), + "CurrencyInstruction1" => Some(523), + "CurrencyInstructionSub" => Some(524), + "CurrencyMarginDate" => Some(525), + "CurrencyMarginId" => Some(526), + "CurrencyPartyCount" => Some(527), + "CurrencyPartyId" => Some(528), + "CurrencyPartySub" => Some(529), + "CurrencyPosition" => Some(530), + "CurrencyPositionCount" => Some(531), + "CurrencyPositionSub" => Some(532), + "CurrencyPriority" => Some(533), + "CurrencyReference2" => Some(534), + "CurrencyReferenceIndicator" => Some(535), + "CurrencyRouting" => Some(536), + "CurrencyRouting2" => Some(537), + "CurrencyRoutingId" => Some(538), + "CurrencySequenceCode" => Some(539), + "CurrencySequenceId" => Some(540), + "CurrencySequenceReport" => Some(541), + "CurrencySettlementType" => Some(542), + "CurrencyStatementSub" => Some(543), + "CurrencyStatusCode" => Some(544), + "CurrencyStatusType" => Some(545), + "CurrencyTransactionIndicator" => Some(546), + "CurrencyTransmissionDate" => Some(547), + "CurrencyValueDateIndicator" => Some(548), + "CurrencyVersionDate" => Some(549), + "IdentificationAccountIndicator" => Some(550), + "IdentificationAdjustmentCount" => Some(551), + "IdentificationAdjustmentIndicator" => Some(552), + "IdentificationAdjustmentReport" => Some(553), + "IdentificationAdjustmentType" => Some(554), + "IdentificationAmount" => Some(555), + "IdentificationAmountCount" => Some(556), + "IdentificationAmountId" => Some(557), + "IdentificationAmountSub" => Some(558), + "IdentificationAuthenticationId" => Some(559), + "IdentificationAuthenticationIndicator" => Some(560), + "IdentificationCategoryId" => Some(561), + "IdentificationClearing" => Some(562), + "IdentificationCollateral2" => Some(563), + "IdentificationCurrencyIndicator" => Some(564), + "IdentificationCurrencyType" => Some(565), + "IdentificationIdentificationCount" => Some(566), + "IdentificationIdentificationDate" => Some(567), + "IdentificationIdentificationSub" => Some(568), + "IdentificationInstructionDate" => Some(569), + "IdentificationInstructionId" => Some(570), + "IdentificationInstructionIndicator" => Some(571), + "IdentificationInstructionType" => Some(572), + "IdentificationMarginIndicator" => Some(573), + "IdentificationPartyFlag" => Some(574), + "IdentificationPartyType" => Some(575), + "IdentificationPositionType" => Some(576), + "IdentificationPriorityDate" => Some(577), + "IdentificationReconciliationCount" => Some(578), + "IdentificationReference1" => Some(579), + "IdentificationReferenceCode" => Some(580), + "IdentificationRouting" => Some(581), + "IdentificationSequence" => Some(582), + "IdentificationSequenceCode" => Some(583), + "IdentificationSequenceIndicator" => Some(584), + "IdentificationSettlement1" => Some(585), + "IdentificationSettlementId" => Some(586), + "IdentificationSettlementSub" => Some(587), + "IdentificationStatementFlag" => Some(588), + "IdentificationStatementIndicator" => Some(589), + "IdentificationStatementReport" => Some(590), + "IdentificationStatusCode" => Some(591), + "IdentificationStatusReport" => Some(592), + "IdentificationTransaction" => Some(593), + "IdentificationTransaction1" => Some(594), + "IdentificationTransactionType" => Some(595), + "IdentificationTransmission1" => Some(596), + "IdentificationValueDate" => Some(597), + "IdentificationValueDateCount" => Some(598), + "IdentificationValueDateDate" => Some(599), + "IdentificationValueDateId" => Some(600), + "IdentificationValueDateIndicator" => Some(601), + "IdentificationValueDateType" => Some(602), + "IdentificationVersion1" => Some(603), + "IdentificationVersionReport" => Some(604), + "InstructionAccount" => Some(605), + "InstructionAccountCode" => Some(606), + "InstructionAdjustment" => Some(607), + "InstructionAdjustmentId" => Some(608), + "InstructionAuthentication2" => Some(609), + "InstructionAuthenticationIndicator" => Some(610), + "InstructionBeneficiary" => Some(611), + "InstructionBeneficiaryDate" => Some(612), + "InstructionBeneficiaryReport" => Some(613), + "InstructionBeneficiaryType" => Some(614), + "InstructionCategoryCode" => Some(615), + "InstructionChannelIndicator" => Some(616), + "InstructionCurrency1" => Some(617), + "InstructionCurrencyCount" => Some(618), + "InstructionCurrencyId" => Some(619), + "InstructionIdentification" => Some(620), + "InstructionIdentificationType" => Some(621), + "InstructionInstruction2" => Some(622), + "InstructionInstructionIndicator" => Some(623), + "InstructionInstructionSub" => Some(624), + "InstructionMarginCode" => Some(625), + "InstructionParty" => Some(626), + "InstructionPositionCode" => Some(627), + "InstructionPositionType" => Some(628), + "InstructionPriority" => Some(629), + "InstructionPriorityFlag" => Some(630), + "InstructionPriorityId" => Some(631), + "InstructionReconciliation2" => Some(632), + "InstructionReconciliationType" => Some(633), + "InstructionReferenceIndicator" => Some(634), + "InstructionRemittanceCount" => Some(635), + "InstructionRemittanceId" => Some(636), + "InstructionSequenceFlag" => Some(637), + "InstructionSettlementType" => Some(638), + "InstructionStatusCode" => Some(639), + "InstructionStatusType" => Some(640), + "InstructionTransactionSub" => Some(641), + "InstructionTransmissionCode" => Some(642), + "InstructionTransmissionType" => Some(643), + "InstructionValueDate" => Some(644), + "InstructionValueDate1" => Some(645), + "InstructionValueDateIndicator" => Some(646), + "InstructionVersion2" => Some(647), + "MarginAccountIndicator" => Some(648), + "MarginAdjustmentDate" => Some(649), + "MarginAmount2" => Some(650), + "MarginAmountReport" => Some(651), + "MarginAuthentication2" => Some(652), + "MarginBeneficiary" => Some(653), + "MarginBeneficiaryReport" => Some(654), + "MarginBeneficiaryType" => Some(655), + "MarginCategory2" => Some(656), + "MarginChannel2" => Some(657), + "MarginClearing" => Some(658), + "MarginClearingCount" => Some(659), + "MarginCollateral1" => Some(660), + "MarginCurrencyCode" => Some(661), + "MarginIdentification" => Some(662), + "MarginIdentificationType" => Some(663), + "MarginInstruction1" => Some(664), + "MarginInstructionIndicator" => Some(665), + "MarginMarginDate" => Some(666), + "MarginMarginId" => Some(667), + "MarginPartyIndicator" => Some(668), + "MarginPositionCode" => Some(669), + "MarginPriorityDate" => Some(670), + "MarginPriorityFlag" => Some(671), + "MarginPrioritySub" => Some(672), + "MarginReconciliation" => Some(673), + "MarginReconciliation1" => Some(674), + "MarginReconciliationFlag" => Some(675), + "MarginReconciliationId" => Some(676), + "MarginReference1" => Some(677), + "MarginReferenceFlag" => Some(678), + "MarginReferenceId" => Some(679), + "MarginRemittance" => Some(680), + "MarginRemittanceType" => Some(681), + "MarginRouting" => Some(682), + "MarginRoutingType" => Some(683), + "MarginSequenceFlag" => Some(684), + "MarginSequenceId" => Some(685), + "MarginStatement" => Some(686), + "MarginStatementCount" => Some(687), + "MarginStatementReport" => Some(688), + "MarginStatusId" => Some(689), + "MarginTransactionCount" => Some(690), + "MarginTransactionFlag" => Some(691), + "MarginTransmissionId" => Some(692), + "MarginTransmissionSub" => Some(693), + "MarginValueDate2" => Some(694), + "PartyAccountReport" => Some(695), + "PartyAccountSub" => Some(696), + "PartyAccountType" => Some(697), + "PartyAdjustmentCode" => Some(698), + "PartyAdjustmentIndicator" => Some(699), + "PartyAdjustmentReport" => Some(700), + "PartyAmount1" => Some(701), + "PartyAmountDate" => Some(702), + "PartyAmountFlag" => Some(703), + "PartyAuthentication1" => Some(704), + "PartyCategoryCount" => Some(705), + "PartyChannel" => Some(706), + "PartyChannel2" => Some(707), + "PartyChannelSub" => Some(708), + "PartyCollateralReport" => Some(709), + "PartyCollateralSub" => Some(710), + "PartyCurrency" => Some(711), + "PartyCurrencyCode" => Some(712), + "PartyCurrencyType" => Some(713), + "PartyIdentificationIndicator" => Some(714), + "PartyInstructionDate" => Some(715), + "PartyInstructionId" => Some(716), + "PartyInstructionReport" => Some(717), + "PartyMarginType" => Some(718), + "PartyPosition2" => Some(719), + "PartyPositionId" => Some(720), + "PartyPositionType" => Some(721), + "PartyPriority" => Some(722), + "PartyPriority1" => Some(723), + "PartyPriorityCount" => Some(724), + "PartyReconciliationCode" => Some(725), + "PartyReconciliationFlag" => Some(726), + "PartyReconciliationId" => Some(727), + "PartyReferenceIndicator" => Some(728), + "PartyReferenceSub" => Some(729), + "PartyRemittanceType" => Some(730), + "PartyRouting1" => Some(731), + "PartyRouting2" => Some(732), + "PartyRoutingId" => Some(733), + "PartySequenceIndicator" => Some(734), + "PartySequenceReport" => Some(735), + "PartySettlement1" => Some(736), + "PartySettlementCount" => Some(737), + "PartyStatement" => Some(738), + "PartyStatementId" => Some(739), + "PartyStatementIndicator" => Some(740), + "PartyStatusId" => Some(741), + "PartyTransactionIndicator" => Some(742), + "PartyValueDateCount" => Some(743), + "PartyValueDateType" => Some(744), + "PartyVersion" => Some(745), + "PositionAccount" => Some(746), + "PositionAccount1" => Some(747), + "PositionAdjustmentCode" => Some(748), + "PositionAdjustmentDate" => Some(749), + "PositionAmount" => Some(750), + "PositionAmount1" => Some(751), + "PositionAmountSub" => Some(752), + "PositionAuthenticationFlag" => Some(753), + "PositionBeneficiaryFlag" => Some(754), + "PositionBeneficiaryReport" => Some(755), + "PositionCategorySub" => Some(756), + "PositionChannelFlag" => Some(757), + "PositionClearingReport" => Some(758), + "PositionClearingType" => Some(759), + "PositionCollateralCode" => Some(760), + "PositionCurrency2" => Some(761), + "PositionCurrencyReport" => Some(762), + "PositionCurrencySub" => Some(763), + "PositionIdentification1" => Some(764), + "PositionIdentificationCode" => Some(765), + "PositionInstruction" => Some(766), + "PositionInstructionReport" => Some(767), + "PositionMarginCode" => Some(768), + "PositionMarginCount" => Some(769), + "PositionMarginFlag" => Some(770), + "PositionMarginType" => Some(771), + "PositionPartyFlag" => Some(772), + "PositionPartyIndicator" => Some(773), + "PositionPositionReport" => Some(774), + "PositionPriorityFlag" => Some(775), + "PositionPriorityIndicator" => Some(776), + "PositionReconciliation1" => Some(777), + "PositionReconciliationReport" => Some(778), + "PositionReferenceSub" => Some(779), + "PositionRemittanceIndicator" => Some(780), + "PositionRoutingCount" => Some(781), + "PositionRoutingIndicator" => Some(782), + "PositionSequence" => Some(783), + "PositionSequenceCode" => Some(784), + "PositionSequenceDate" => Some(785), + "PositionSequenceIndicator" => Some(786), + "PositionStatement1" => Some(787), + "PositionStatementCount" => Some(788), + "PositionStatusSub" => Some(789), + "PositionTransmission" => Some(790), + "PositionTransmissionDate" => Some(791), + "PositionTransmissionType" => Some(792), + "PositionValueDate1" => Some(793), + "PositionValueDateFlag" => Some(794), + "PositionValueDateIndicator" => Some(795), + "PositionValueDateReport" => Some(796), + "PositionValueDateSub" => Some(797), + "PositionValueDateType" => Some(798), + "PriorityAccount1" => Some(799), + "PriorityAccountDate" => Some(800), + "PriorityAccountIndicator" => Some(801), + "PriorityAdjustment" => Some(802), + "PriorityAdjustment1" => Some(803), + "PriorityAdjustment2" => Some(804), + "PriorityAdjustmentCode" => Some(805), + "PriorityAdjustmentFlag" => Some(806), + "PriorityAdjustmentReport" => Some(807), + "PriorityAmount" => Some(808), + "PriorityAmountSub" => Some(809), + "PriorityAuthentication" => Some(810), + "PriorityAuthenticationDate" => Some(811), + "PriorityBeneficiaryCount" => Some(812), + "PriorityBeneficiaryIndicator" => Some(813), + "PriorityBeneficiaryType" => Some(814), + "PriorityChannelCount" => Some(815), + "PriorityChannelReport" => Some(816), + "PriorityChannelType" => Some(817), + "PriorityClearingType" => Some(818), + "PriorityCurrency" => Some(819), + "PriorityCurrencyId" => Some(820), + "PriorityCurrencyType" => Some(821), + "PriorityIdentificationDate" => Some(822), + "PriorityIdentificationId" => Some(823), + "PriorityInstruction2" => Some(824), + "PriorityInstructionCode" => Some(825), + "PriorityInstructionFlag" => Some(826), + "PriorityMargin2" => Some(827), + "PriorityMarginCount" => Some(828), + "PriorityParty" => Some(829), + "PriorityParty1" => Some(830), + "PriorityPartyDate" => Some(831), + "PriorityPartyFlag" => Some(832), + "PriorityPosition" => Some(833), + "PriorityPositionId" => Some(834), + "PriorityPositionType" => Some(835), + "PriorityPriority" => Some(836), + "PriorityReconciliation2" => Some(837), + "PriorityReconciliationCode" => Some(838), + "PriorityReconciliationFlag" => Some(839), + "PriorityReconciliationReport" => Some(840), + "PriorityReference1" => Some(841), + "PriorityReferenceCount" => Some(842), + "PriorityReferenceDate" => Some(843), + "PriorityReferenceId" => Some(844), + "PriorityRemittanceCode" => Some(845), + "PriorityRemittanceCount" => Some(846), + "PriorityRemittanceFlag" => Some(847), + "PriorityRoutingFlag" => Some(848), + "PrioritySequenceCount" => Some(849), + "PrioritySettlement" => Some(850), + "PriorityStatementCount" => Some(851), + "PriorityStatementFlag" => Some(852), + "PriorityStatementId" => Some(853), + "PriorityStatusId" => Some(854), + "PriorityStatusSub" => Some(855), + "PriorityTransactionCount" => Some(856), + "PriorityTransactionIndicator" => Some(857), + "PriorityTransmission" => Some(858), + "PriorityValueDate1" => Some(859), + "PriorityValueDate2" => Some(860), + "PriorityValueDateIndicator" => Some(861), + "PriorityVersion" => Some(862), + "PriorityVersionIndicator" => Some(863), + "PriorityVersionType" => Some(864), + "ReconciliationAccountReport" => Some(865), + "ReconciliationAdjustment" => Some(866), + "ReconciliationAmountCount" => Some(867), + "ReconciliationAuthenticationDate" => Some(868), + "ReconciliationBeneficiary1" => Some(869), + "ReconciliationBeneficiaryDate" => Some(870), + "ReconciliationChannel" => Some(871), + "ReconciliationChannelFlag" => Some(872), + "ReconciliationChannelSub" => Some(873), + "ReconciliationClearing1" => Some(874), + "ReconciliationClearingId" => Some(875), + "ReconciliationCurrency" => Some(876), + "ReconciliationIdentificationId" => Some(877), + "ReconciliationInstruction2" => Some(878), + "ReconciliationInstructionFlag" => Some(879), + "ReconciliationInstructionSub" => Some(880), + "ReconciliationMarginId" => Some(881), + "ReconciliationParty" => Some(882), + "ReconciliationPartyIndicator" => Some(883), + "ReconciliationPositionCount" => Some(884), + "ReconciliationPriority" => Some(885), + "ReconciliationPriority2" => Some(886), + "ReconciliationReconciliation1" => Some(887), + "ReconciliationReconciliationCount" => Some(888), + "ReconciliationReconciliationSub" => Some(889), + "ReconciliationReferenceCount" => Some(890), + "ReconciliationReferenceIndicator" => Some(891), + "ReconciliationRemittance" => Some(892), + "ReconciliationRemittanceId" => Some(893), + "ReconciliationRemittanceIndicator" => Some(894), + "ReconciliationRemittanceSub" => Some(895), + "ReconciliationSequenceCode" => Some(896), + "ReconciliationSequenceCount" => Some(897), + "ReconciliationStatusCount" => Some(898), + "ReconciliationStatusReport" => Some(899), + "ReconciliationTransactionDate" => Some(900), + "ReconciliationTransactionId" => Some(901), + "ReconciliationTransactionSub" => Some(902), + "ReconciliationTransactionType" => Some(903), + "ReconciliationTransmissionCount" => Some(904), + "ReconciliationTransmissionDate" => Some(905), + "ReconciliationValueDate2" => Some(906), + "ReconciliationValueDateType" => Some(907), + "ReconciliationVersion" => Some(908), + "ReconciliationVersionIndicator" => Some(909), + "ReconciliationVersionReport" => Some(910), + "ReferenceAccount" => Some(911), + "ReferenceAccount2" => Some(912), + "ReferenceAccountId" => Some(913), + "ReferenceAccountReport" => Some(914), + "ReferenceAmountCode" => Some(915), + "ReferenceAmountType" => Some(916), + "ReferenceAuthenticationSub" => Some(917), + "ReferenceBeneficiaryIndicator" => Some(918), + "ReferenceCategory2" => Some(919), + "ReferenceCategoryDate" => Some(920), + "ReferenceCategoryIndicator" => Some(921), + "ReferenceChannelReport" => Some(922), + "ReferenceClearing" => Some(923), + "ReferenceClearingId" => Some(924), + "ReferenceClearingType" => Some(925), + "ReferenceCollateral" => Some(926), + "ReferenceCollateral1" => Some(927), + "ReferenceCollateral2" => Some(928), + "ReferenceCurrency" => Some(929), + "ReferenceCurrencyCode" => Some(930), + "ReferenceIdentification" => Some(931), + "ReferenceIdentification1" => Some(932), + "ReferenceIdentificationCount" => Some(933), + "ReferenceIdentificationDate" => Some(934), + "ReferenceIdentificationReport" => Some(935), + "ReferenceInstructionDate" => Some(936), + "ReferenceInstructionFlag" => Some(937), + "ReferenceInstructionType" => Some(938), + "ReferenceMargin" => Some(939), + "ReferenceParty" => Some(940), + "ReferencePartySub" => Some(941), + "ReferencePositionId" => Some(942), + "ReferencePositionType" => Some(943), + "ReferencePriority" => Some(944), + "ReferencePriority1" => Some(945), + "ReferencePriorityReport" => Some(946), + "ReferenceReconciliation" => Some(947), + "ReferenceReconciliation1" => Some(948), + "ReferenceReconciliation2" => Some(949), + "ReferenceReferenceDate" => Some(950), + "ReferenceReferenceFlag" => Some(951), + "ReferenceRouting" => Some(952), + "ReferenceRoutingCount" => Some(953), + "ReferenceRoutingDate" => Some(954), + "ReferenceRoutingIndicator" => Some(955), + "ReferenceRoutingSub" => Some(956), + "ReferenceSequence" => Some(957), + "ReferenceSequenceId" => Some(958), + "ReferenceStatementDate" => Some(959), + "ReferenceTransactionDate" => Some(960), + "ReferenceTransactionSub" => Some(961), + "ReferenceTransmission" => Some(962), + "ReferenceTransmissionCode" => Some(963), + "ReferenceTransmissionCount" => Some(964), + "ReferenceValueDate2" => Some(965), + "ReferenceValueDateCode" => Some(966), + "ReferenceVersion1" => Some(967), + "ReferenceVersion2" => Some(968), + "RemittanceAccount" => Some(969), + "RemittanceAdjustmentDate" => Some(970), + "RemittanceAmountCode" => Some(971), + "RemittanceAmountDate" => Some(972), + "RemittanceAmountFlag" => Some(973), + "RemittanceAmountReport" => Some(974), + "RemittanceAuthentication" => Some(975), + "RemittanceAuthentication2" => Some(976), + "RemittanceAuthenticationCode" => Some(977), + "RemittanceAuthenticationDate" => Some(978), + "RemittanceBeneficiaryCount" => Some(979), + "RemittanceCategoryDate" => Some(980), + "RemittanceCategoryFlag" => Some(981), + "RemittanceChannel1" => Some(982), + "RemittanceClearing1" => Some(983), + "RemittanceClearingFlag" => Some(984), + "RemittanceClearingType" => Some(985), + "RemittanceCollateral" => Some(986), + "RemittanceCollateralCode" => Some(987), + "RemittanceCollateralSub" => Some(988), + "RemittanceCurrencyIndicator" => Some(989), + "RemittanceIdentificationReport" => Some(990), + "RemittanceIdentificationSub" => Some(991), + "RemittanceInstruction" => Some(992), + "RemittanceInstructionIndicator" => Some(993), + "RemittanceMarginId" => Some(994), + "RemittanceMarginSub" => Some(995), + "RemittancePositionId" => Some(996), + "RemittancePositionSub" => Some(997), + "RemittancePriorityReport" => Some(998), + "RemittanceReconciliation1" => Some(999), + "RemittanceReference" => Some(1000), + "RemittanceReference1" => Some(1001), + "RemittanceReferenceDate" => Some(1002), + "RemittanceRemittance1" => Some(1003), + "RemittanceRemittanceCount" => Some(1004), + "RemittanceRemittanceDate" => Some(1005), + "RemittanceRemittanceReport" => Some(1006), + "RemittanceRemittanceType" => Some(1007), + "RemittanceRouting" => Some(1008), + "RemittanceRouting1" => Some(1009), + "RemittanceRoutingSub" => Some(1010), + "RemittanceSequenceReport" => Some(1011), + "RemittanceSettlementFlag" => Some(1012), + "RemittanceSettlementReport" => Some(1013), + "RemittanceSettlementType" => Some(1014), + "RemittanceStatementSub" => Some(1015), + "RemittanceStatementType" => Some(1016), + "RemittanceStatus2" => Some(1017), + "RemittanceTransactionCount" => Some(1018), + "RemittanceTransactionReport" => Some(1019), + "RemittanceTransmission2" => Some(1020), + "RemittanceTransmissionCode" => Some(1021), + "RemittanceTransmissionFlag" => Some(1022), + "RemittanceValueDateCount" => Some(1023), + "RemittanceVersionDate" => Some(1024), + "RemittanceVersionFlag" => Some(1025), + "RemittanceVersionIndicator" => Some(1026), + "RemittanceVersionSub" => Some(1027), + "RoutingAccount" => Some(1028), + "RoutingAccountIndicator" => Some(1029), + "RoutingAdjustmentFlag" => Some(1030), + "RoutingAdjustmentSub" => Some(1031), + "RoutingAmountSub" => Some(1032), + "RoutingAmountType" => Some(1033), + "RoutingAuthentication" => Some(1034), + "RoutingAuthenticationFlag" => Some(1035), + "RoutingBeneficiaryDate" => Some(1036), + "RoutingBeneficiaryIndicator" => Some(1037), + "RoutingCategoryCount" => Some(1038), + "RoutingCategorySub" => Some(1039), + "RoutingChannel" => Some(1040), + "RoutingChannel2" => Some(1041), + "RoutingChannelId" => Some(1042), + "RoutingClearing" => Some(1043), + "RoutingClearingCode" => Some(1044), + "RoutingCollateralIndicator" => Some(1045), + "RoutingCurrency" => Some(1046), + "RoutingCurrencyCount" => Some(1047), + "RoutingCurrencyId" => Some(1048), + "RoutingCurrencyReport" => Some(1049), + "RoutingIdentificationSub" => Some(1050), + "RoutingInstruction" => Some(1051), + "RoutingInstructionId" => Some(1052), + "RoutingInstructionIndicator" => Some(1053), + "RoutingInstructionType" => Some(1054), + "RoutingMarginType" => Some(1055), + "RoutingParty2" => Some(1056), + "RoutingPartyCode" => Some(1057), + "RoutingPartyId" => Some(1058), + "RoutingPartyIndicator" => Some(1059), + "RoutingPartyReport" => Some(1060), + "RoutingPartyType" => Some(1061), + "RoutingPosition1" => Some(1062), + "RoutingPositionFlag" => Some(1063), + "RoutingPositionSub" => Some(1064), + "RoutingPriorityId" => Some(1065), + "RoutingReconciliation2" => Some(1066), + "RoutingReconciliationCode" => Some(1067), + "RoutingReconciliationId" => Some(1068), + "RoutingReferenceReport" => Some(1069), + "RoutingRouting" => Some(1070), + "RoutingRoutingCode" => Some(1071), + "RoutingSequenceReport" => Some(1072), + "RoutingSequenceSub" => Some(1073), + "RoutingSettlement1" => Some(1074), + "RoutingStatement2" => Some(1075), + "RoutingStatementCount" => Some(1076), + "RoutingStatementDate" => Some(1077), + "RoutingStatementId" => Some(1078), + "RoutingTransaction1" => Some(1079), + "RoutingTransactionDate" => Some(1080), + "RoutingTransactionIndicator" => Some(1081), + "RoutingTransmissionId" => Some(1082), + "RoutingTransmissionSub" => Some(1083), + "RoutingValueDate1" => Some(1084), + "RoutingVersionDate" => Some(1085), + "RoutingVersionReport" => Some(1086), + "SequenceAccount2" => Some(1087), + "SequenceAccountId" => Some(1088), + "SequenceAdjustment" => Some(1089), + "SequenceAmountCount" => Some(1090), + "SequenceAmountReport" => Some(1091), + "SequenceAuthentication2" => Some(1092), + "SequenceAuthenticationDate" => Some(1093), + "SequenceAuthenticationType" => Some(1094), + "SequenceCategoryDate" => Some(1095), + "SequenceCategorySub" => Some(1096), + "SequenceChannelDate" => Some(1097), + "SequenceClearingDate" => Some(1098), + "SequenceClearingId" => Some(1099), + "SequenceClearingIndicator" => Some(1100), + "SequenceCollateralReport" => Some(1101), + "SequenceCurrency" => Some(1102), + "SequenceIdentificationDate" => Some(1103), + "SequenceIdentificationId" => Some(1104), + "SequenceInstructionSub" => Some(1105), + "SequenceMargin" => Some(1106), + "SequenceMarginCount" => Some(1107), + "SequencePositionId" => Some(1108), + "SequencePriorityCount" => Some(1109), + "SequencePrioritySub" => Some(1110), + "SequenceReconciliationDate" => Some(1111), + "SequenceReference" => Some(1112), + "SequenceReferenceIndicator" => Some(1113), + "SequenceRemittanceCount" => Some(1114), + "SequenceRemittanceFlag" => Some(1115), + "SequenceRemittanceIndicator" => Some(1116), + "SequenceRouting2" => Some(1117), + "SequenceSettlement" => Some(1118), + "SequenceSettlementIndicator" => Some(1119), + "SequenceStatement" => Some(1120), + "SequenceStatementType" => Some(1121), + "SequenceStatusSub" => Some(1122), + "SequenceTransactionFlag" => Some(1123), + "SequenceTransactionSub" => Some(1124), + "SequenceTransactionType" => Some(1125), + "SequenceValueDateReport" => Some(1126), + "SequenceValueDateSub" => Some(1127), + "SequenceVersion2" => Some(1128), + "SequenceVersionIndicator" => Some(1129), + "SettlementAccount" => Some(1130), + "SettlementAdjustment1" => Some(1131), + "SettlementAdjustment2" => Some(1132), + "SettlementAdjustmentFlag" => Some(1133), + "SettlementAdjustmentReport" => Some(1134), + "SettlementAmount1" => Some(1135), + "SettlementAmount2" => Some(1136), + "SettlementAmountIndicator" => Some(1137), + "SettlementAuthentication2" => Some(1138), + "SettlementAuthenticationCount" => Some(1139), + "SettlementBeneficiaryFlag" => Some(1140), + "SettlementBeneficiaryType" => Some(1141), + "SettlementCategoryCode" => Some(1142), + "SettlementChannel" => Some(1143), + "SettlementChannelCode" => Some(1144), + "SettlementClearingReport" => Some(1145), + "SettlementCollateral2" => Some(1146), + "SettlementCollateralCode" => Some(1147), + "SettlementCollateralDate" => Some(1148), + "SettlementCollateralSub" => Some(1149), + "SettlementCurrency" => Some(1150), + "SettlementCurrency2" => Some(1151), + "SettlementIdentificationCount" => Some(1152), + "SettlementIdentificationIndicator" => Some(1153), + "SettlementInstruction" => Some(1154), + "SettlementInstructionDate" => Some(1155), + "SettlementMargin2" => Some(1156), + "SettlementMarginId" => Some(1157), + "SettlementMarginSub" => Some(1158), + "SettlementPartyCode" => Some(1159), + "SettlementPartyFlag" => Some(1160), + "SettlementPartyType" => Some(1161), + "SettlementPositionType" => Some(1162), + "SettlementPriorityCount" => Some(1163), + "SettlementPriorityFlag" => Some(1164), + "SettlementPriorityIndicator" => Some(1165), + "SettlementPriorityReport" => Some(1166), + "SettlementReconciliation1" => Some(1167), + "SettlementReconciliationSub" => Some(1168), + "SettlementReferenceReport" => Some(1169), + "SettlementRemittanceSub" => Some(1170), + "SettlementSequence2" => Some(1171), + "SettlementSequenceSub" => Some(1172), + "SettlementSequenceType" => Some(1173), + "SettlementSettlementId" => Some(1174), + "SettlementSettlementReport" => Some(1175), + "SettlementStatementCode" => Some(1176), + "SettlementStatus1" => Some(1177), + "SettlementTransaction" => Some(1178), + "SettlementTransactionReport" => Some(1179), + "SettlementTransmission" => Some(1180), + "SettlementValueDate" => Some(1181), + "SettlementValueDate2" => Some(1182), + "SettlementValueDateFlag" => Some(1183), + "SettlementValueDateSub" => Some(1184), + "StatementAccount2" => Some(1185), + "StatementAdjustmentDate" => Some(1186), + "StatementAdjustmentIndicator" => Some(1187), + "StatementAdjustmentReport" => Some(1188), + "StatementAdjustmentSub" => Some(1189), + "StatementAdjustmentType" => Some(1190), + "StatementAmount" => Some(1191), + "StatementAmount2" => Some(1192), + "StatementAmountCode" => Some(1193), + "StatementBeneficiaryCode" => Some(1194), + "StatementBeneficiaryFlag" => Some(1195), + "StatementBeneficiaryId" => Some(1196), + "StatementBeneficiaryType" => Some(1197), + "StatementCategory" => Some(1198), + "StatementChannel1" => Some(1199), + "StatementChannelCode" => Some(1200), + "StatementChannelId" => Some(1201), + "StatementCollateral" => Some(1202), + "StatementCollateralSub" => Some(1203), + "StatementCurrencyCode" => Some(1204), + "StatementCurrencyType" => Some(1205), + "StatementIdentification" => Some(1206), + "StatementIdentificationReport" => Some(1207), + "StatementInstructionReport" => Some(1208), + "StatementMargin1" => Some(1209), + "StatementMarginCount" => Some(1210), + "StatementMarginReport" => Some(1211), + "StatementMarginType" => Some(1212), + "StatementParty" => Some(1213), + "StatementPosition" => Some(1214), + "StatementPositionDate" => Some(1215), + "StatementReconciliationIndicator" => Some(1216), + "StatementReference2" => Some(1217), + "StatementReferenceCode" => Some(1218), + "StatementReferenceSub" => Some(1219), + "StatementRouting" => Some(1220), + "StatementRoutingCode" => Some(1221), + "StatementRoutingId" => Some(1222), + "StatementRoutingIndicator" => Some(1223), + "StatementRoutingType" => Some(1224), + "StatementSequenceCode" => Some(1225), + "StatementSequenceCount" => Some(1226), + "StatementSequenceSub" => Some(1227), + "StatementSettlementReport" => Some(1228), + "StatementStatement2" => Some(1229), + "StatementStatementId" => Some(1230), + "StatementStatus2" => Some(1231), + "StatementTransaction1" => Some(1232), + "StatementTransactionIndicator" => Some(1233), + "StatementTransactionSub" => Some(1234), + "StatementTransmissionReport" => Some(1235), + "StatementVersion1" => Some(1236), + "StatementVersionCount" => Some(1237), + "StatementVersionDate" => Some(1238), + "StatementVersionType" => Some(1239), + "StatusAccountId" => Some(1240), + "StatusAccountIndicator" => Some(1241), + "StatusAccountType" => Some(1242), + "StatusAdjustmentCode" => Some(1243), + "StatusAdjustmentDate" => Some(1244), + "StatusAdjustmentFlag" => Some(1245), + "StatusAmount" => Some(1246), + "StatusAmount1" => Some(1247), + "StatusAmountCode" => Some(1248), + "StatusAmountDate" => Some(1249), + "StatusAmountReport" => Some(1250), + "StatusAuthenticationFlag" => Some(1251), + "StatusAuthenticationReport" => Some(1252), + "StatusBeneficiary" => Some(1253), + "StatusCategory" => Some(1254), + "StatusCategory2" => Some(1255), + "StatusChannel" => Some(1256), + "StatusChannel1" => Some(1257), + "StatusChannelFlag" => Some(1258), + "StatusChannelType" => Some(1259), + "StatusClearing" => Some(1260), + "StatusCollateral2" => Some(1261), + "StatusCollateralCount" => Some(1262), + "StatusCollateralDate" => Some(1263), + "StatusCollateralFlag" => Some(1264), + "StatusCollateralSub" => Some(1265), + "StatusCurrencyDate" => Some(1266), + "StatusCurrencySub" => Some(1267), + "StatusCurrencyType" => Some(1268), + "StatusIdentificationId" => Some(1269), + "StatusInstructionCount" => Some(1270), + "StatusInstructionIndicator" => Some(1271), + "StatusMargin2" => Some(1272), + "StatusMarginCode" => Some(1273), + "StatusMarginId" => Some(1274), + "StatusPartyReport" => Some(1275), + "StatusPositionCount" => Some(1276), + "StatusPositionFlag" => Some(1277), + "StatusPositionIndicator" => Some(1278), + "StatusPriority" => Some(1279), + "StatusPriorityReport" => Some(1280), + "StatusReconciliationDate" => Some(1281), + "StatusReconciliationId" => Some(1282), + "StatusReconciliationType" => Some(1283), + "StatusRemittanceCount" => Some(1284), + "StatusRemittanceDate" => Some(1285), + "StatusRouting" => Some(1286), + "StatusSequenceCode" => Some(1287), + "StatusSettlementIndicator" => Some(1288), + "StatusTransactionCode" => Some(1289), + "StatusTransactionCount" => Some(1290), + "StatusTransactionFlag" => Some(1291), + "StatusTransactionReport" => Some(1292), + "StatusTransmissionCount" => Some(1293), + "StatusTransmissionType" => Some(1294), + "StatusValueDate2" => Some(1295), + "StatusValueDateCode" => Some(1296), + "StatusValueDateCount" => Some(1297), + "StatusVersion1" => Some(1298), + "TransactionAccount" => Some(1299), + "TransactionAccountCount" => Some(1300), + "TransactionAdjustment1" => Some(1301), + "TransactionAdjustmentCode" => Some(1302), + "TransactionAdjustmentFlag" => Some(1303), + "TransactionAmount" => Some(1304), + "TransactionAmountCount" => Some(1305), + "TransactionAmountIndicator" => Some(1306), + "TransactionAuthenticationIndicator" => Some(1307), + "TransactionBeneficiary2" => Some(1308), + "TransactionBeneficiarySub" => Some(1309), + "TransactionCategoryDate" => Some(1310), + "TransactionCategoryFlag" => Some(1311), + "TransactionChannelSub" => Some(1312), + "TransactionChannelType" => Some(1313), + "TransactionClearing" => Some(1314), + "TransactionClearingReport" => Some(1315), + "TransactionClearingSub" => Some(1316), + "TransactionCollateral" => Some(1317), + "TransactionCollateralFlag" => Some(1318), + "TransactionCollateralId" => Some(1319), + "TransactionIdentificationIndicator" => Some(1320), + "TransactionInstruction" => Some(1321), + "TransactionInstructionCode" => Some(1322), + "TransactionInstructionSub" => Some(1323), + "TransactionMargin2" => Some(1324), + "TransactionMarginCount" => Some(1325), + "TransactionPosition2" => Some(1326), + "TransactionPositionIndicator" => Some(1327), + "TransactionPriorityId" => Some(1328), + "TransactionReconciliationCount" => Some(1329), + "TransactionRemittanceFlag" => Some(1330), + "TransactionRemittanceReport" => Some(1331), + "TransactionRemittanceType" => Some(1332), + "TransactionRoutingFlag" => Some(1333), + "TransactionSequenceFlag" => Some(1334), + "TransactionSequenceId" => Some(1335), + "TransactionSequenceSub" => Some(1336), + "TransactionSequenceType" => Some(1337), + "TransactionSettlementDate" => Some(1338), + "TransactionSettlementFlag" => Some(1339), + "TransactionStatement" => Some(1340), + "TransactionStatementIndicator" => Some(1341), + "TransactionStatusType" => Some(1342), + "TransactionTransactionDate" => Some(1343), + "TransactionTransmissionCount" => Some(1344), + "TransactionVersionCode" => Some(1345), + "TransactionVersionType" => Some(1346), + "TransmissionAdjustment1" => Some(1347), + "TransmissionAdjustmentDate" => Some(1348), + "TransmissionAdjustmentFlag" => Some(1349), + "TransmissionAdjustmentType" => Some(1350), + "TransmissionCategory" => Some(1351), + "TransmissionCategoryFlag" => Some(1352), + "TransmissionCategoryId" => Some(1353), + "TransmissionChannel" => Some(1354), + "TransmissionClearingId" => Some(1355), + "TransmissionCollateralDate" => Some(1356), + "TransmissionCurrencyIndicator" => Some(1357), + "TransmissionCurrencyReport" => Some(1358), + "TransmissionIdentification1" => Some(1359), + "TransmissionInstruction" => Some(1360), + "TransmissionInstruction2" => Some(1361), + "TransmissionInstructionFlag" => Some(1362), + "TransmissionInstructionType" => Some(1363), + "TransmissionMargin2" => Some(1364), + "TransmissionMarginDate" => Some(1365), + "TransmissionPartyCode" => Some(1366), + "TransmissionPartyDate" => Some(1367), + "TransmissionPositionReport" => Some(1368), + "TransmissionReconciliation2" => Some(1369), + "TransmissionReconciliationSub" => Some(1370), + "TransmissionReference1" => Some(1371), + "TransmissionReferenceIndicator" => Some(1372), + "TransmissionRemittanceDate" => Some(1373), + "TransmissionRemittanceReport" => Some(1374), + "TransmissionRoutingCount" => Some(1375), + "TransmissionRoutingSub" => Some(1376), + "TransmissionSequenceDate" => Some(1377), + "TransmissionSettlementCount" => Some(1378), + "TransmissionSettlementFlag" => Some(1379), + "TransmissionStatement" => Some(1380), + "TransmissionStatement2" => Some(1381), + "TransmissionStatementCount" => Some(1382), + "TransmissionStatusCode" => Some(1383), + "TransmissionTransaction" => Some(1384), + "TransmissionTransmissionDate" => Some(1385), + "TransmissionValueDate" => Some(1386), + "TransmissionValueDateId" => Some(1387), + "TransmissionValueDateIndicator" => Some(1388), + "ValueDateAccount2" => Some(1389), + "ValueDateAccountCount" => Some(1390), + "ValueDateAdjustment" => Some(1391), + "ValueDateAdjustmentCount" => Some(1392), + "ValueDateAdjustmentId" => Some(1393), + "ValueDateAdjustmentReport" => Some(1394), + "ValueDateAdjustmentType" => Some(1395), + "ValueDateAmountCode" => Some(1396), + "ValueDateAmountCount" => Some(1397), + "ValueDateBeneficiary" => Some(1398), + "ValueDateBeneficiarySub" => Some(1399), + "ValueDateClearingDate" => Some(1400), + "ValueDateCurrency" => Some(1401), + "ValueDateIdentification2" => Some(1402), + "ValueDateIdentificationFlag" => Some(1403), + "ValueDateIdentificationIndicator" => Some(1404), + "ValueDateIdentificationReport" => Some(1405), + "ValueDateMargin" => Some(1406), + "ValueDatePartyId" => Some(1407), + "ValueDatePosition" => Some(1408), + "ValueDatePositionType" => Some(1409), + "ValueDatePriority" => Some(1410), + "ValueDatePriority2" => Some(1411), + "ValueDatePriorityCount" => Some(1412), + "ValueDatePriorityReport" => Some(1413), + "ValueDateReconciliationIndicator" => Some(1414), + "ValueDateReferenceType" => Some(1415), + "ValueDateRemittance2" => Some(1416), + "ValueDateRemittanceReport" => Some(1417), + "ValueDateRoutingReport" => Some(1418), + "ValueDateSequence" => Some(1419), + "ValueDateSequence1" => Some(1420), + "ValueDateSequenceCount" => Some(1421), + "ValueDateSequenceType" => Some(1422), + "ValueDateSettlement2" => Some(1423), + "ValueDateSettlementCount" => Some(1424), + "ValueDateStatementCode" => Some(1425), + "ValueDateStatementReport" => Some(1426), + "ValueDateStatementSub" => Some(1427), + "ValueDateStatusCode" => Some(1428), + "ValueDateStatusType" => Some(1429), + "ValueDateTransaction2" => Some(1430), + "ValueDateTransactionCount" => Some(1431), + "ValueDateTransactionId" => Some(1432), + "ValueDateTransactionReport" => Some(1433), + "ValueDateValueDateCode" => Some(1434), + "ValueDateValueDateIndicator" => Some(1435), + "ValueDateValueDateReport" => Some(1436), + "ValueDateVersionFlag" => Some(1437), + "ValueDateVersionSub" => Some(1438), + "VersionAccount" => Some(1439), + "VersionAccountCode" => Some(1440), + "VersionAccountCount" => Some(1441), + "VersionAccountIndicator" => Some(1442), + "VersionAdjustmentIndicator" => Some(1443), + "VersionAdjustmentSub" => Some(1444), + "VersionAmount" => Some(1445), + "VersionAmount2" => Some(1446), + "VersionAmountCode" => Some(1447), + "VersionAuthentication1" => Some(1448), + "VersionAuthenticationCode" => Some(1449), + "VersionBeneficiary1" => Some(1450), + "VersionBeneficiary2" => Some(1451), + "VersionBeneficiaryIndicator" => Some(1452), + "VersionCategory2" => Some(1453), + "VersionChannel" => Some(1454), + "VersionChannelDate" => Some(1455), + "VersionChannelFlag" => Some(1456), + "VersionClearing" => Some(1457), + "VersionClearingCode" => Some(1458), + "VersionClearingCount" => Some(1459), + "VersionClearingFlag" => Some(1460), + "VersionClearingSub" => Some(1461), + "VersionCollateralFlag" => Some(1462), + "VersionCurrencyCode" => Some(1463), + "VersionCurrencyFlag" => Some(1464), + "VersionCurrencyIndicator" => Some(1465), + "VersionIdentificationCount" => Some(1466), + "VersionIdentificationDate" => Some(1467), + "VersionIdentificationFlag" => Some(1468), + "VersionIdentificationIndicator" => Some(1469), + "VersionInstructionFlag" => Some(1470), + "VersionMargin1" => Some(1471), + "VersionMarginId" => Some(1472), + "VersionPartyId" => Some(1473), + "VersionPosition1" => Some(1474), + "VersionPosition2" => Some(1475), + "VersionPositionReport" => Some(1476), + "VersionPriority2" => Some(1477), + "VersionPriorityCode" => Some(1478), + "VersionReconciliationCount" => Some(1479), + "VersionReconciliationId" => Some(1480), + "VersionReconciliationSub" => Some(1481), + "VersionReference2" => Some(1482), + "VersionReferenceCode" => Some(1483), + "VersionRemittanceIndicator" => Some(1484), + "VersionRoutingReport" => Some(1485), + "VersionSequence2" => Some(1486), + "VersionSequenceFlag" => Some(1487), + "VersionSequenceType" => Some(1488), + "VersionSettlement" => Some(1489), + "VersionSettlementType" => Some(1490), + "VersionStatement" => Some(1491), + "VersionStatementFlag" => Some(1492), + "VersionStatementId" => Some(1493), + "VersionStatusIndicator" => Some(1494), + "VersionTransactionCode" => Some(1495), + "VersionTransactionCount" => Some(1496), + "VersionTransmissionType" => Some(1497), + "VersionValueDateCount" => Some(1498), + "VersionValueDateFlag" => Some(1499), + _ => None, + } +} + +pub static PHF_1500: phf::Map<&'static str, u32> = phf::phf_map! { + "AccountAccount" => 0u32, + "AccountAccount1" => 1u32, + "AccountAdjustment2" => 2u32, + "AccountAmountDate" => 3u32, + "AccountAmountFlag" => 4u32, + "AccountAmountType" => 5u32, + "AccountAuthenticationType" => 6u32, + "AccountBeneficiaryCode" => 7u32, + "AccountBeneficiaryCount" => 8u32, + "AccountBeneficiaryFlag" => 9u32, + "AccountCategory" => 10u32, + "AccountCategoryDate" => 11u32, + "AccountCategoryFlag" => 12u32, + "AccountCategoryIndicator" => 13u32, + "AccountChannel" => 14u32, + "AccountChannelCount" => 15u32, + "AccountChannelId" => 16u32, + "AccountChannelIndicator" => 17u32, + "AccountClearing1" => 18u32, + "AccountCollateral2" => 19u32, + "AccountCollateralFlag" => 20u32, + "AccountCollateralReport" => 21u32, + "AccountCurrency1" => 22u32, + "AccountCurrencyFlag" => 23u32, + "AccountCurrencyId" => 24u32, + "AccountCurrencyIndicator" => 25u32, + "AccountCurrencySub" => 26u32, + "AccountIdentification" => 27u32, + "AccountIdentification1" => 28u32, + "AccountInstructionReport" => 29u32, + "AccountMarginFlag" => 30u32, + "AccountMarginType" => 31u32, + "AccountParty1" => 32u32, + "AccountPartyCount" => 33u32, + "AccountPartyFlag" => 34u32, + "AccountPartyIndicator" => 35u32, + "AccountPartyType" => 36u32, + "AccountPosition" => 37u32, + "AccountPriorityDate" => 38u32, + "AccountReconciliation" => 39u32, + "AccountReconciliationType" => 40u32, + "AccountReferenceId" => 41u32, + "AccountReferenceIndicator" => 42u32, + "AccountReferenceType" => 43u32, + "AccountRemittanceId" => 44u32, + "AccountRemittanceIndicator" => 45u32, + "AccountRoutingCount" => 46u32, + "AccountRoutingIndicator" => 47u32, + "AccountRoutingType" => 48u32, + "AccountSequence" => 49u32, + "AccountSequence1" => 50u32, + "AccountSequence2" => 51u32, + "AccountSettlementDate" => 52u32, + "AccountSettlementType" => 53u32, + "AccountStatement" => 54u32, + "AccountStatementCount" => 55u32, + "AccountStatusFlag" => 56u32, + "AccountStatusType" => 57u32, + "AccountTransaction" => 58u32, + "AccountTransactionCode" => 59u32, + "AccountTransactionSub" => 60u32, + "AccountTransactionType" => 61u32, + "AccountTransmission" => 62u32, + "AccountTransmission1" => 63u32, + "AccountVersion" => 64u32, + "AccountVersionSub" => 65u32, + "AccountVersionType" => 66u32, + "AdjustmentAccount" => 67u32, + "AdjustmentAccount1" => 68u32, + "AdjustmentAccountCode" => 69u32, + "AdjustmentAdjustment2" => 70u32, + "AdjustmentAdjustmentIndicator" => 71u32, + "AdjustmentAmount" => 72u32, + "AdjustmentAuthentication" => 73u32, + "AdjustmentAuthenticationFlag" => 74u32, + "AdjustmentBeneficiaryCount" => 75u32, + "AdjustmentBeneficiaryId" => 76u32, + "AdjustmentCategory1" => 77u32, + "AdjustmentChannelFlag" => 78u32, + "AdjustmentChannelType" => 79u32, + "AdjustmentClearingDate" => 80u32, + "AdjustmentClearingId" => 81u32, + "AdjustmentCollateralIndicator" => 82u32, + "AdjustmentCurrency" => 83u32, + "AdjustmentCurrencyId" => 84u32, + "AdjustmentCurrencyReport" => 85u32, + "AdjustmentIdentificationReport" => 86u32, + "AdjustmentInstructionDate" => 87u32, + "AdjustmentInstructionType" => 88u32, + "AdjustmentMarginCode" => 89u32, + "AdjustmentMarginReport" => 90u32, + "AdjustmentPosition2" => 91u32, + "AdjustmentPositionCount" => 92u32, + "AdjustmentPositionDate" => 93u32, + "AdjustmentReconciliation" => 94u32, + "AdjustmentReconciliationCount" => 95u32, + "AdjustmentReference1" => 96u32, + "AdjustmentRemittance" => 97u32, + "AdjustmentRemittance1" => 98u32, + "AdjustmentRemittanceFlag" => 99u32, + "AdjustmentRoutingIndicator" => 100u32, + "AdjustmentSequence" => 101u32, + "AdjustmentSequenceReport" => 102u32, + "AdjustmentSettlementFlag" => 103u32, + "AdjustmentStatement" => 104u32, + "AdjustmentStatementCode" => 105u32, + "AdjustmentStatementFlag" => 106u32, + "AdjustmentStatementId" => 107u32, + "AdjustmentStatementIndicator" => 108u32, + "AdjustmentStatusDate" => 109u32, + "AdjustmentStatusType" => 110u32, + "AdjustmentTransactionDate" => 111u32, + "AdjustmentTransmissionCode" => 112u32, + "AdjustmentTransmissionCount" => 113u32, + "AdjustmentTransmissionId" => 114u32, + "AdjustmentTransmissionSub" => 115u32, + "AdjustmentValueDateFlag" => 116u32, + "AdjustmentVersionType" => 117u32, + "AmountAccount1" => 118u32, + "AmountAccountCode" => 119u32, + "AmountAccountFlag" => 120u32, + "AmountAdjustment" => 121u32, + "AmountAdjustmentCode" => 122u32, + "AmountAdjustmentFlag" => 123u32, + "AmountAuthentication" => 124u32, + "AmountAuthenticationId" => 125u32, + "AmountAuthenticationIndicator" => 126u32, + "AmountChannel" => 127u32, + "AmountChannelCode" => 128u32, + "AmountChannelDate" => 129u32, + "AmountCollateralFlag" => 130u32, + "AmountCurrency" => 131u32, + "AmountCurrencyCount" => 132u32, + "AmountIdentificationIndicator" => 133u32, + "AmountIdentificationSub" => 134u32, + "AmountInstructionFlag" => 135u32, + "AmountInstructionReport" => 136u32, + "AmountPartyId" => 137u32, + "AmountPartyIndicator" => 138u32, + "AmountPriorityReport" => 139u32, + "AmountReconciliationIndicator" => 140u32, + "AmountReconciliationSub" => 141u32, + "AmountReference" => 142u32, + "AmountReferenceIndicator" => 143u32, + "AmountRemittance" => 144u32, + "AmountRemittance1" => 145u32, + "AmountRemittance2" => 146u32, + "AmountRemittanceFlag" => 147u32, + "AmountRemittanceReport" => 148u32, + "AmountSequenceDate" => 149u32, + "AmountSequenceReport" => 150u32, + "AmountSettlementDate" => 151u32, + "AmountStatementIndicator" => 152u32, + "AmountStatusFlag" => 153u32, + "AmountStatusIndicator" => 154u32, + "AmountTransaction" => 155u32, + "AmountTransactionCount" => 156u32, + "AmountTransmission2" => 157u32, + "AmountTransmissionCode" => 158u32, + "AmountTransmissionIndicator" => 159u32, + "AmountValueDate" => 160u32, + "AmountValueDate1" => 161u32, + "AmountVersionDate" => 162u32, + "AuthenticationAccountDate" => 163u32, + "AuthenticationAccountId" => 164u32, + "AuthenticationAccountType" => 165u32, + "AuthenticationAdjustmentType" => 166u32, + "AuthenticationAmountFlag" => 167u32, + "AuthenticationAuthentication1" => 168u32, + "AuthenticationBeneficiaryCode" => 169u32, + "AuthenticationBeneficiaryDate" => 170u32, + "AuthenticationBeneficiaryId" => 171u32, + "AuthenticationBeneficiaryIndicator" => 172u32, + "AuthenticationBeneficiarySub" => 173u32, + "AuthenticationCategory1" => 174u32, + "AuthenticationCategoryDate" => 175u32, + "AuthenticationCategoryFlag" => 176u32, + "AuthenticationChannel1" => 177u32, + "AuthenticationChannel2" => 178u32, + "AuthenticationChannelFlag" => 179u32, + "AuthenticationClearing" => 180u32, + "AuthenticationClearingIndicator" => 181u32, + "AuthenticationClearingReport" => 182u32, + "AuthenticationCollateralType" => 183u32, + "AuthenticationCurrencyIndicator" => 184u32, + "AuthenticationCurrencyType" => 185u32, + "AuthenticationIdentificationFlag" => 186u32, + "AuthenticationIdentificationReport" => 187u32, + "AuthenticationInstruction" => 188u32, + "AuthenticationInstructionId" => 189u32, + "AuthenticationMarginCount" => 190u32, + "AuthenticationPartyCode" => 191u32, + "AuthenticationPosition1" => 192u32, + "AuthenticationPositionFlag" => 193u32, + "AuthenticationPositionReport" => 194u32, + "AuthenticationPriorityCode" => 195u32, + "AuthenticationPriorityIndicator" => 196u32, + "AuthenticationReconciliationIndicator" => 197u32, + "AuthenticationReference2" => 198u32, + "AuthenticationReferenceFlag" => 199u32, + "AuthenticationReferenceId" => 200u32, + "AuthenticationRemittance1" => 201u32, + "AuthenticationRemittanceFlag" => 202u32, + "AuthenticationRemittanceIndicator" => 203u32, + "AuthenticationRemittanceType" => 204u32, + "AuthenticationRouting" => 205u32, + "AuthenticationRouting1" => 206u32, + "AuthenticationSequenceCode" => 207u32, + "AuthenticationSequenceId" => 208u32, + "AuthenticationSequenceIndicator" => 209u32, + "AuthenticationSequenceSub" => 210u32, + "AuthenticationSettlement" => 211u32, + "AuthenticationSettlement1" => 212u32, + "AuthenticationSettlementId" => 213u32, + "AuthenticationSettlementSub" => 214u32, + "AuthenticationStatementCount" => 215u32, + "AuthenticationStatementId" => 216u32, + "AuthenticationStatusId" => 217u32, + "AuthenticationTransaction1" => 218u32, + "AuthenticationTransactionDate" => 219u32, + "AuthenticationTransactionSub" => 220u32, + "AuthenticationTransactionType" => 221u32, + "AuthenticationTransmissionCode" => 222u32, + "AuthenticationTransmissionIndicator" => 223u32, + "AuthenticationTransmissionReport" => 224u32, + "AuthenticationValueDate2" => 225u32, + "AuthenticationVersion1" => 226u32, + "BeneficiaryAccount1" => 227u32, + "BeneficiaryAccountReport" => 228u32, + "BeneficiaryAccountType" => 229u32, + "BeneficiaryAdjustmentCount" => 230u32, + "BeneficiaryAdjustmentDate" => 231u32, + "BeneficiaryAdjustmentType" => 232u32, + "BeneficiaryAmountType" => 233u32, + "BeneficiaryAuthenticationCode" => 234u32, + "BeneficiaryAuthenticationSub" => 235u32, + "BeneficiaryAuthenticationType" => 236u32, + "BeneficiaryBeneficiary" => 237u32, + "BeneficiaryBeneficiary2" => 238u32, + "BeneficiaryBeneficiaryCode" => 239u32, + "BeneficiaryBeneficiaryCount" => 240u32, + "BeneficiaryBeneficiaryIndicator" => 241u32, + "BeneficiaryBeneficiaryReport" => 242u32, + "BeneficiaryCategory" => 243u32, + "BeneficiaryCategoryCode" => 244u32, + "BeneficiaryChannel2" => 245u32, + "BeneficiaryChannelCount" => 246u32, + "BeneficiaryChannelId" => 247u32, + "BeneficiaryCollateralCount" => 248u32, + "BeneficiaryCollateralFlag" => 249u32, + "BeneficiaryCollateralIndicator" => 250u32, + "BeneficiaryCurrencyType" => 251u32, + "BeneficiaryIdentification1" => 252u32, + "BeneficiaryIdentification2" => 253u32, + "BeneficiaryIdentificationReport" => 254u32, + "BeneficiaryInstructionReport" => 255u32, + "BeneficiaryInstructionSub" => 256u32, + "BeneficiaryMarginId" => 257u32, + "BeneficiaryPartyCode" => 258u32, + "BeneficiaryPartyFlag" => 259u32, + "BeneficiaryPartyIndicator" => 260u32, + "BeneficiaryPosition" => 261u32, + "BeneficiaryPriorityType" => 262u32, + "BeneficiaryReference1" => 263u32, + "BeneficiaryReferenceDate" => 264u32, + "BeneficiaryReferenceFlag" => 265u32, + "BeneficiaryReferenceReport" => 266u32, + "BeneficiaryRemittance" => 267u32, + "BeneficiaryRemittance2" => 268u32, + "BeneficiaryRemittanceCount" => 269u32, + "BeneficiaryRemittanceSub" => 270u32, + "BeneficiaryRoutingIndicator" => 271u32, + "BeneficiarySequenceId" => 272u32, + "BeneficiarySettlementIndicator" => 273u32, + "BeneficiaryStatus2" => 274u32, + "BeneficiaryStatusCount" => 275u32, + "BeneficiaryStatusDate" => 276u32, + "BeneficiaryTransactionSub" => 277u32, + "BeneficiaryTransmission" => 278u32, + "BeneficiaryTransmission1" => 279u32, + "BeneficiaryTransmission2" => 280u32, + "BeneficiaryTransmissionFlag" => 281u32, + "BeneficiaryTransmissionIndicator" => 282u32, + "BeneficiaryValueDate" => 283u32, + "BeneficiaryValueDate1" => 284u32, + "BeneficiaryVersionCount" => 285u32, + "BeneficiaryVersionFlag" => 286u32, + "CategoryAccount1" => 287u32, + "CategoryAccountDate" => 288u32, + "CategoryAccountFlag" => 289u32, + "CategoryAccountType" => 290u32, + "CategoryAdjustment1" => 291u32, + "CategoryAdjustmentDate" => 292u32, + "CategoryAmount" => 293u32, + "CategoryAmountIndicator" => 294u32, + "CategoryAuthentication1" => 295u32, + "CategoryAuthentication2" => 296u32, + "CategoryAuthenticationIndicator" => 297u32, + "CategoryAuthenticationType" => 298u32, + "CategoryBeneficiaryCode" => 299u32, + "CategoryBeneficiaryIndicator" => 300u32, + "CategoryChannel" => 301u32, + "CategoryChannelIndicator" => 302u32, + "CategoryClearingFlag" => 303u32, + "CategoryClearingType" => 304u32, + "CategoryCollateral1" => 305u32, + "CategoryCollateralFlag" => 306u32, + "CategoryCollateralType" => 307u32, + "CategoryCurrency" => 308u32, + "CategoryCurrency1" => 309u32, + "CategoryCurrencyReport" => 310u32, + "CategoryCurrencySub" => 311u32, + "CategoryCurrencyType" => 312u32, + "CategoryIdentification1" => 313u32, + "CategoryIdentificationSub" => 314u32, + "CategoryIdentificationType" => 315u32, + "CategoryInstruction" => 316u32, + "CategoryInstructionCount" => 317u32, + "CategoryInstructionIndicator" => 318u32, + "CategoryMargin" => 319u32, + "CategoryPartySub" => 320u32, + "CategoryPositionId" => 321u32, + "CategoryPositionIndicator" => 322u32, + "CategoryPositionReport" => 323u32, + "CategoryPriority1" => 324u32, + "CategoryPriorityId" => 325u32, + "CategoryReconciliation1" => 326u32, + "CategoryReconciliation2" => 327u32, + "CategoryReconciliationId" => 328u32, + "CategoryReconciliationSub" => 329u32, + "CategoryReferenceDate" => 330u32, + "CategoryRemittance1" => 331u32, + "CategoryRemittanceDate" => 332u32, + "CategoryRemittanceFlag" => 333u32, + "CategoryRemittanceReport" => 334u32, + "CategoryRouting" => 335u32, + "CategoryRoutingIndicator" => 336u32, + "CategorySequenceCount" => 337u32, + "CategorySequenceIndicator" => 338u32, + "CategorySettlement" => 339u32, + "CategoryStatusCount" => 340u32, + "CategoryStatusReport" => 341u32, + "CategoryTransaction" => 342u32, + "CategoryTransactionDate" => 343u32, + "CategoryTransmission" => 344u32, + "CategoryTransmissionCode" => 345u32, + "CategoryValueDateIndicator" => 346u32, + "CategoryVersion" => 347u32, + "CategoryVersion1" => 348u32, + "ChannelAccount2" => 349u32, + "ChannelAccountCode" => 350u32, + "ChannelAccountFlag" => 351u32, + "ChannelAdjustment" => 352u32, + "ChannelAdjustmentIndicator" => 353u32, + "ChannelAmount" => 354u32, + "ChannelAmountIndicator" => 355u32, + "ChannelAmountReport" => 356u32, + "ChannelAuthentication" => 357u32, + "ChannelAuthenticationReport" => 358u32, + "ChannelAuthenticationType" => 359u32, + "ChannelBeneficiary1" => 360u32, + "ChannelBeneficiaryDate" => 361u32, + "ChannelCategoryId" => 362u32, + "ChannelCategoryType" => 363u32, + "ChannelChannelCode" => 364u32, + "ChannelClearingDate" => 365u32, + "ChannelCollateralCount" => 366u32, + "ChannelCollateralReport" => 367u32, + "ChannelCollateralSub" => 368u32, + "ChannelIdentification" => 369u32, + "ChannelIdentificationFlag" => 370u32, + "ChannelIdentificationId" => 371u32, + "ChannelInstructionFlag" => 372u32, + "ChannelMargin2" => 373u32, + "ChannelMarginCount" => 374u32, + "ChannelMarginDate" => 375u32, + "ChannelMarginIndicator" => 376u32, + "ChannelParty" => 377u32, + "ChannelParty2" => 378u32, + "ChannelPosition" => 379u32, + "ChannelPositionId" => 380u32, + "ChannelPositionIndicator" => 381u32, + "ChannelPriority" => 382u32, + "ChannelPriority2" => 383u32, + "ChannelPriorityCount" => 384u32, + "ChannelReconciliation" => 385u32, + "ChannelReference" => 386u32, + "ChannelReferenceCode" => 387u32, + "ChannelReferenceFlag" => 388u32, + "ChannelRemittance" => 389u32, + "ChannelRemittanceFlag" => 390u32, + "ChannelRemittanceId" => 391u32, + "ChannelRoutingId" => 392u32, + "ChannelRoutingReport" => 393u32, + "ChannelRoutingSub" => 394u32, + "ChannelSettlement" => 395u32, + "ChannelSettlementSub" => 396u32, + "ChannelStatement1" => 397u32, + "ChannelStatementId" => 398u32, + "ChannelStatus1" => 399u32, + "ChannelTransactionCount" => 400u32, + "ChannelTransactionFlag" => 401u32, + "ChannelTransmissionIndicator" => 402u32, + "ChannelValueDate1" => 403u32, + "ChannelValueDateId" => 404u32, + "ChannelValueDateSub" => 405u32, + "ChannelValueDateType" => 406u32, + "ClearingAccount" => 407u32, + "ClearingAccountFlag" => 408u32, + "ClearingAdjustment" => 409u32, + "ClearingAdjustmentDate" => 410u32, + "ClearingAmountCount" => 411u32, + "ClearingAuthenticationDate" => 412u32, + "ClearingBeneficiaryId" => 413u32, + "ClearingBeneficiaryReport" => 414u32, + "ClearingClearingDate" => 415u32, + "ClearingClearingId" => 416u32, + "ClearingClearingReport" => 417u32, + "ClearingCollateral" => 418u32, + "ClearingCollateral2" => 419u32, + "ClearingCollateralCount" => 420u32, + "ClearingIdentificationType" => 421u32, + "ClearingInstructionCount" => 422u32, + "ClearingInstructionIndicator" => 423u32, + "ClearingMarginFlag" => 424u32, + "ClearingParty" => 425u32, + "ClearingPartyCount" => 426u32, + "ClearingPartyId" => 427u32, + "ClearingPartyIndicator" => 428u32, + "ClearingPosition" => 429u32, + "ClearingReconciliation" => 430u32, + "ClearingReconciliationSub" => 431u32, + "ClearingReferenceCode" => 432u32, + "ClearingReferenceSub" => 433u32, + "ClearingRemittanceReport" => 434u32, + "ClearingRouting" => 435u32, + "ClearingRoutingCount" => 436u32, + "ClearingSequenceCount" => 437u32, + "ClearingSequenceId" => 438u32, + "ClearingSettlement" => 439u32, + "ClearingStatementType" => 440u32, + "ClearingStatus2" => 441u32, + "ClearingStatusFlag" => 442u32, + "ClearingStatusId" => 443u32, + "ClearingTransactionIndicator" => 444u32, + "ClearingTransmissionCount" => 445u32, + "ClearingTransmissionReport" => 446u32, + "ClearingTransmissionType" => 447u32, + "ClearingValueDateCode" => 448u32, + "ClearingValueDateDate" => 449u32, + "CollateralAccount2" => 450u32, + "CollateralAccountCount" => 451u32, + "CollateralAdjustment2" => 452u32, + "CollateralAdjustmentDate" => 453u32, + "CollateralAdjustmentSub" => 454u32, + "CollateralAmountType" => 455u32, + "CollateralAuthentication2" => 456u32, + "CollateralAuthenticationCode" => 457u32, + "CollateralBeneficiary" => 458u32, + "CollateralBeneficiaryCode" => 459u32, + "CollateralBeneficiaryIndicator" => 460u32, + "CollateralCategory" => 461u32, + "CollateralCategory2" => 462u32, + "CollateralCategoryType" => 463u32, + "CollateralChannelCode" => 464u32, + "CollateralChannelCount" => 465u32, + "CollateralChannelDate" => 466u32, + "CollateralChannelReport" => 467u32, + "CollateralChannelSub" => 468u32, + "CollateralClearingDate" => 469u32, + "CollateralCollateralCode" => 470u32, + "CollateralCollateralCount" => 471u32, + "CollateralCollateralFlag" => 472u32, + "CollateralIdentification" => 473u32, + "CollateralInstructionCode" => 474u32, + "CollateralInstructionFlag" => 475u32, + "CollateralInstructionSub" => 476u32, + "CollateralMarginId" => 477u32, + "CollateralParty" => 478u32, + "CollateralPartyReport" => 479u32, + "CollateralPositionCount" => 480u32, + "CollateralPositionSub" => 481u32, + "CollateralPositionType" => 482u32, + "CollateralPriority2" => 483u32, + "CollateralPriorityDate" => 484u32, + "CollateralPriorityReport" => 485u32, + "CollateralReconciliation" => 486u32, + "CollateralReconciliation2" => 487u32, + "CollateralReconciliationCount" => 488u32, + "CollateralReconciliationDate" => 489u32, + "CollateralReference" => 490u32, + "CollateralReference2" => 491u32, + "CollateralReferenceType" => 492u32, + "CollateralRoutingReport" => 493u32, + "CollateralSequence2" => 494u32, + "CollateralSequenceCount" => 495u32, + "CollateralSettlementCode" => 496u32, + "CollateralSettlementDate" => 497u32, + "CollateralStatement" => 498u32, + "CollateralStatementReport" => 499u32, + "CollateralStatus" => 500u32, + "CollateralStatus2" => 501u32, + "CollateralStatusIndicator" => 502u32, + "CollateralTransaction2" => 503u32, + "CollateralTransmission2" => 504u32, + "CollateralValueDate2" => 505u32, + "CollateralValueDateCode" => 506u32, + "CollateralVersionIndicator" => 507u32, + "CollateralVersionType" => 508u32, + "CurrencyAccountId" => 509u32, + "CurrencyAccountSub" => 510u32, + "CurrencyAmountIndicator" => 511u32, + "CurrencyAuthentication" => 512u32, + "CurrencyBeneficiaryIndicator" => 513u32, + "CurrencyCategoryDate" => 514u32, + "CurrencyChannel2" => 515u32, + "CurrencyChannelCount" => 516u32, + "CurrencyChannelId" => 517u32, + "CurrencyClearing" => 518u32, + "CurrencyCollateralCount" => 519u32, + "CurrencyCollateralType" => 520u32, + "CurrencyCurrency" => 521u32, + "CurrencyCurrency1" => 522u32, + "CurrencyInstruction1" => 523u32, + "CurrencyInstructionSub" => 524u32, + "CurrencyMarginDate" => 525u32, + "CurrencyMarginId" => 526u32, + "CurrencyPartyCount" => 527u32, + "CurrencyPartyId" => 528u32, + "CurrencyPartySub" => 529u32, + "CurrencyPosition" => 530u32, + "CurrencyPositionCount" => 531u32, + "CurrencyPositionSub" => 532u32, + "CurrencyPriority" => 533u32, + "CurrencyReference2" => 534u32, + "CurrencyReferenceIndicator" => 535u32, + "CurrencyRouting" => 536u32, + "CurrencyRouting2" => 537u32, + "CurrencyRoutingId" => 538u32, + "CurrencySequenceCode" => 539u32, + "CurrencySequenceId" => 540u32, + "CurrencySequenceReport" => 541u32, + "CurrencySettlementType" => 542u32, + "CurrencyStatementSub" => 543u32, + "CurrencyStatusCode" => 544u32, + "CurrencyStatusType" => 545u32, + "CurrencyTransactionIndicator" => 546u32, + "CurrencyTransmissionDate" => 547u32, + "CurrencyValueDateIndicator" => 548u32, + "CurrencyVersionDate" => 549u32, + "IdentificationAccountIndicator" => 550u32, + "IdentificationAdjustmentCount" => 551u32, + "IdentificationAdjustmentIndicator" => 552u32, + "IdentificationAdjustmentReport" => 553u32, + "IdentificationAdjustmentType" => 554u32, + "IdentificationAmount" => 555u32, + "IdentificationAmountCount" => 556u32, + "IdentificationAmountId" => 557u32, + "IdentificationAmountSub" => 558u32, + "IdentificationAuthenticationId" => 559u32, + "IdentificationAuthenticationIndicator" => 560u32, + "IdentificationCategoryId" => 561u32, + "IdentificationClearing" => 562u32, + "IdentificationCollateral2" => 563u32, + "IdentificationCurrencyIndicator" => 564u32, + "IdentificationCurrencyType" => 565u32, + "IdentificationIdentificationCount" => 566u32, + "IdentificationIdentificationDate" => 567u32, + "IdentificationIdentificationSub" => 568u32, + "IdentificationInstructionDate" => 569u32, + "IdentificationInstructionId" => 570u32, + "IdentificationInstructionIndicator" => 571u32, + "IdentificationInstructionType" => 572u32, + "IdentificationMarginIndicator" => 573u32, + "IdentificationPartyFlag" => 574u32, + "IdentificationPartyType" => 575u32, + "IdentificationPositionType" => 576u32, + "IdentificationPriorityDate" => 577u32, + "IdentificationReconciliationCount" => 578u32, + "IdentificationReference1" => 579u32, + "IdentificationReferenceCode" => 580u32, + "IdentificationRouting" => 581u32, + "IdentificationSequence" => 582u32, + "IdentificationSequenceCode" => 583u32, + "IdentificationSequenceIndicator" => 584u32, + "IdentificationSettlement1" => 585u32, + "IdentificationSettlementId" => 586u32, + "IdentificationSettlementSub" => 587u32, + "IdentificationStatementFlag" => 588u32, + "IdentificationStatementIndicator" => 589u32, + "IdentificationStatementReport" => 590u32, + "IdentificationStatusCode" => 591u32, + "IdentificationStatusReport" => 592u32, + "IdentificationTransaction" => 593u32, + "IdentificationTransaction1" => 594u32, + "IdentificationTransactionType" => 595u32, + "IdentificationTransmission1" => 596u32, + "IdentificationValueDate" => 597u32, + "IdentificationValueDateCount" => 598u32, + "IdentificationValueDateDate" => 599u32, + "IdentificationValueDateId" => 600u32, + "IdentificationValueDateIndicator" => 601u32, + "IdentificationValueDateType" => 602u32, + "IdentificationVersion1" => 603u32, + "IdentificationVersionReport" => 604u32, + "InstructionAccount" => 605u32, + "InstructionAccountCode" => 606u32, + "InstructionAdjustment" => 607u32, + "InstructionAdjustmentId" => 608u32, + "InstructionAuthentication2" => 609u32, + "InstructionAuthenticationIndicator" => 610u32, + "InstructionBeneficiary" => 611u32, + "InstructionBeneficiaryDate" => 612u32, + "InstructionBeneficiaryReport" => 613u32, + "InstructionBeneficiaryType" => 614u32, + "InstructionCategoryCode" => 615u32, + "InstructionChannelIndicator" => 616u32, + "InstructionCurrency1" => 617u32, + "InstructionCurrencyCount" => 618u32, + "InstructionCurrencyId" => 619u32, + "InstructionIdentification" => 620u32, + "InstructionIdentificationType" => 621u32, + "InstructionInstruction2" => 622u32, + "InstructionInstructionIndicator" => 623u32, + "InstructionInstructionSub" => 624u32, + "InstructionMarginCode" => 625u32, + "InstructionParty" => 626u32, + "InstructionPositionCode" => 627u32, + "InstructionPositionType" => 628u32, + "InstructionPriority" => 629u32, + "InstructionPriorityFlag" => 630u32, + "InstructionPriorityId" => 631u32, + "InstructionReconciliation2" => 632u32, + "InstructionReconciliationType" => 633u32, + "InstructionReferenceIndicator" => 634u32, + "InstructionRemittanceCount" => 635u32, + "InstructionRemittanceId" => 636u32, + "InstructionSequenceFlag" => 637u32, + "InstructionSettlementType" => 638u32, + "InstructionStatusCode" => 639u32, + "InstructionStatusType" => 640u32, + "InstructionTransactionSub" => 641u32, + "InstructionTransmissionCode" => 642u32, + "InstructionTransmissionType" => 643u32, + "InstructionValueDate" => 644u32, + "InstructionValueDate1" => 645u32, + "InstructionValueDateIndicator" => 646u32, + "InstructionVersion2" => 647u32, + "MarginAccountIndicator" => 648u32, + "MarginAdjustmentDate" => 649u32, + "MarginAmount2" => 650u32, + "MarginAmountReport" => 651u32, + "MarginAuthentication2" => 652u32, + "MarginBeneficiary" => 653u32, + "MarginBeneficiaryReport" => 654u32, + "MarginBeneficiaryType" => 655u32, + "MarginCategory2" => 656u32, + "MarginChannel2" => 657u32, + "MarginClearing" => 658u32, + "MarginClearingCount" => 659u32, + "MarginCollateral1" => 660u32, + "MarginCurrencyCode" => 661u32, + "MarginIdentification" => 662u32, + "MarginIdentificationType" => 663u32, + "MarginInstruction1" => 664u32, + "MarginInstructionIndicator" => 665u32, + "MarginMarginDate" => 666u32, + "MarginMarginId" => 667u32, + "MarginPartyIndicator" => 668u32, + "MarginPositionCode" => 669u32, + "MarginPriorityDate" => 670u32, + "MarginPriorityFlag" => 671u32, + "MarginPrioritySub" => 672u32, + "MarginReconciliation" => 673u32, + "MarginReconciliation1" => 674u32, + "MarginReconciliationFlag" => 675u32, + "MarginReconciliationId" => 676u32, + "MarginReference1" => 677u32, + "MarginReferenceFlag" => 678u32, + "MarginReferenceId" => 679u32, + "MarginRemittance" => 680u32, + "MarginRemittanceType" => 681u32, + "MarginRouting" => 682u32, + "MarginRoutingType" => 683u32, + "MarginSequenceFlag" => 684u32, + "MarginSequenceId" => 685u32, + "MarginStatement" => 686u32, + "MarginStatementCount" => 687u32, + "MarginStatementReport" => 688u32, + "MarginStatusId" => 689u32, + "MarginTransactionCount" => 690u32, + "MarginTransactionFlag" => 691u32, + "MarginTransmissionId" => 692u32, + "MarginTransmissionSub" => 693u32, + "MarginValueDate2" => 694u32, + "PartyAccountReport" => 695u32, + "PartyAccountSub" => 696u32, + "PartyAccountType" => 697u32, + "PartyAdjustmentCode" => 698u32, + "PartyAdjustmentIndicator" => 699u32, + "PartyAdjustmentReport" => 700u32, + "PartyAmount1" => 701u32, + "PartyAmountDate" => 702u32, + "PartyAmountFlag" => 703u32, + "PartyAuthentication1" => 704u32, + "PartyCategoryCount" => 705u32, + "PartyChannel" => 706u32, + "PartyChannel2" => 707u32, + "PartyChannelSub" => 708u32, + "PartyCollateralReport" => 709u32, + "PartyCollateralSub" => 710u32, + "PartyCurrency" => 711u32, + "PartyCurrencyCode" => 712u32, + "PartyCurrencyType" => 713u32, + "PartyIdentificationIndicator" => 714u32, + "PartyInstructionDate" => 715u32, + "PartyInstructionId" => 716u32, + "PartyInstructionReport" => 717u32, + "PartyMarginType" => 718u32, + "PartyPosition2" => 719u32, + "PartyPositionId" => 720u32, + "PartyPositionType" => 721u32, + "PartyPriority" => 722u32, + "PartyPriority1" => 723u32, + "PartyPriorityCount" => 724u32, + "PartyReconciliationCode" => 725u32, + "PartyReconciliationFlag" => 726u32, + "PartyReconciliationId" => 727u32, + "PartyReferenceIndicator" => 728u32, + "PartyReferenceSub" => 729u32, + "PartyRemittanceType" => 730u32, + "PartyRouting1" => 731u32, + "PartyRouting2" => 732u32, + "PartyRoutingId" => 733u32, + "PartySequenceIndicator" => 734u32, + "PartySequenceReport" => 735u32, + "PartySettlement1" => 736u32, + "PartySettlementCount" => 737u32, + "PartyStatement" => 738u32, + "PartyStatementId" => 739u32, + "PartyStatementIndicator" => 740u32, + "PartyStatusId" => 741u32, + "PartyTransactionIndicator" => 742u32, + "PartyValueDateCount" => 743u32, + "PartyValueDateType" => 744u32, + "PartyVersion" => 745u32, + "PositionAccount" => 746u32, + "PositionAccount1" => 747u32, + "PositionAdjustmentCode" => 748u32, + "PositionAdjustmentDate" => 749u32, + "PositionAmount" => 750u32, + "PositionAmount1" => 751u32, + "PositionAmountSub" => 752u32, + "PositionAuthenticationFlag" => 753u32, + "PositionBeneficiaryFlag" => 754u32, + "PositionBeneficiaryReport" => 755u32, + "PositionCategorySub" => 756u32, + "PositionChannelFlag" => 757u32, + "PositionClearingReport" => 758u32, + "PositionClearingType" => 759u32, + "PositionCollateralCode" => 760u32, + "PositionCurrency2" => 761u32, + "PositionCurrencyReport" => 762u32, + "PositionCurrencySub" => 763u32, + "PositionIdentification1" => 764u32, + "PositionIdentificationCode" => 765u32, + "PositionInstruction" => 766u32, + "PositionInstructionReport" => 767u32, + "PositionMarginCode" => 768u32, + "PositionMarginCount" => 769u32, + "PositionMarginFlag" => 770u32, + "PositionMarginType" => 771u32, + "PositionPartyFlag" => 772u32, + "PositionPartyIndicator" => 773u32, + "PositionPositionReport" => 774u32, + "PositionPriorityFlag" => 775u32, + "PositionPriorityIndicator" => 776u32, + "PositionReconciliation1" => 777u32, + "PositionReconciliationReport" => 778u32, + "PositionReferenceSub" => 779u32, + "PositionRemittanceIndicator" => 780u32, + "PositionRoutingCount" => 781u32, + "PositionRoutingIndicator" => 782u32, + "PositionSequence" => 783u32, + "PositionSequenceCode" => 784u32, + "PositionSequenceDate" => 785u32, + "PositionSequenceIndicator" => 786u32, + "PositionStatement1" => 787u32, + "PositionStatementCount" => 788u32, + "PositionStatusSub" => 789u32, + "PositionTransmission" => 790u32, + "PositionTransmissionDate" => 791u32, + "PositionTransmissionType" => 792u32, + "PositionValueDate1" => 793u32, + "PositionValueDateFlag" => 794u32, + "PositionValueDateIndicator" => 795u32, + "PositionValueDateReport" => 796u32, + "PositionValueDateSub" => 797u32, + "PositionValueDateType" => 798u32, + "PriorityAccount1" => 799u32, + "PriorityAccountDate" => 800u32, + "PriorityAccountIndicator" => 801u32, + "PriorityAdjustment" => 802u32, + "PriorityAdjustment1" => 803u32, + "PriorityAdjustment2" => 804u32, + "PriorityAdjustmentCode" => 805u32, + "PriorityAdjustmentFlag" => 806u32, + "PriorityAdjustmentReport" => 807u32, + "PriorityAmount" => 808u32, + "PriorityAmountSub" => 809u32, + "PriorityAuthentication" => 810u32, + "PriorityAuthenticationDate" => 811u32, + "PriorityBeneficiaryCount" => 812u32, + "PriorityBeneficiaryIndicator" => 813u32, + "PriorityBeneficiaryType" => 814u32, + "PriorityChannelCount" => 815u32, + "PriorityChannelReport" => 816u32, + "PriorityChannelType" => 817u32, + "PriorityClearingType" => 818u32, + "PriorityCurrency" => 819u32, + "PriorityCurrencyId" => 820u32, + "PriorityCurrencyType" => 821u32, + "PriorityIdentificationDate" => 822u32, + "PriorityIdentificationId" => 823u32, + "PriorityInstruction2" => 824u32, + "PriorityInstructionCode" => 825u32, + "PriorityInstructionFlag" => 826u32, + "PriorityMargin2" => 827u32, + "PriorityMarginCount" => 828u32, + "PriorityParty" => 829u32, + "PriorityParty1" => 830u32, + "PriorityPartyDate" => 831u32, + "PriorityPartyFlag" => 832u32, + "PriorityPosition" => 833u32, + "PriorityPositionId" => 834u32, + "PriorityPositionType" => 835u32, + "PriorityPriority" => 836u32, + "PriorityReconciliation2" => 837u32, + "PriorityReconciliationCode" => 838u32, + "PriorityReconciliationFlag" => 839u32, + "PriorityReconciliationReport" => 840u32, + "PriorityReference1" => 841u32, + "PriorityReferenceCount" => 842u32, + "PriorityReferenceDate" => 843u32, + "PriorityReferenceId" => 844u32, + "PriorityRemittanceCode" => 845u32, + "PriorityRemittanceCount" => 846u32, + "PriorityRemittanceFlag" => 847u32, + "PriorityRoutingFlag" => 848u32, + "PrioritySequenceCount" => 849u32, + "PrioritySettlement" => 850u32, + "PriorityStatementCount" => 851u32, + "PriorityStatementFlag" => 852u32, + "PriorityStatementId" => 853u32, + "PriorityStatusId" => 854u32, + "PriorityStatusSub" => 855u32, + "PriorityTransactionCount" => 856u32, + "PriorityTransactionIndicator" => 857u32, + "PriorityTransmission" => 858u32, + "PriorityValueDate1" => 859u32, + "PriorityValueDate2" => 860u32, + "PriorityValueDateIndicator" => 861u32, + "PriorityVersion" => 862u32, + "PriorityVersionIndicator" => 863u32, + "PriorityVersionType" => 864u32, + "ReconciliationAccountReport" => 865u32, + "ReconciliationAdjustment" => 866u32, + "ReconciliationAmountCount" => 867u32, + "ReconciliationAuthenticationDate" => 868u32, + "ReconciliationBeneficiary1" => 869u32, + "ReconciliationBeneficiaryDate" => 870u32, + "ReconciliationChannel" => 871u32, + "ReconciliationChannelFlag" => 872u32, + "ReconciliationChannelSub" => 873u32, + "ReconciliationClearing1" => 874u32, + "ReconciliationClearingId" => 875u32, + "ReconciliationCurrency" => 876u32, + "ReconciliationIdentificationId" => 877u32, + "ReconciliationInstruction2" => 878u32, + "ReconciliationInstructionFlag" => 879u32, + "ReconciliationInstructionSub" => 880u32, + "ReconciliationMarginId" => 881u32, + "ReconciliationParty" => 882u32, + "ReconciliationPartyIndicator" => 883u32, + "ReconciliationPositionCount" => 884u32, + "ReconciliationPriority" => 885u32, + "ReconciliationPriority2" => 886u32, + "ReconciliationReconciliation1" => 887u32, + "ReconciliationReconciliationCount" => 888u32, + "ReconciliationReconciliationSub" => 889u32, + "ReconciliationReferenceCount" => 890u32, + "ReconciliationReferenceIndicator" => 891u32, + "ReconciliationRemittance" => 892u32, + "ReconciliationRemittanceId" => 893u32, + "ReconciliationRemittanceIndicator" => 894u32, + "ReconciliationRemittanceSub" => 895u32, + "ReconciliationSequenceCode" => 896u32, + "ReconciliationSequenceCount" => 897u32, + "ReconciliationStatusCount" => 898u32, + "ReconciliationStatusReport" => 899u32, + "ReconciliationTransactionDate" => 900u32, + "ReconciliationTransactionId" => 901u32, + "ReconciliationTransactionSub" => 902u32, + "ReconciliationTransactionType" => 903u32, + "ReconciliationTransmissionCount" => 904u32, + "ReconciliationTransmissionDate" => 905u32, + "ReconciliationValueDate2" => 906u32, + "ReconciliationValueDateType" => 907u32, + "ReconciliationVersion" => 908u32, + "ReconciliationVersionIndicator" => 909u32, + "ReconciliationVersionReport" => 910u32, + "ReferenceAccount" => 911u32, + "ReferenceAccount2" => 912u32, + "ReferenceAccountId" => 913u32, + "ReferenceAccountReport" => 914u32, + "ReferenceAmountCode" => 915u32, + "ReferenceAmountType" => 916u32, + "ReferenceAuthenticationSub" => 917u32, + "ReferenceBeneficiaryIndicator" => 918u32, + "ReferenceCategory2" => 919u32, + "ReferenceCategoryDate" => 920u32, + "ReferenceCategoryIndicator" => 921u32, + "ReferenceChannelReport" => 922u32, + "ReferenceClearing" => 923u32, + "ReferenceClearingId" => 924u32, + "ReferenceClearingType" => 925u32, + "ReferenceCollateral" => 926u32, + "ReferenceCollateral1" => 927u32, + "ReferenceCollateral2" => 928u32, + "ReferenceCurrency" => 929u32, + "ReferenceCurrencyCode" => 930u32, + "ReferenceIdentification" => 931u32, + "ReferenceIdentification1" => 932u32, + "ReferenceIdentificationCount" => 933u32, + "ReferenceIdentificationDate" => 934u32, + "ReferenceIdentificationReport" => 935u32, + "ReferenceInstructionDate" => 936u32, + "ReferenceInstructionFlag" => 937u32, + "ReferenceInstructionType" => 938u32, + "ReferenceMargin" => 939u32, + "ReferenceParty" => 940u32, + "ReferencePartySub" => 941u32, + "ReferencePositionId" => 942u32, + "ReferencePositionType" => 943u32, + "ReferencePriority" => 944u32, + "ReferencePriority1" => 945u32, + "ReferencePriorityReport" => 946u32, + "ReferenceReconciliation" => 947u32, + "ReferenceReconciliation1" => 948u32, + "ReferenceReconciliation2" => 949u32, + "ReferenceReferenceDate" => 950u32, + "ReferenceReferenceFlag" => 951u32, + "ReferenceRouting" => 952u32, + "ReferenceRoutingCount" => 953u32, + "ReferenceRoutingDate" => 954u32, + "ReferenceRoutingIndicator" => 955u32, + "ReferenceRoutingSub" => 956u32, + "ReferenceSequence" => 957u32, + "ReferenceSequenceId" => 958u32, + "ReferenceStatementDate" => 959u32, + "ReferenceTransactionDate" => 960u32, + "ReferenceTransactionSub" => 961u32, + "ReferenceTransmission" => 962u32, + "ReferenceTransmissionCode" => 963u32, + "ReferenceTransmissionCount" => 964u32, + "ReferenceValueDate2" => 965u32, + "ReferenceValueDateCode" => 966u32, + "ReferenceVersion1" => 967u32, + "ReferenceVersion2" => 968u32, + "RemittanceAccount" => 969u32, + "RemittanceAdjustmentDate" => 970u32, + "RemittanceAmountCode" => 971u32, + "RemittanceAmountDate" => 972u32, + "RemittanceAmountFlag" => 973u32, + "RemittanceAmountReport" => 974u32, + "RemittanceAuthentication" => 975u32, + "RemittanceAuthentication2" => 976u32, + "RemittanceAuthenticationCode" => 977u32, + "RemittanceAuthenticationDate" => 978u32, + "RemittanceBeneficiaryCount" => 979u32, + "RemittanceCategoryDate" => 980u32, + "RemittanceCategoryFlag" => 981u32, + "RemittanceChannel1" => 982u32, + "RemittanceClearing1" => 983u32, + "RemittanceClearingFlag" => 984u32, + "RemittanceClearingType" => 985u32, + "RemittanceCollateral" => 986u32, + "RemittanceCollateralCode" => 987u32, + "RemittanceCollateralSub" => 988u32, + "RemittanceCurrencyIndicator" => 989u32, + "RemittanceIdentificationReport" => 990u32, + "RemittanceIdentificationSub" => 991u32, + "RemittanceInstruction" => 992u32, + "RemittanceInstructionIndicator" => 993u32, + "RemittanceMarginId" => 994u32, + "RemittanceMarginSub" => 995u32, + "RemittancePositionId" => 996u32, + "RemittancePositionSub" => 997u32, + "RemittancePriorityReport" => 998u32, + "RemittanceReconciliation1" => 999u32, + "RemittanceReference" => 1000u32, + "RemittanceReference1" => 1001u32, + "RemittanceReferenceDate" => 1002u32, + "RemittanceRemittance1" => 1003u32, + "RemittanceRemittanceCount" => 1004u32, + "RemittanceRemittanceDate" => 1005u32, + "RemittanceRemittanceReport" => 1006u32, + "RemittanceRemittanceType" => 1007u32, + "RemittanceRouting" => 1008u32, + "RemittanceRouting1" => 1009u32, + "RemittanceRoutingSub" => 1010u32, + "RemittanceSequenceReport" => 1011u32, + "RemittanceSettlementFlag" => 1012u32, + "RemittanceSettlementReport" => 1013u32, + "RemittanceSettlementType" => 1014u32, + "RemittanceStatementSub" => 1015u32, + "RemittanceStatementType" => 1016u32, + "RemittanceStatus2" => 1017u32, + "RemittanceTransactionCount" => 1018u32, + "RemittanceTransactionReport" => 1019u32, + "RemittanceTransmission2" => 1020u32, + "RemittanceTransmissionCode" => 1021u32, + "RemittanceTransmissionFlag" => 1022u32, + "RemittanceValueDateCount" => 1023u32, + "RemittanceVersionDate" => 1024u32, + "RemittanceVersionFlag" => 1025u32, + "RemittanceVersionIndicator" => 1026u32, + "RemittanceVersionSub" => 1027u32, + "RoutingAccount" => 1028u32, + "RoutingAccountIndicator" => 1029u32, + "RoutingAdjustmentFlag" => 1030u32, + "RoutingAdjustmentSub" => 1031u32, + "RoutingAmountSub" => 1032u32, + "RoutingAmountType" => 1033u32, + "RoutingAuthentication" => 1034u32, + "RoutingAuthenticationFlag" => 1035u32, + "RoutingBeneficiaryDate" => 1036u32, + "RoutingBeneficiaryIndicator" => 1037u32, + "RoutingCategoryCount" => 1038u32, + "RoutingCategorySub" => 1039u32, + "RoutingChannel" => 1040u32, + "RoutingChannel2" => 1041u32, + "RoutingChannelId" => 1042u32, + "RoutingClearing" => 1043u32, + "RoutingClearingCode" => 1044u32, + "RoutingCollateralIndicator" => 1045u32, + "RoutingCurrency" => 1046u32, + "RoutingCurrencyCount" => 1047u32, + "RoutingCurrencyId" => 1048u32, + "RoutingCurrencyReport" => 1049u32, + "RoutingIdentificationSub" => 1050u32, + "RoutingInstruction" => 1051u32, + "RoutingInstructionId" => 1052u32, + "RoutingInstructionIndicator" => 1053u32, + "RoutingInstructionType" => 1054u32, + "RoutingMarginType" => 1055u32, + "RoutingParty2" => 1056u32, + "RoutingPartyCode" => 1057u32, + "RoutingPartyId" => 1058u32, + "RoutingPartyIndicator" => 1059u32, + "RoutingPartyReport" => 1060u32, + "RoutingPartyType" => 1061u32, + "RoutingPosition1" => 1062u32, + "RoutingPositionFlag" => 1063u32, + "RoutingPositionSub" => 1064u32, + "RoutingPriorityId" => 1065u32, + "RoutingReconciliation2" => 1066u32, + "RoutingReconciliationCode" => 1067u32, + "RoutingReconciliationId" => 1068u32, + "RoutingReferenceReport" => 1069u32, + "RoutingRouting" => 1070u32, + "RoutingRoutingCode" => 1071u32, + "RoutingSequenceReport" => 1072u32, + "RoutingSequenceSub" => 1073u32, + "RoutingSettlement1" => 1074u32, + "RoutingStatement2" => 1075u32, + "RoutingStatementCount" => 1076u32, + "RoutingStatementDate" => 1077u32, + "RoutingStatementId" => 1078u32, + "RoutingTransaction1" => 1079u32, + "RoutingTransactionDate" => 1080u32, + "RoutingTransactionIndicator" => 1081u32, + "RoutingTransmissionId" => 1082u32, + "RoutingTransmissionSub" => 1083u32, + "RoutingValueDate1" => 1084u32, + "RoutingVersionDate" => 1085u32, + "RoutingVersionReport" => 1086u32, + "SequenceAccount2" => 1087u32, + "SequenceAccountId" => 1088u32, + "SequenceAdjustment" => 1089u32, + "SequenceAmountCount" => 1090u32, + "SequenceAmountReport" => 1091u32, + "SequenceAuthentication2" => 1092u32, + "SequenceAuthenticationDate" => 1093u32, + "SequenceAuthenticationType" => 1094u32, + "SequenceCategoryDate" => 1095u32, + "SequenceCategorySub" => 1096u32, + "SequenceChannelDate" => 1097u32, + "SequenceClearingDate" => 1098u32, + "SequenceClearingId" => 1099u32, + "SequenceClearingIndicator" => 1100u32, + "SequenceCollateralReport" => 1101u32, + "SequenceCurrency" => 1102u32, + "SequenceIdentificationDate" => 1103u32, + "SequenceIdentificationId" => 1104u32, + "SequenceInstructionSub" => 1105u32, + "SequenceMargin" => 1106u32, + "SequenceMarginCount" => 1107u32, + "SequencePositionId" => 1108u32, + "SequencePriorityCount" => 1109u32, + "SequencePrioritySub" => 1110u32, + "SequenceReconciliationDate" => 1111u32, + "SequenceReference" => 1112u32, + "SequenceReferenceIndicator" => 1113u32, + "SequenceRemittanceCount" => 1114u32, + "SequenceRemittanceFlag" => 1115u32, + "SequenceRemittanceIndicator" => 1116u32, + "SequenceRouting2" => 1117u32, + "SequenceSettlement" => 1118u32, + "SequenceSettlementIndicator" => 1119u32, + "SequenceStatement" => 1120u32, + "SequenceStatementType" => 1121u32, + "SequenceStatusSub" => 1122u32, + "SequenceTransactionFlag" => 1123u32, + "SequenceTransactionSub" => 1124u32, + "SequenceTransactionType" => 1125u32, + "SequenceValueDateReport" => 1126u32, + "SequenceValueDateSub" => 1127u32, + "SequenceVersion2" => 1128u32, + "SequenceVersionIndicator" => 1129u32, + "SettlementAccount" => 1130u32, + "SettlementAdjustment1" => 1131u32, + "SettlementAdjustment2" => 1132u32, + "SettlementAdjustmentFlag" => 1133u32, + "SettlementAdjustmentReport" => 1134u32, + "SettlementAmount1" => 1135u32, + "SettlementAmount2" => 1136u32, + "SettlementAmountIndicator" => 1137u32, + "SettlementAuthentication2" => 1138u32, + "SettlementAuthenticationCount" => 1139u32, + "SettlementBeneficiaryFlag" => 1140u32, + "SettlementBeneficiaryType" => 1141u32, + "SettlementCategoryCode" => 1142u32, + "SettlementChannel" => 1143u32, + "SettlementChannelCode" => 1144u32, + "SettlementClearingReport" => 1145u32, + "SettlementCollateral2" => 1146u32, + "SettlementCollateralCode" => 1147u32, + "SettlementCollateralDate" => 1148u32, + "SettlementCollateralSub" => 1149u32, + "SettlementCurrency" => 1150u32, + "SettlementCurrency2" => 1151u32, + "SettlementIdentificationCount" => 1152u32, + "SettlementIdentificationIndicator" => 1153u32, + "SettlementInstruction" => 1154u32, + "SettlementInstructionDate" => 1155u32, + "SettlementMargin2" => 1156u32, + "SettlementMarginId" => 1157u32, + "SettlementMarginSub" => 1158u32, + "SettlementPartyCode" => 1159u32, + "SettlementPartyFlag" => 1160u32, + "SettlementPartyType" => 1161u32, + "SettlementPositionType" => 1162u32, + "SettlementPriorityCount" => 1163u32, + "SettlementPriorityFlag" => 1164u32, + "SettlementPriorityIndicator" => 1165u32, + "SettlementPriorityReport" => 1166u32, + "SettlementReconciliation1" => 1167u32, + "SettlementReconciliationSub" => 1168u32, + "SettlementReferenceReport" => 1169u32, + "SettlementRemittanceSub" => 1170u32, + "SettlementSequence2" => 1171u32, + "SettlementSequenceSub" => 1172u32, + "SettlementSequenceType" => 1173u32, + "SettlementSettlementId" => 1174u32, + "SettlementSettlementReport" => 1175u32, + "SettlementStatementCode" => 1176u32, + "SettlementStatus1" => 1177u32, + "SettlementTransaction" => 1178u32, + "SettlementTransactionReport" => 1179u32, + "SettlementTransmission" => 1180u32, + "SettlementValueDate" => 1181u32, + "SettlementValueDate2" => 1182u32, + "SettlementValueDateFlag" => 1183u32, + "SettlementValueDateSub" => 1184u32, + "StatementAccount2" => 1185u32, + "StatementAdjustmentDate" => 1186u32, + "StatementAdjustmentIndicator" => 1187u32, + "StatementAdjustmentReport" => 1188u32, + "StatementAdjustmentSub" => 1189u32, + "StatementAdjustmentType" => 1190u32, + "StatementAmount" => 1191u32, + "StatementAmount2" => 1192u32, + "StatementAmountCode" => 1193u32, + "StatementBeneficiaryCode" => 1194u32, + "StatementBeneficiaryFlag" => 1195u32, + "StatementBeneficiaryId" => 1196u32, + "StatementBeneficiaryType" => 1197u32, + "StatementCategory" => 1198u32, + "StatementChannel1" => 1199u32, + "StatementChannelCode" => 1200u32, + "StatementChannelId" => 1201u32, + "StatementCollateral" => 1202u32, + "StatementCollateralSub" => 1203u32, + "StatementCurrencyCode" => 1204u32, + "StatementCurrencyType" => 1205u32, + "StatementIdentification" => 1206u32, + "StatementIdentificationReport" => 1207u32, + "StatementInstructionReport" => 1208u32, + "StatementMargin1" => 1209u32, + "StatementMarginCount" => 1210u32, + "StatementMarginReport" => 1211u32, + "StatementMarginType" => 1212u32, + "StatementParty" => 1213u32, + "StatementPosition" => 1214u32, + "StatementPositionDate" => 1215u32, + "StatementReconciliationIndicator" => 1216u32, + "StatementReference2" => 1217u32, + "StatementReferenceCode" => 1218u32, + "StatementReferenceSub" => 1219u32, + "StatementRouting" => 1220u32, + "StatementRoutingCode" => 1221u32, + "StatementRoutingId" => 1222u32, + "StatementRoutingIndicator" => 1223u32, + "StatementRoutingType" => 1224u32, + "StatementSequenceCode" => 1225u32, + "StatementSequenceCount" => 1226u32, + "StatementSequenceSub" => 1227u32, + "StatementSettlementReport" => 1228u32, + "StatementStatement2" => 1229u32, + "StatementStatementId" => 1230u32, + "StatementStatus2" => 1231u32, + "StatementTransaction1" => 1232u32, + "StatementTransactionIndicator" => 1233u32, + "StatementTransactionSub" => 1234u32, + "StatementTransmissionReport" => 1235u32, + "StatementVersion1" => 1236u32, + "StatementVersionCount" => 1237u32, + "StatementVersionDate" => 1238u32, + "StatementVersionType" => 1239u32, + "StatusAccountId" => 1240u32, + "StatusAccountIndicator" => 1241u32, + "StatusAccountType" => 1242u32, + "StatusAdjustmentCode" => 1243u32, + "StatusAdjustmentDate" => 1244u32, + "StatusAdjustmentFlag" => 1245u32, + "StatusAmount" => 1246u32, + "StatusAmount1" => 1247u32, + "StatusAmountCode" => 1248u32, + "StatusAmountDate" => 1249u32, + "StatusAmountReport" => 1250u32, + "StatusAuthenticationFlag" => 1251u32, + "StatusAuthenticationReport" => 1252u32, + "StatusBeneficiary" => 1253u32, + "StatusCategory" => 1254u32, + "StatusCategory2" => 1255u32, + "StatusChannel" => 1256u32, + "StatusChannel1" => 1257u32, + "StatusChannelFlag" => 1258u32, + "StatusChannelType" => 1259u32, + "StatusClearing" => 1260u32, + "StatusCollateral2" => 1261u32, + "StatusCollateralCount" => 1262u32, + "StatusCollateralDate" => 1263u32, + "StatusCollateralFlag" => 1264u32, + "StatusCollateralSub" => 1265u32, + "StatusCurrencyDate" => 1266u32, + "StatusCurrencySub" => 1267u32, + "StatusCurrencyType" => 1268u32, + "StatusIdentificationId" => 1269u32, + "StatusInstructionCount" => 1270u32, + "StatusInstructionIndicator" => 1271u32, + "StatusMargin2" => 1272u32, + "StatusMarginCode" => 1273u32, + "StatusMarginId" => 1274u32, + "StatusPartyReport" => 1275u32, + "StatusPositionCount" => 1276u32, + "StatusPositionFlag" => 1277u32, + "StatusPositionIndicator" => 1278u32, + "StatusPriority" => 1279u32, + "StatusPriorityReport" => 1280u32, + "StatusReconciliationDate" => 1281u32, + "StatusReconciliationId" => 1282u32, + "StatusReconciliationType" => 1283u32, + "StatusRemittanceCount" => 1284u32, + "StatusRemittanceDate" => 1285u32, + "StatusRouting" => 1286u32, + "StatusSequenceCode" => 1287u32, + "StatusSettlementIndicator" => 1288u32, + "StatusTransactionCode" => 1289u32, + "StatusTransactionCount" => 1290u32, + "StatusTransactionFlag" => 1291u32, + "StatusTransactionReport" => 1292u32, + "StatusTransmissionCount" => 1293u32, + "StatusTransmissionType" => 1294u32, + "StatusValueDate2" => 1295u32, + "StatusValueDateCode" => 1296u32, + "StatusValueDateCount" => 1297u32, + "StatusVersion1" => 1298u32, + "TransactionAccount" => 1299u32, + "TransactionAccountCount" => 1300u32, + "TransactionAdjustment1" => 1301u32, + "TransactionAdjustmentCode" => 1302u32, + "TransactionAdjustmentFlag" => 1303u32, + "TransactionAmount" => 1304u32, + "TransactionAmountCount" => 1305u32, + "TransactionAmountIndicator" => 1306u32, + "TransactionAuthenticationIndicator" => 1307u32, + "TransactionBeneficiary2" => 1308u32, + "TransactionBeneficiarySub" => 1309u32, + "TransactionCategoryDate" => 1310u32, + "TransactionCategoryFlag" => 1311u32, + "TransactionChannelSub" => 1312u32, + "TransactionChannelType" => 1313u32, + "TransactionClearing" => 1314u32, + "TransactionClearingReport" => 1315u32, + "TransactionClearingSub" => 1316u32, + "TransactionCollateral" => 1317u32, + "TransactionCollateralFlag" => 1318u32, + "TransactionCollateralId" => 1319u32, + "TransactionIdentificationIndicator" => 1320u32, + "TransactionInstruction" => 1321u32, + "TransactionInstructionCode" => 1322u32, + "TransactionInstructionSub" => 1323u32, + "TransactionMargin2" => 1324u32, + "TransactionMarginCount" => 1325u32, + "TransactionPosition2" => 1326u32, + "TransactionPositionIndicator" => 1327u32, + "TransactionPriorityId" => 1328u32, + "TransactionReconciliationCount" => 1329u32, + "TransactionRemittanceFlag" => 1330u32, + "TransactionRemittanceReport" => 1331u32, + "TransactionRemittanceType" => 1332u32, + "TransactionRoutingFlag" => 1333u32, + "TransactionSequenceFlag" => 1334u32, + "TransactionSequenceId" => 1335u32, + "TransactionSequenceSub" => 1336u32, + "TransactionSequenceType" => 1337u32, + "TransactionSettlementDate" => 1338u32, + "TransactionSettlementFlag" => 1339u32, + "TransactionStatement" => 1340u32, + "TransactionStatementIndicator" => 1341u32, + "TransactionStatusType" => 1342u32, + "TransactionTransactionDate" => 1343u32, + "TransactionTransmissionCount" => 1344u32, + "TransactionVersionCode" => 1345u32, + "TransactionVersionType" => 1346u32, + "TransmissionAdjustment1" => 1347u32, + "TransmissionAdjustmentDate" => 1348u32, + "TransmissionAdjustmentFlag" => 1349u32, + "TransmissionAdjustmentType" => 1350u32, + "TransmissionCategory" => 1351u32, + "TransmissionCategoryFlag" => 1352u32, + "TransmissionCategoryId" => 1353u32, + "TransmissionChannel" => 1354u32, + "TransmissionClearingId" => 1355u32, + "TransmissionCollateralDate" => 1356u32, + "TransmissionCurrencyIndicator" => 1357u32, + "TransmissionCurrencyReport" => 1358u32, + "TransmissionIdentification1" => 1359u32, + "TransmissionInstruction" => 1360u32, + "TransmissionInstruction2" => 1361u32, + "TransmissionInstructionFlag" => 1362u32, + "TransmissionInstructionType" => 1363u32, + "TransmissionMargin2" => 1364u32, + "TransmissionMarginDate" => 1365u32, + "TransmissionPartyCode" => 1366u32, + "TransmissionPartyDate" => 1367u32, + "TransmissionPositionReport" => 1368u32, + "TransmissionReconciliation2" => 1369u32, + "TransmissionReconciliationSub" => 1370u32, + "TransmissionReference1" => 1371u32, + "TransmissionReferenceIndicator" => 1372u32, + "TransmissionRemittanceDate" => 1373u32, + "TransmissionRemittanceReport" => 1374u32, + "TransmissionRoutingCount" => 1375u32, + "TransmissionRoutingSub" => 1376u32, + "TransmissionSequenceDate" => 1377u32, + "TransmissionSettlementCount" => 1378u32, + "TransmissionSettlementFlag" => 1379u32, + "TransmissionStatement" => 1380u32, + "TransmissionStatement2" => 1381u32, + "TransmissionStatementCount" => 1382u32, + "TransmissionStatusCode" => 1383u32, + "TransmissionTransaction" => 1384u32, + "TransmissionTransmissionDate" => 1385u32, + "TransmissionValueDate" => 1386u32, + "TransmissionValueDateId" => 1387u32, + "TransmissionValueDateIndicator" => 1388u32, + "ValueDateAccount2" => 1389u32, + "ValueDateAccountCount" => 1390u32, + "ValueDateAdjustment" => 1391u32, + "ValueDateAdjustmentCount" => 1392u32, + "ValueDateAdjustmentId" => 1393u32, + "ValueDateAdjustmentReport" => 1394u32, + "ValueDateAdjustmentType" => 1395u32, + "ValueDateAmountCode" => 1396u32, + "ValueDateAmountCount" => 1397u32, + "ValueDateBeneficiary" => 1398u32, + "ValueDateBeneficiarySub" => 1399u32, + "ValueDateClearingDate" => 1400u32, + "ValueDateCurrency" => 1401u32, + "ValueDateIdentification2" => 1402u32, + "ValueDateIdentificationFlag" => 1403u32, + "ValueDateIdentificationIndicator" => 1404u32, + "ValueDateIdentificationReport" => 1405u32, + "ValueDateMargin" => 1406u32, + "ValueDatePartyId" => 1407u32, + "ValueDatePosition" => 1408u32, + "ValueDatePositionType" => 1409u32, + "ValueDatePriority" => 1410u32, + "ValueDatePriority2" => 1411u32, + "ValueDatePriorityCount" => 1412u32, + "ValueDatePriorityReport" => 1413u32, + "ValueDateReconciliationIndicator" => 1414u32, + "ValueDateReferenceType" => 1415u32, + "ValueDateRemittance2" => 1416u32, + "ValueDateRemittanceReport" => 1417u32, + "ValueDateRoutingReport" => 1418u32, + "ValueDateSequence" => 1419u32, + "ValueDateSequence1" => 1420u32, + "ValueDateSequenceCount" => 1421u32, + "ValueDateSequenceType" => 1422u32, + "ValueDateSettlement2" => 1423u32, + "ValueDateSettlementCount" => 1424u32, + "ValueDateStatementCode" => 1425u32, + "ValueDateStatementReport" => 1426u32, + "ValueDateStatementSub" => 1427u32, + "ValueDateStatusCode" => 1428u32, + "ValueDateStatusType" => 1429u32, + "ValueDateTransaction2" => 1430u32, + "ValueDateTransactionCount" => 1431u32, + "ValueDateTransactionId" => 1432u32, + "ValueDateTransactionReport" => 1433u32, + "ValueDateValueDateCode" => 1434u32, + "ValueDateValueDateIndicator" => 1435u32, + "ValueDateValueDateReport" => 1436u32, + "ValueDateVersionFlag" => 1437u32, + "ValueDateVersionSub" => 1438u32, + "VersionAccount" => 1439u32, + "VersionAccountCode" => 1440u32, + "VersionAccountCount" => 1441u32, + "VersionAccountIndicator" => 1442u32, + "VersionAdjustmentIndicator" => 1443u32, + "VersionAdjustmentSub" => 1444u32, + "VersionAmount" => 1445u32, + "VersionAmount2" => 1446u32, + "VersionAmountCode" => 1447u32, + "VersionAuthentication1" => 1448u32, + "VersionAuthenticationCode" => 1449u32, + "VersionBeneficiary1" => 1450u32, + "VersionBeneficiary2" => 1451u32, + "VersionBeneficiaryIndicator" => 1452u32, + "VersionCategory2" => 1453u32, + "VersionChannel" => 1454u32, + "VersionChannelDate" => 1455u32, + "VersionChannelFlag" => 1456u32, + "VersionClearing" => 1457u32, + "VersionClearingCode" => 1458u32, + "VersionClearingCount" => 1459u32, + "VersionClearingFlag" => 1460u32, + "VersionClearingSub" => 1461u32, + "VersionCollateralFlag" => 1462u32, + "VersionCurrencyCode" => 1463u32, + "VersionCurrencyFlag" => 1464u32, + "VersionCurrencyIndicator" => 1465u32, + "VersionIdentificationCount" => 1466u32, + "VersionIdentificationDate" => 1467u32, + "VersionIdentificationFlag" => 1468u32, + "VersionIdentificationIndicator" => 1469u32, + "VersionInstructionFlag" => 1470u32, + "VersionMargin1" => 1471u32, + "VersionMarginId" => 1472u32, + "VersionPartyId" => 1473u32, + "VersionPosition1" => 1474u32, + "VersionPosition2" => 1475u32, + "VersionPositionReport" => 1476u32, + "VersionPriority2" => 1477u32, + "VersionPriorityCode" => 1478u32, + "VersionReconciliationCount" => 1479u32, + "VersionReconciliationId" => 1480u32, + "VersionReconciliationSub" => 1481u32, + "VersionReference2" => 1482u32, + "VersionReferenceCode" => 1483u32, + "VersionRemittanceIndicator" => 1484u32, + "VersionRoutingReport" => 1485u32, + "VersionSequence2" => 1486u32, + "VersionSequenceFlag" => 1487u32, + "VersionSequenceType" => 1488u32, + "VersionSettlement" => 1489u32, + "VersionSettlementType" => 1490u32, + "VersionStatement" => 1491u32, + "VersionStatementFlag" => 1492u32, + "VersionStatementId" => 1493u32, + "VersionStatusIndicator" => 1494u32, + "VersionTransactionCode" => 1495u32, + "VersionTransactionCount" => 1496u32, + "VersionTransmissionType" => 1497u32, + "VersionValueDateCount" => 1498u32, + "VersionValueDateFlag" => 1499u32, +}; diff --git a/crates/polyxml-core/benches/tag_dispatch.rs b/crates/polyxml-core/benches/tag_dispatch.rs new file mode 100644 index 00000000..f9b56339 --- /dev/null +++ b/crates/polyxml-core/benches/tag_dispatch.rs @@ -0,0 +1,119 @@ +//! Issue #49: tag-dispatch strategy comparison at schema scale tiers. +//! +//! Baselines per the issue's benchmark setup: +//! 1. Standard Rust `match` on string literals (LLVM bucketed memcmp chains) +//! 2. Runtime `HashMap<&str, u32>` +//! 3. Compile-time `phf::Map<&str, u32>` +//! +//! Each iteration sweeps the full tier (hit path for every tag); Criterion's +//! `Throughput::Elements` reports tags/second directly. Tokenization is +//! intentionally isolated OUT of the bench: it is identical across dispatch +//! strategies, so only the lookup is measured (see +//! docs/benchmarks/rust-phf-dispatch.md). +//! +//! Regenerate the tag tables with scripts/gen_tag_dispatch_fixtures.py. + +use std::collections::HashMap; +use std::hint::black_box; + +use criterion::{criterion_group, criterion_main, Criterion, Throughput}; + +#[path = "support/tag_dispatch_fixtures.rs"] +mod fixtures; + +fn bench_tier( + c: &mut Criterion, + tier: &str, + tags: &[&'static str], + match_fn: fn(&str) -> Option, + phf_map: &'static phf::Map<&'static str, u32>, +) { + let hashmap: HashMap<&'static str, u32> = tags + .iter() + .enumerate() + .map(|(i, tag)| (*tag, i as u32)) + .collect(); + // A tag present in no strategy: the miss path matters for unknown children. + let miss = format!("Unknown{tier}Sentinel"); + + let mut group = c.benchmark_group(format!("tag_dispatch/{tier}")); + group.throughput(Throughput::Elements(tags.len() as u64)); + + group.bench_function("match_hit", |b| { + b.iter(|| { + let mut acc = 0u64; + for tag in tags { + acc += u64::from(match_fn(black_box(tag)).unwrap_or(u32::MAX)); + } + black_box(acc); + }); + }); + group.bench_function("match_miss", |b| { + let miss = black_box(miss.as_str()); + b.iter(|| black_box(match_fn(miss))); + }); + + group.bench_function("hashmap_hit", |b| { + b.iter(|| { + let mut acc = 0u64; + for tag in tags { + acc += u64::from(*hashmap.get(black_box(tag)).unwrap_or(&u32::MAX)); + } + black_box(acc); + }); + }); + group.bench_function("hashmap_miss", |b| { + let miss = black_box(miss.as_str()); + b.iter(|| black_box(hashmap.get(miss))); + }); + + group.bench_function("phf_hit", |b| { + b.iter(|| { + let mut acc = 0u64; + for tag in tags { + acc += u64::from(*phf_map.get(black_box(tag)).unwrap_or(&u32::MAX)); + } + black_box(acc); + }); + }); + group.bench_function("phf_miss", |b| { + let miss = black_box(miss.as_str()); + b.iter(|| black_box(phf_map.get(miss))); + }); + + group.finish(); +} + +fn tag_dispatch(c: &mut Criterion) { + bench_tier( + c, + "small_16", + &fixtures::TAGS_16, + fixtures::match_dispatch_16, + &fixtures::PHF_16, + ); + bench_tier( + c, + "medium_120", + &fixtures::TAGS_120, + fixtures::match_dispatch_120, + &fixtures::PHF_120, + ); + bench_tier( + c, + "large_600", + &fixtures::TAGS_600, + fixtures::match_dispatch_600, + &fixtures::PHF_600, + ); + bench_tier( + c, + "large_1500", + &fixtures::TAGS_1500, + fixtures::match_dispatch_1500, + &fixtures::PHF_1500, + ); +} + +criterion_group!(benches, tag_dispatch); +criterion_main!(benches); diff --git a/crates/polyxml-core/src/codegen/cpp/mod.rs b/crates/polyxml-core/src/codegen/cpp/mod.rs index a57f9ec1..2a5c8ba4 100644 --- a/crates/polyxml-core/src/codegen/cpp/mod.rs +++ b/crates/polyxml-core/src/codegen/cpp/mod.rs @@ -274,7 +274,7 @@ impl CppCodegen { writeln!(out, "// Target: Modern C++20/C++23 (Header-Only)").unwrap(); writeln!(out, "#pragma once\n").unwrap(); - self.emit_includes(&mut out); + self.emit_includes(&mut out, ir); let ns = to_cpp_namespace(&self.options.namespace); writeln!(out, "namespace {} {{\n", ns).unwrap(); @@ -312,7 +312,7 @@ impl CppCodegen { writeln!(out, "// Target: Modern C++20/C++23 Module Interface Unit").unwrap(); writeln!(out, "module;\n").unwrap(); - self.emit_includes(&mut out); + self.emit_includes(&mut out, ir); let mod_id = if module_name.is_empty() { "polyxml.models" @@ -457,7 +457,19 @@ endif() ) } - fn emit_includes(&self, out: &mut String) { + fn emit_includes(&self, out: &mut String, ir: &SchemaIR) { + if self.options.validate_facets + && ir.types.values().any(|t| match t { + TypeDef::Simple(s) => !s.facets.patterns.is_empty(), + TypeDef::Struct(s) => s + .fields + .iter() + .any(|f| f.facets.as_ref().is_some_and(|f| !f.patterns.is_empty())), + _ => false, + }) + { + writeln!(out, "#include ").unwrap(); + } writeln!(out, "#include ").unwrap(); writeln!(out, "#include ").unwrap(); writeln!(out, "#include ").unwrap(); @@ -601,7 +613,7 @@ concept XmlModel = requires(T a) {{ // Emit simple type aliases first for type_def in ir.types.values() { if let TypeDef::Simple(simple) = type_def { - self.emit_simple_type(out, simple); + self.emit_simple_type(out, simple, ir); } } @@ -626,15 +638,27 @@ concept XmlModel = requires(T a) {{ } } - fn emit_simple_type(&self, out: &mut String, simple: &SimpleTypeDef) { + fn emit_simple_type(&self, out: &mut String, simple: &SimpleTypeDef, ir: &SchemaIR) { if let Some(ref doc) = simple.documentation { for line in doc.lines() { writeln!(out, "/// {}", line).unwrap(); } } let type_name = type_ident(&simple.qname); - let base_type = self.context.map_type_ref(&simple.base_type); + let base = if simple.facets.patterns.is_empty() { + &simple.base_type + } else { + super::primitive_base(&simple.base_type, ir) + }; + let base_type = self.context.map_type_ref(base); writeln!(out, "using {} = {};\n", type_name, base_type).unwrap(); + if self.options.validate_facets && !simple.facets.patterns.is_empty() { + writeln!(out, "[[nodiscard]] inline bool validate_{}_patterns(std::string_view value) noexcept {{\n try {{", type_name).unwrap(); + for (i, pattern) in simple.facets.patterns.iter().enumerate() { + writeln!(out, " static const std::regex pattern_{}({:?});\n if (!std::regex_match(value.begin(), value.end(), pattern_{})) return false;", i, pattern, i).unwrap(); + } + writeln!(out, " return true;\n }} catch (const std::regex_error&) {{ return false; }}\n}}\n").unwrap(); + } } fn emit_enum(&self, out: &mut String, enum_def: &EnumDef) { @@ -833,7 +857,7 @@ concept XmlModel = requires(T a) {{ } if self.options.validate_facets { - self.emit_struct_validator(out, s); + self.emit_struct_validator(out, s, ir); } writeln!(out, "}};\n").unwrap(); @@ -905,11 +929,39 @@ concept XmlModel = requires(T a) {{ } } - fn emit_struct_validator(&self, out: &mut String, s: &StructDef) { + fn emit_struct_validator(&self, out: &mut String, s: &StructDef, ir: &SchemaIR) { writeln!(out, "\n [[nodiscard]] bool validate() const noexcept {{").unwrap(); let mut has_checks = false; for f in &s.fields { + if let Some(simple) = super::patterned_simple(&f.type_ref, ir) { + let field = to_cpp_field_name(&f.name); + let name = type_ident(&simple.qname); + let is_string = self + .context + .map_type_ref(super::primitive_base(&simple.base_type, ir)) + == "std::string"; + let render = |target: String| { + if is_string { + target + } else { + format!("std::to_string({target})") + } + }; + if f.cardinality.is_list() || f.type_ref.is_list() { + writeln!(out, " for (const auto& value : {}) {{ if (!validate_{}_patterns({})) return false; }}", field, name, render("value".into())).unwrap(); + } else if f.cardinality.is_optional() || f.nillable { + writeln!(out, " if ({}.has_value()) {{ if (!validate_{}_patterns({})) return false; }}", field, name, render(format!("*{}", field))).unwrap(); + } else { + writeln!( + out, + " if (!validate_{}_patterns({})) return false;", + name, + render(field.clone()) + ) + .unwrap(); + } + } if let Some(ref facets) = f.facets { let field_name = to_cpp_field_name(&f.name); let is_opt = f.cardinality.is_optional() || f.nillable; @@ -943,6 +995,9 @@ concept XmlModel = requires(T a) {{ target: &str, indent: &str, ) { + for pattern in &facets.patterns { + writeln!(out, "{}try {{ static const std::regex pattern({:?}); if (!std::regex_match({}, pattern)) return false; }} catch (const std::regex_error&) {{ return false; }}", indent, pattern, target).unwrap(); + } if let Some(min_len) = facets.min_length { writeln!( out, diff --git a/crates/polyxml-core/src/codegen/csharp/mod.rs b/crates/polyxml-core/src/codegen/csharp/mod.rs index 74dba7f9..fc30832f 100644 --- a/crates/polyxml-core/src/codegen/csharp/mod.rs +++ b/crates/polyxml-core/src/codegen/csharp/mod.rs @@ -299,7 +299,11 @@ impl CSharpCodegen { if let TypeDef::Enum(e) = def { self.emit_enum(&mut out, e, indent); } else if let TypeDef::Simple(s) = def { - self.emit_simple(&mut out, s, indent); + let mut simple = s.clone(); + if !simple.facets.patterns.is_empty() { + simple.base_type = super::primitive_base(&simple.base_type, ir).clone(); + } + self.emit_simple(&mut out, &simple, indent); } } @@ -902,10 +906,12 @@ impl CSharpCodegen { .unwrap(); } for pattern in &facets.patterns { - let escaped = pattern.replace('"', "\\\""); + let escaped = format!(r"\A(?:{pattern})\z") + .replace('\\', "\\\\") + .replace('"', "\\\""); writeln!( out, - "{}if (!Regex.IsMatch({}.ToString() ?? \"\", \"^{}$\")) yield return new ValidationResult(\"{} does not match pattern {}\", [nameof({})]);", + "{}if (!Regex.IsMatch({}.ToString() ?? \"\", \"{}\")) yield return new ValidationResult(\"{} does not match pattern {}\", [nameof({})]);", indent, target, escaped, target, escaped, target ) .unwrap(); diff --git a/crates/polyxml-core/src/codegen/go/mod.rs b/crates/polyxml-core/src/codegen/go/mod.rs index 282b344a..1f7f4e22 100644 --- a/crates/polyxml-core/src/codegen/go/mod.rs +++ b/crates/polyxml-core/src/codegen/go/mod.rs @@ -252,7 +252,16 @@ impl GoCodegen { set_type_name_map(build_type_name_map(ir, to_go_type_name)); let mut body = String::new(); let mut has_time = false; - let mut has_fmt = false; + let has_patterns = self.options.validate_facets + && ir.types.values().any(|t| match t { + TypeDef::Simple(s) => !s.facets.patterns.is_empty(), + TypeDef::Struct(s) => s + .fields + .iter() + .any(|f| f.facets.as_ref().is_some_and(|f| !f.patterns.is_empty())), + _ => false, + }); + let mut has_fmt = has_patterns; let mut has_io = false; let mut has_xml = self.options.emit_xml_tags; @@ -329,6 +338,9 @@ impl GoCodegen { if has_io { imports.push("\"io\""); } + if has_patterns { + imports.push("\"regexp\""); + } if has_time { imports.push("\"time\""); } @@ -370,7 +382,7 @@ impl GoCodegen { // Emit simple types for type_def in ir.types.values() { if let TypeDef::Simple(simple) = type_def { - self.emit_simple_type(out, simple); + self.emit_simple_type(out, simple, ir); } } @@ -396,7 +408,7 @@ impl GoCodegen { } } - fn emit_simple_type(&self, out: &mut String, simple: &SimpleTypeDef) { + fn emit_simple_type(&self, out: &mut String, simple: &SimpleTypeDef, ir: &SchemaIR) { if let Some(ref doc) = simple.documentation { for line in doc.lines() { writeln!(out, "// {}", line).unwrap(); @@ -406,6 +418,21 @@ impl GoCodegen { let type_name = type_ident(&simple.qname); let base_type = self.context.map_type_ref(&simple.base_type); writeln!(out, "type {} {}\n", type_name, base_type).unwrap(); + if self.options.validate_facets && !simple.facets.patterns.is_empty() { + writeln!(out, "func (s {}) Validate() error {{", type_name).unwrap(); + for pattern in &simple.facets.patterns { + writeln!(out, " if matched, err := regexp.MatchString({:?}, fmt.Sprint(s)); err != nil || !matched {{ return fmt.Errorf(\"pattern constraint failed\") }}", format!("^(?:{pattern})$")).unwrap(); + } + writeln!(out, " return nil\n}}\n").unwrap(); + if self + .context + .map_type_ref(super::primitive_base(&simple.base_type, ir)) + == "string" + { + writeln!(out, "func (s *{}) UnmarshalText(text []byte) error {{\n value := {}(text)\n if err := value.Validate(); err != nil {{ return err }}\n *s = value\n return nil\n}}\n", type_name, type_name).unwrap(); + writeln!(out, "func (s {}) MarshalText() ([]byte, error) {{\n if err := s.Validate(); err != nil {{ return nil, err }}\n return []byte(s), nil\n}}\n", type_name).unwrap(); + } + } } fn emit_enum(&self, out: &mut String, enum_def: &EnumDef) { @@ -678,7 +705,7 @@ impl GoCodegen { writeln!(out, "}}\n").unwrap(); if self.options.validate_facets { - self.emit_struct_validator(out, s); + self.emit_struct_validator(out, s, ir); } } @@ -752,7 +779,7 @@ impl GoCodegen { } } - fn emit_struct_validator(&self, out: &mut String, s: &StructDef) { + fn emit_struct_validator(&self, out: &mut String, s: &StructDef, ir: &SchemaIR) { let struct_name = type_ident(&s.qname); writeln!(out, "func (s {}) Validate() error {{", struct_name).unwrap(); @@ -761,6 +788,20 @@ impl GoCodegen { let field_name = to_go_field_name(&f.name); let is_opt = f.cardinality.is_optional() || f.nillable; + if super::patterned_simple(&f.type_ref, ir).is_some() { + if f.cardinality.is_list() || f.type_ref.is_list() { + writeln!(out, " for _, value := range s.{} {{ if err := value.Validate(); err != nil {{ return err }} }}", field_name).unwrap(); + } else if is_opt { + writeln!(out, " if s.{} != nil {{ if err := s.{}.Validate(); err != nil {{ return err }} }}", field_name, field_name).unwrap(); + } else { + writeln!( + out, + " if err := s.{}.Validate(); err != nil {{ return err }}", + field_name + ) + .unwrap(); + } + } if let Some(ref facets) = f.facets { if is_opt { writeln!(out, " if s.{} != nil {{", field_name).unwrap(); @@ -790,6 +831,9 @@ impl GoCodegen { target: &str, indent: &str, ) { + for pattern in &facets.patterns { + writeln!(out, "{}if matched, err := regexp.MatchString({:?}, fmt.Sprint({})); err != nil || !matched {{ return fmt.Errorf(\"pattern constraint failed\") }}", indent, format!("^(?:{pattern})$"), target).unwrap(); + } if let Some(min_len) = facets.min_length { writeln!( out, diff --git a/crates/polyxml-core/src/codegen/java/codec.rs b/crates/polyxml-core/src/codegen/java/codec.rs index e5cd49b3..7bbc8a52 100644 --- a/crates/polyxml-core/src/codegen/java/codec.rs +++ b/crates/polyxml-core/src/codegen/java/codec.rs @@ -53,7 +53,14 @@ impl JavaCodegen { } } } - TypeDef::Simple(s) => check_type(&s.base_type, ir)?, + TypeDef::Simple(s) => check_type( + if s.facets.patterns.is_empty() { + &s.base_type + } else { + crate::codegen::primitive_base(&s.base_type, ir) + }, + ir, + )?, TypeDef::Union(u) => { for b in &u.branches { check_type(&b.type_ref, ir)?; @@ -151,7 +158,15 @@ impl JavaCodegen { match def { TypeDef::Struct(s) => self.emit_read_struct(&mut out, s, ir), TypeDef::Simple(s) => { - let expr = self.parse_scalar(&s.base_type, "reader.getElementText()", ir); + let expr = self.parse_scalar( + if s.facets.patterns.is_empty() { + &s.base_type + } else { + crate::codegen::primitive_base(&s.base_type, ir) + }, + "reader.getElementText()", + ir, + ); let _ = writeln!(out, " return new {name}({expr});"); } TypeDef::Enum(_) => { @@ -186,7 +201,15 @@ impl JavaCodegen { } else { "value.getValue()" }; - let expr = self.format_scalar(&s.base_type, access, ir); + let expr = self.format_scalar( + if s.facets.patterns.is_empty() { + &s.base_type + } else { + crate::codegen::primitive_base(&s.base_type, ir) + }, + access, + ir, + ); let _ = writeln!(out, " writer.writeCharacters({expr});"); } TypeDef::Union(u) => { @@ -432,7 +455,15 @@ impl JavaCodegen { Some(TypeDef::Simple(s)) => format!( "new {}({})", type_ident(q), - self.parse_scalar(&s.base_type, raw, ir) + self.parse_scalar( + if s.facets.patterns.is_empty() { + &s.base_type + } else { + crate::codegen::primitive_base(&s.base_type, ir) + }, + raw, + ir + ) ), _ => "null".into(), }, @@ -468,7 +499,7 @@ impl JavaCodegen { TypeRef::List(t)=>format!("{value}.stream().map(item -> {}).collect(java.util.stream.Collectors.joining(\" \"))",self.format_scalar(t,"item",ir)), TypeRef::Named(q)=>match ir.types.get(q) { Some(TypeDef::Enum(_))=>format!("{value}.getValue()"), - Some(TypeDef::Simple(s))=>self.format_scalar(&s.base_type,&format!("{value}.{}",if self.options.use_records {"value()"}else{"getValue()"}),ir), + Some(TypeDef::Simple(s))=>self.format_scalar(if s.facets.patterns.is_empty() { &s.base_type } else { crate::codegen::primitive_base(&s.base_type, ir) },&format!("{value}.{}",if self.options.use_records {"value()"}else{"getValue()"}),ir), _=>format!("String.valueOf({value})"), }, TypeRef::Primitive(PrimitiveType::Base64Binary)=>format!("java.util.Base64.getEncoder().encodeToString({value})"), diff --git a/crates/polyxml-core/src/codegen/java/mod.rs b/crates/polyxml-core/src/codegen/java/mod.rs index 7ad0a4c3..7fcb3ed9 100644 --- a/crates/polyxml-core/src/codegen/java/mod.rs +++ b/crates/polyxml-core/src/codegen/java/mod.rs @@ -394,7 +394,13 @@ impl JavaCodegen { } TypeDef::Simple(s) => { let name = type_ident(&s.qname); - self.emit_simple(&mut out, s, &name, indent); + { + let mut simple = s.clone(); + if !simple.facets.patterns.is_empty() { + simple.base_type = super::primitive_base(&simple.base_type, ir).clone(); + } + self.emit_simple(&mut out, &simple, &name, indent); + } name } }; @@ -829,7 +835,7 @@ impl JavaCodegen { } for pat in &facets.patterns { checks.push(format!( - "{}.ifPresent(v -> {{ if (!Pattern.matches({:?}, v)) throw new IllegalArgumentException(\"{} does not match pattern: \" + {:?}); }});", + "{}.ifPresent(v -> {{ if (!Pattern.compile({:?}).matcher(v).matches()) throw new IllegalArgumentException(\"{} does not match pattern: \" + {:?}); }});", var_name, pat, var_name, pat )); } @@ -858,7 +864,7 @@ impl JavaCodegen { } for pat in &facets.patterns { checks.push(format!( - "if (!Pattern.matches({:?}, {})) throw new IllegalArgumentException(\"{} does not match pattern: \" + {:?});", + "if (!Pattern.compile({:?}).matcher({}).matches()) throw new IllegalArgumentException(\"{} does not match pattern: \" + {:?});", pat, var_name, var_name, pat )); } diff --git a/crates/polyxml-core/src/codegen/java/models.rs b/crates/polyxml-core/src/codegen/java/models.rs index bce6a35a..b8861f8a 100644 --- a/crates/polyxml-core/src/codegen/java/models.rs +++ b/crates/polyxml-core/src/codegen/java/models.rs @@ -1,4 +1,5 @@ use super::*; +use crate::codegen::flatten_fields; use crate::ir::{FieldDef, FieldKind}; impl JavaCodegen { @@ -38,28 +39,11 @@ impl JavaCodegen { s: &'a StructDef, ir: &'a SchemaIR, ) -> Vec<(&'a FieldDef, String)> { - fn collect<'a>( - s: &'a StructDef, - ir: &'a SchemaIR, - seen: &mut HashSet, - fields: &mut Vec<&'a FieldDef>, - ) { - if !seen.insert(s.qname.clone()) { - return; - } - if let Some(TypeDef::Struct(base)) = s.base_type.as_ref().and_then(|q| ir.types.get(q)) - { - collect(base, ir, seen, fields); - } - fields.extend(&s.fields); - } - let mut fields = Vec::new(); - if self.options.use_records && !self.options.emit_builder && !self.options.emit_direct_codec - { - fields.extend(&s.fields); - } else { - collect(s, ir, &mut HashSet::new(), &mut fields); - } + // Records cannot `extends`, so every record mode must inline the base + // chain — a plain record previously declared only its own components + // and silently dropped inherited fields. Classes re-slice back to + // their own tail for field declarations because they inherit instead. + let fields = flatten_fields(s, ir); let mut seen = HashSet::new(); fields .into_iter() diff --git a/crates/polyxml-core/src/codegen/mod.rs b/crates/polyxml-core/src/codegen/mod.rs index 635645d0..18525182 100644 --- a/crates/polyxml-core/src/codegen/mod.rs +++ b/crates/polyxml-core/src/codegen/mod.rs @@ -22,7 +22,7 @@ pub use python::{PythonBackend, PythonCodegen, PythonOptions}; pub use rust::{RustCodegen, RustOptions}; pub use typescript::{TypeScriptBackend, TypeScriptCodegen, TypeScriptOptions}; -use crate::ir::{PrimitiveType, QName, SchemaIR, TypeRef}; +use crate::ir::{FieldDef, PrimitiveType, QName, SchemaIR, StructDef, TypeRef}; #[derive(Debug, Error)] pub enum CodegenError { @@ -598,3 +598,89 @@ fn generic_keywords() -> &'static HashSet<&'static str> { .collect() }) } + +/// Resolve a simple alias to its underlying primitive without looping on cycles. +pub(crate) fn primitive_base<'a>(ty: &'a TypeRef, ir: &'a SchemaIR) -> &'a TypeRef { + let mut current = ty; + let mut visited = HashSet::new(); + while let TypeRef::Named(q) = current { + if !visited.insert(q) { + break; + } + match ir.types.get(q) { + Some(crate::ir::TypeDef::Simple(s)) => current = &s.base_type, + _ => break, + } + } + current +} + +/// Patterned simple types referenced by fields, including list/boxed wrappers. +pub(crate) fn patterned_simple<'a>( + ty: &TypeRef, + ir: &'a SchemaIR, +) -> Option<&'a crate::ir::SimpleTypeDef> { + match ty { + TypeRef::Named(q) => match ir.types.get(q) { + Some(crate::ir::TypeDef::Simple(s)) if !s.facets.patterns.is_empty() => Some(s), + _ => None, + }, + TypeRef::Boxed(inner) | TypeRef::List(inner) => patterned_simple(inner, ir), + _ => None, + } +} + +/// Flattened field list for a struct: every field inherited through its +/// `xsd:extension` base chain (root first), followed by the struct's own +/// fields. Backends whose targets cannot represent the base chain directly — +/// Rust structs and Java records have no inheritance — must emit this exact +/// list; declaration, name allocation, and both codec directions must agree +/// on it or derived types silently drop inherited data. +/// +/// When a derived type re-declares a field whose schema name matches an +/// inherited one (the `simpleContent` value field, for instance), the +/// most-derived declaration wins and the inherited duplicate is skipped, so +/// flattening never introduces suffixed `value2`-style duplicates. A base +/// chain that revisits a type is cut at the revisit. +pub(crate) fn flatten_fields<'a>(s: &'a StructDef, ir: &'a SchemaIR) -> Vec<&'a FieldDef> { + fn collect_bases<'a>( + s: &'a StructDef, + ir: &'a SchemaIR, + seen: &mut HashSet, + out: &mut Vec<&'a StructDef>, + ) { + if !seen.insert(s.qname.clone()) { + return; + } + if let Some(crate::ir::TypeDef::Struct(base)) = + s.base_type.as_ref().and_then(|q| ir.types.get(q)) + { + collect_bases(base, ir, seen, out); + out.push(base); + } + } + + let mut bases = Vec::new(); + collect_bases(s, ir, &mut HashSet::new(), &mut bases); + if bases.is_empty() { + return s.fields.iter().collect(); + } + + // Claim schema names leaf-to-root so the most-derived declaration of a + // name shadows any inherited duplicate. Names are claimed per struct, not + // per field, so an attribute and an element sharing a name inside one + // struct are both preserved. + let mut claimed: HashSet<&str> = HashSet::new(); + let mut groups: Vec> = Vec::new(); + for st in std::iter::once(s).chain(bases.iter().rev().copied()) { + let group: Vec<&FieldDef> = st + .fields + .iter() + .filter(|f| !claimed.contains(f.name.as_str())) + .collect(); + claimed.extend(group.iter().map(|f| f.name.as_str())); + groups.push(group); + } + groups.reverse(); + groups.into_iter().flatten().collect() +} diff --git a/crates/polyxml-core/src/codegen/python/mod.rs b/crates/polyxml-core/src/codegen/python/mod.rs index 23a7dc8f..f9350445 100644 --- a/crates/polyxml-core/src/codegen/python/mod.rs +++ b/crates/polyxml-core/src/codegen/python/mod.rs @@ -21,7 +21,7 @@ const PATTERN_VALIDATOR_HELPER: &str = r#"def _polyxml_patterns(*patterns: str): def _validate(value: str) -> str: for regex in _compiled: - if regex.search(value) is None: + if regex.fullmatch(value) is None: raise ValueError(f"string_pattern_mismatch: {regex.pattern!r}") return value @@ -568,6 +568,12 @@ impl PythonCodegen { if let Some(ref ns) = s.qname.namespace { let _ = writeln!(out, " namespace = \"{}\"", ns); } + // Marks the type as a dispatch base: the runtime raises a clear + // error when xsi:type targets an abstract type with no registered + // derivations (issue #53). + if s.is_abstract { + out.push_str(" abstract = True\n"); + } has_body = true; } @@ -883,7 +889,7 @@ impl PythonCodegen { clauses.push(format!("min_length={}, max_length={}", l, l)); } if let Some(pat) = facets.patterns.first() { - clauses.push(format!("pattern=r\"{}\"", pat)); + clauses.push(format!("pattern=r\"\\A(?:{})\\z\"", pat)); } clauses.join(", ") diff --git a/crates/polyxml-core/src/codegen/rust/mod.rs b/crates/polyxml-core/src/codegen/rust/mod.rs index f712067d..8a0e728e 100644 --- a/crates/polyxml-core/src/codegen/rust/mod.rs +++ b/crates/polyxml-core/src/codegen/rust/mod.rs @@ -1,11 +1,12 @@ -use std::collections::{BTreeSet, HashSet}; +use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt::Write as FmtWrite; use heck::{AsPascalCase, AsSnakeCase}; use serde::{Deserialize, Serialize}; use crate::codegen::{ - build_type_name_map, lookup_type_name, sanitize_keyword, set_type_name_map, LanguageContext, + build_type_name_map, flatten_fields, lookup_type_name, sanitize_keyword, set_type_name_map, + LanguageContext, }; use crate::ir::{ EnumDef, FieldDef, FieldKind, PrimitiveType, QName, SchemaIR, SimpleTypeDef, StructDef, @@ -29,6 +30,10 @@ pub struct RustOptions { pub emit_codecs: bool, /// Derive rkyv::{Archive, Serialize, Deserialize} zero-copy wire format serialization (default: false). pub emit_rkyv: bool, + /// Emit compile-time perfect-hash (phf) element-tag dispatch instead of a + /// string `match` (default: false). Generated crates must depend on `phf`. + #[serde(default)] + pub phf: bool, /// Custom header text to prepend to generated files (default: None). pub custom_header: Option, } @@ -43,6 +48,7 @@ impl Default for RustOptions { emit_root_aliases: true, emit_codecs: true, emit_rkyv: false, + phf: false, custom_header: None, } } @@ -226,7 +232,7 @@ impl RustCodegen { out.push_str("// @generated by PolyXML Compiler (https://github.com/nth-bailey/PolyXML)\n"); out.push_str( - "#![allow(dead_code, unused_imports, unused_mut, unused_variables, non_camel_case_types, non_snake_case)]\n\n", + "#![allow(dead_code, unused_imports, unused_mut, unused_variables, unused_assignments, non_camel_case_types, non_snake_case)]\n\n", ); let types_with_lifetime = if self.options.zero_copy { @@ -325,9 +331,8 @@ impl RustCodegen { .branches .iter() .any(|b| typeref_has_lifetime(&b.type_ref, &requires_lifetime)), - TypeDef::Struct(s) => s - .fields - .iter() + TypeDef::Struct(s) => flatten_fields(s, ir) + .into_iter() .any(|f| typeref_has_lifetime(&f.type_ref, &requires_lifetime)), }; @@ -437,6 +442,17 @@ impl RustCodegen { } else { let _ = writeln!(out, "pub type {} = {};", type_name, base_type); } + + if !s.facets.patterns.is_empty() { + let _ = writeln!(out, "pub fn validate_{}_patterns(value: &str) -> std::result::Result<(), &'static str> {{", type_name); + for (i, pattern) in s.facets.patterns.iter().enumerate() { + let _ = writeln!(out, " static PATTERN_{}: std::sync::OnceLock> = std::sync::OnceLock::new();", i); + let full_pattern = format!(r"\A(?:{pattern})\z"); + let _ = writeln!(out, " let pattern = PATTERN_{}.get_or_init(|| regex::Regex::new({:?})).as_ref().map_err(|_| \"unsupported pattern syntax\")?;", i, full_pattern); + out.push_str(" if !pattern.is_match(value) { return Err(\"pattern constraint failed\"); }\n"); + } + out.push_str(" Ok(())\n}\n"); + } } fn emit_enum(&self, out: &mut String, e: &EnumDef) { @@ -643,7 +659,7 @@ impl RustCodegen { let _ = writeln!(out, "{} {{", struct_decl); let mut seen_fields = HashSet::new(); - for field in &s.fields { + for field in flatten_fields(s, ir) { let rust_name = self.unique_rust_field_name(&field.name, &mut seen_fields); self.emit_struct_field(out, field, &rust_name, types_with_lifetime); } @@ -835,7 +851,27 @@ impl RustCodegen { out.push_str(" },\n"); out.push_str(" Cow::Owned(s) => Cow::Owned(quick_xml::escape::unescape(&s)?.into_owned()),\n"); out.push_str(" };\n"); - out.push_str(" text = raw;\n"); + out.push_str(" if text.is_empty() {\n"); + out.push_str(" text = raw;\n"); + out.push_str(" } else {\n"); + out.push_str(" text.to_mut().push_str(&raw);\n"); + out.push_str(" }\n"); + out.push_str(" }\n"); + out.push_str(" Event::CData(c) => {\n"); + out.push_str(" text.to_mut().push_str(c.as_ref());\n"); + out.push_str(" }\n"); + out.push_str(" Event::GeneralRef(r) => {\n"); + out.push_str(" if r.is_char_ref() {\n"); + out.push_str(" if let Some(ch) = r.resolve_char_ref()? {\n"); + out.push_str(" text.to_mut().push(ch);\n"); + out.push_str(" }\n"); + out.push_str( + " } else if let Some(val) = quick_xml::escape::resolve_xml_entity(r.as_ref()) {\n", + ); + out.push_str(" text.to_mut().push_str(val);\n"); + out.push_str(" } else {\n"); + out.push_str(" text.to_mut().push_str(r.as_ref());\n"); + out.push_str(" }\n"); out.push_str(" }\n"); out.push_str( " Event::End(e) if e.local_name().as_ref() == tag_name => break,\n", @@ -856,7 +892,23 @@ impl RustCodegen { out.push_str( " let unescaped = quick_xml::escape::unescape(t.as_ref())?;\n", ); - out.push_str(" text = unescaped.into_owned();\n"); + out.push_str(" text.push_str(&unescaped);\n"); + out.push_str(" }\n"); + out.push_str(" Event::CData(c) => {\n"); + out.push_str(" text.push_str(c.as_ref());\n"); + out.push_str(" }\n"); + out.push_str(" Event::GeneralRef(r) => {\n"); + out.push_str(" if r.is_char_ref() {\n"); + out.push_str(" if let Some(ch) = r.resolve_char_ref()? {\n"); + out.push_str(" text.push(ch);\n"); + out.push_str(" }\n"); + out.push_str( + " } else if let Some(val) = quick_xml::escape::resolve_xml_entity(r.as_ref()) {\n", + ); + out.push_str(" text.push_str(val);\n"); + out.push_str(" } else {\n"); + out.push_str(" text.push_str(r.as_ref());\n"); + out.push_str(" }\n"); out.push_str(" }\n"); out.push_str( " Event::End(e) if e.local_name().as_ref() == tag_name => break,\n", @@ -998,6 +1050,9 @@ impl RustCodegen { types_with_lifetime: &HashSet, ir: &SchemaIR, ) { + let has_patterns = flatten_fields(s, ir) + .into_iter() + .any(|f| super::patterned_simple(&f.type_ref, ir).is_some()); let struct_name = type_ident(&s.qname); let needs_lifetime = types_with_lifetime.contains(&s.qname); @@ -1007,6 +1062,72 @@ impl RustCodegen { format!("impl {}", struct_name) }; + // --feature phf: emit a module-level perfect-hash dispatch table (enum + // + static, built with phf_codegen) ahead of the impl and route both + // child-event match sites through it. Fully gated so default output + // stays byte-identical. + let phf_enum_name = format!("__{}ElementId", struct_name); + let phf_static_name = format!("__{}_ELEMENT_DISPATCH", struct_name.to_uppercase()); + let mut phf_arms: Vec<(String, String, Vec)> = Vec::new(); + if self.options.phf { + let mut seen_fields = HashSet::new(); + let mut seen_variants = HashSet::new(); + for field in flatten_fields(s, ir) { + // Uniquify over EVERY field (attrs/text included) so names stay + // in lockstep with field_metas and struct-field emission; a + // non-element sharing a name with a later element must still + // consume its slot or dispatch lookups miss and fall back to a + // bare-string arm inside the Option match (won't compile). + let rust_name = self.unique_rust_field_name(&field.name, &mut seen_fields); + if field.kind != FieldKind::Element { + continue; + } + let tags: Vec = match self.resolve_union_def(&field.type_ref, ir) { + Some(u) => u.branches.iter().map(|b| b.xml_name.clone()).collect(), + None => vec![field.xml_name.clone()], + }; + let base_variant = to_rust_variant_identifier(rust_name.trim_start_matches("r#")); + let mut variant = base_variant.clone(); + let mut n = 2; + while !seen_variants.insert(variant.clone()) { + variant = format!("{}{}", base_variant, n); + n += 1; + } + phf_arms.push((rust_name, variant, tags)); + } + if !phf_arms.is_empty() { + out.push('\n'); + let _ = writeln!( + out, + "/// Perfect-hash element dispatch id for `{struct_name}` (`--feature phf`)." + ); + out.push_str("#[derive(Clone, Copy)]\n"); + let _ = writeln!(out, "enum {} {{", phf_enum_name); + for (_, variant, _) in &phf_arms { + let _ = writeln!(out, " {},", variant); + } + out.push_str("}\n\n"); + let mut map = phf_codegen::Map::new(); + for (_, variant, tags) in &phf_arms { + for tag in tags { + map.entry(tag.as_str(), format!("{}::{}", phf_enum_name, variant)); + } + } + let _ = writeln!( + out, + "static {}: ::phf::Map<&'static str, {}> = {};", + phf_static_name, + phf_enum_name, + map.build() + ); + out.push('\n'); + } + } + let phf_variant_of: HashMap<&str, &str> = phf_arms + .iter() + .map(|(rust_name, variant, _)| (rust_name.as_str(), variant.as_str())) + .collect(); + out.push('\n'); let _ = writeln!(out, "{} {{", impl_header); @@ -1060,7 +1181,7 @@ impl RustCodegen { rust_name: String, } let mut field_metas = Vec::new(); - for field in &s.fields { + for field in flatten_fields(s, ir) { let rust_name = self.unique_rust_field_name(&field.name, &mut seen_fields); field_metas.push(FieldMeta { field: field.clone(), @@ -1077,6 +1198,8 @@ impl RustCodegen { } } + let text_meta = field_metas.iter().find(|m| m.field.kind == FieldKind::Text); + let attr_fields: Vec<_> = field_metas .iter() .filter(|m| m.field.kind == FieldKind::Attribute) @@ -1103,7 +1226,17 @@ impl RustCodegen { if !elem_fields.is_empty() { out.push_str("\n loop {\n"); out.push_str(" match reader.read_event()? {\n"); - out.push_str(" Event::Start(e) => match e.local_name().as_ref() {\n"); + if phf_variant_of.is_empty() { + out.push_str( + " Event::Start(e) => match e.local_name().as_ref() {\n", + ); + } else { + let _ = writeln!( + out, + " Event::Start(e) => match {}.get(e.local_name().as_ref()) {{", + phf_static_name + ); + } for meta in &elem_fields { if let Some(union_def) = self.resolve_union_def(&meta.field.type_ref, ir) { @@ -1112,7 +1245,16 @@ impl RustCodegen { .iter() .map(|b| format!("\"{}\"", b.xml_name)) .collect(); - let _ = writeln!(out, " {} => {{", branch_tags.join(" | ")); + if let Some(variant) = phf_variant_of.get(meta.rust_name.as_str()) { + let _ = writeln!( + out, + " Some(&{}::{}) => {{", + phf_enum_name, variant + ); + } else { + let _ = + writeln!(out, " {} => {{", branch_tags.join(" | ")); + } let union_name = type_ident(&union_def.qname); let _ = writeln!( out, @@ -1135,7 +1277,16 @@ impl RustCodegen { } out.push_str(" }\n"); } else { - let _ = writeln!(out, " \"{}\" => {{", meta.field.xml_name); + if let Some(variant) = phf_variant_of.get(meta.rust_name.as_str()) { + let _ = writeln!( + out, + " Some(&{}::{}) => {{", + phf_enum_name, variant + ); + } else { + let _ = + writeln!(out, " \"{}\" => {{", meta.field.xml_name); + } self.emit_element_parse( out, &meta.field, @@ -1152,7 +1303,17 @@ impl RustCodegen { out.push_str(" }\n"); out.push_str(" },\n"); - out.push_str(" Event::Empty(e) => match e.local_name().as_ref() {\n"); + if phf_variant_of.is_empty() { + out.push_str( + " Event::Empty(e) => match e.local_name().as_ref() {\n", + ); + } else { + let _ = writeln!( + out, + " Event::Empty(e) => match {}.get(e.local_name().as_ref()) {{", + phf_static_name + ); + } for meta in &elem_fields { if let Some(union_def) = self.resolve_union_def(&meta.field.type_ref, ir) { let branch_tags: Vec<_> = union_def @@ -1160,7 +1321,16 @@ impl RustCodegen { .iter() .map(|b| format!("\"{}\"", b.xml_name)) .collect(); - let _ = writeln!(out, " {} => {{", branch_tags.join(" | ")); + if let Some(variant) = phf_variant_of.get(meta.rust_name.as_str()) { + let _ = writeln!( + out, + " Some(&{}::{}) => {{", + phf_enum_name, variant + ); + } else { + let _ = + writeln!(out, " {} => {{", branch_tags.join(" | ")); + } let union_name = type_ident(&union_def.qname); let _ = writeln!( out, @@ -1183,7 +1353,16 @@ impl RustCodegen { } out.push_str(" }\n"); } else { - let _ = writeln!(out, " \"{}\" => {{", meta.field.xml_name); + if let Some(variant) = phf_variant_of.get(meta.rust_name.as_str()) { + let _ = writeln!( + out, + " Some(&{}::{}) => {{", + phf_enum_name, variant + ); + } else { + let _ = + writeln!(out, " \"{}\" => {{", meta.field.xml_name); + } self.emit_empty_element_parse( out, &meta.field, @@ -1204,7 +1383,20 @@ impl RustCodegen { out.push_str(" }\n"); } - out.push_str("\n Ok(Self {\n"); + // simpleContent value field: consume the element's own text content. + // Only reachable when there are no child elements — the element loop + // above already reads through End(start). + if elem_fields.is_empty() { + if let Some(meta) = &text_meta { + self.emit_text_content_parse(out, &meta.field, &meta.rust_name, ir); + } + } + + if has_patterns { + out.push_str("\n let value = Self {\n"); + } else { + out.push_str("\n Ok(Self {\n"); + } for meta in &field_metas { let is_list = meta.field.cardinality.is_list() || meta.field.type_ref.is_list(); let is_optional = meta.field.cardinality.is_optional() || meta.field.nillable; @@ -1223,7 +1415,11 @@ impl RustCodegen { ); } } - out.push_str(" })\n"); + if has_patterns { + out.push_str(" };\n value.validate_patterns()?;\n Ok(value)\n"); + } else { + out.push_str(" })\n"); + } out.push_str(" }\n\n"); out.push_str(" pub fn decode_xml_empty(start: &BytesStart<'_>) -> Result {\n"); @@ -1250,7 +1446,11 @@ impl RustCodegen { out.push_str(" }\n"); } - out.push_str("\n Ok(Self {\n"); + if has_patterns { + out.push_str("\n let value = Self {\n"); + } else { + out.push_str("\n Ok(Self {\n"); + } for meta in &field_metas { let is_list = meta.field.cardinality.is_list() || meta.field.type_ref.is_list(); let is_optional = meta.field.cardinality.is_optional() || meta.field.nillable; @@ -1295,7 +1495,11 @@ impl RustCodegen { ); } } - out.push_str(" })\n"); + if has_patterns { + out.push_str(" };\n value.validate_patterns()?;\n Ok(value)\n"); + } else { + out.push_str(" })\n"); + } out.push_str(" }\n\n"); out.push_str(" pub fn to_xml(&self) -> Result> {\n"); @@ -1340,7 +1544,41 @@ impl RustCodegen { out.push_str(" }\n\n"); } + if has_patterns { + out.push_str(" pub fn validate_patterns(&self) -> Result<()> {\n"); + for meta in &field_metas { + let f = &meta.field; + if super::patterned_simple(&f.type_ref, ir).is_none() { + continue; + } + if f.cardinality.is_list() || f.type_ref.is_list() { + let _ = writeln!(out, " for value in &self.{} {{", meta.rust_name); + self.emit_pattern_check(out, f, "&value.to_string()", ir); + out.push_str(" }\n"); + } else if f.cardinality.is_optional() || f.nillable { + let _ = writeln!( + out, + " if let Some(value) = &self.{} {{", + meta.rust_name + ); + self.emit_pattern_check(out, f, "&value.to_string()", ir); + out.push_str(" }\n"); + } else { + self.emit_pattern_check( + out, + f, + &format!("&self.{}.to_string()", meta.rust_name), + ir, + ); + } + } + + out.push_str(" Ok(())\n }\n"); + } out.push_str(" pub fn encode_xml(&self, writer: &mut Writer, tag_name: Option<&str>) -> Result<()> {\n"); + if has_patterns { + out.push_str(" self.validate_patterns()?;\n"); + } let _ = writeln!( out, " let tag = tag_name.unwrap_or(\"{}\");", @@ -1354,6 +1592,15 @@ impl RustCodegen { out.push_str(" writer.write_event(Event::Start(start))?;\n"); + // simpleContent value: write the element's own text content between + // Start and End. Text and child content are mutually exclusive in the + // IR, so this never overlaps the element loop below. + if elem_fields.is_empty() { + if let Some(meta) = &text_meta { + self.emit_text_content_serialize(out, &meta.field, &meta.rust_name, ir); + } + } + for meta in &elem_fields { self.emit_element_serialize(out, &meta.field, &meta.rust_name, ir); } @@ -1365,6 +1612,12 @@ impl RustCodegen { out.push_str("}\n"); } + fn emit_pattern_check(&self, out: &mut String, field: &FieldDef, value: &str, ir: &SchemaIR) { + if let Some(simple) = super::patterned_simple(&field.type_ref, ir) { + let _ = writeln!(out, " validate_{}_patterns({}).map_err(|message| PolyXmlError::FacetViolation {{ field: {:?}.into(), expected: message.into(), actual: ({}).to_string() }})?;", type_ident(&simple.qname), value, field.name, value); + } + } + fn emit_attr_parse(&self, out: &mut String, field: &FieldDef, rust_name: &str, ir: &SchemaIR) { if self.field_is_string(&field.type_ref, ir) { out.push_str(" let s = attr.value.as_ref();\n"); @@ -1375,6 +1628,7 @@ impl RustCodegen { out.push_str(" Cow::Owned(s) => Cow::Owned(s),\n"); out.push_str(" };\n"); self.emit_facet_checks(out, field, "val"); + self.emit_pattern_check(out, field, "&val", ir); if self.options.zero_copy { let _ = writeln!(out, " var_{} = Some(val);", rust_name); } else { @@ -1426,6 +1680,7 @@ impl RustCodegen { field.xml_name ); self.emit_facet_checks(out, field, "text"); + self.emit_pattern_check(out, field, "&text", ir); if is_list { let _ = writeln!(out, " var_{}.push(text);", rust_name); } else { @@ -1532,6 +1787,7 @@ impl RustCodegen { let is_boxed = field.is_cycle_cut || field.type_ref.is_boxed(); if self.field_is_string(&field.type_ref, ir) { + self.emit_pattern_check(out, field, "\"\"", ir); if self.options.zero_copy { if is_list { let _ = writeln!( @@ -1589,6 +1845,106 @@ impl RustCodegen { } } + /// Decode the simpleContent value field from the element's own text + /// content. Emitted only when the struct has no child elements (the + /// element loop already consumes through `End(start)`), and only for + /// scalar-backed text: a simpleContent chain whose value is typed as its + /// base struct has no scalar form here and keeps its previous behavior. + fn emit_text_content_parse( + &self, + out: &mut String, + field: &FieldDef, + rust_name: &str, + ir: &SchemaIR, + ) { + let read = " let text = read_element_text(reader, start.local_name().as_ref())?;\n"; + if self.field_is_string(&field.type_ref, ir) { + out.push_str(read); + self.emit_facet_checks(out, field, "text"); + self.emit_pattern_check(out, field, "&text", ir); + let _ = writeln!(out, " var_{} = Some(text);", rust_name); + } else if let Some(num) = self.field_numeric_type(&field.type_ref, ir) { + out.push_str(read); + out.push_str(" let s = text.trim();\n"); + let _ = writeln!( + out, + " let val = s.parse::<{}>().map_err(|_| PolyXmlError::ScalarParseError {{ field: \"{}\".into(), expected: \"{}\", value: s.into() }})?;", + num, field.name, num + ); + self.emit_facet_checks(out, field, "val"); + let _ = writeln!(out, " var_{} = Some(val);", rust_name); + } else if self.field_is_bool(&field.type_ref, ir) { + out.push_str(read); + out.push_str(" let s = text.trim();\n"); + let _ = writeln!(out, " let val = match s {{ \"true\" | \"1\" => true, \"false\" | \"0\" => false, _ => return Err(PolyXmlError::ScalarParseError {{ field: {:?}.into(), expected: \"bool\", value: s.into() }}) }};", field.name); + let _ = writeln!(out, " var_{} = Some(val);", rust_name); + } else if let Some(enum_name) = self.field_is_enum(&field.type_ref, ir) { + out.push_str(read); + out.push_str(" let s = text.trim();\n"); + let _ = writeln!( + out, + " let val = {}::from_str(s).map_err(|_| PolyXmlError::ScalarParseError {{ field: \"{}\".into(), expected: \"{}\", value: s.into() }})?;", + enum_name, field.name, enum_name + ); + let _ = writeln!(out, " var_{} = Some(val);", rust_name); + } + } + + /// Serialize the simpleContent value field as the element's own text + /// content; mirrors [`Self::emit_text_content_parse`] and skips the same + /// struct-typed values. + fn emit_text_content_serialize( + &self, + out: &mut String, + field: &FieldDef, + rust_name: &str, + ir: &SchemaIR, + ) { + let is_optional = field.cardinality.is_optional() || field.nillable; + if self.field_is_string(&field.type_ref, ir) { + if is_optional { + let _ = writeln!(out, " if let Some(ref val) = self.{} {{", rust_name); + out.push_str( + " writer.write_event(Event::Text(BytesText::new(val.as_ref())))?;\n }\n", + ); + } else { + let _ = writeln!( + out, + " writer.write_event(Event::Text(BytesText::new(self.{}.as_ref())))?;", + rust_name + ); + } + } else if self.field_numeric_type(&field.type_ref, ir).is_some() + || self.field_is_bool(&field.type_ref, ir) + { + if is_optional { + let _ = writeln!(out, " if let Some(ref val) = self.{} {{", rust_name); + out.push_str( + " writer.write_event(Event::Text(BytesText::new(&val.to_string())))?;\n }\n", + ); + } else { + let _ = writeln!( + out, + " writer.write_event(Event::Text(BytesText::new(&self.{}.to_string())))?;", + rust_name + ); + } + } else if self.field_is_enum(&field.type_ref, ir).is_some() { + if is_optional { + let _ = writeln!(out, " if let Some(ref val) = self.{} {{", rust_name); + out.push_str( + " writer.write_event(Event::Text(BytesText::new(val.as_str())))?;\n }\n", + ); + } else { + let _ = writeln!( + out, + " writer.write_event(Event::Text(BytesText::new(self.{}.as_str())))?;", + rust_name + ); + } + } + } + fn emit_facet_checks(&self, out: &mut String, field: &FieldDef, val_var: &str) { if let Some(ref facets) = field.facets { if let Some(min_len) = facets.min_length { diff --git a/crates/polyxml-core/src/codegen/typescript/mod.rs b/crates/polyxml-core/src/codegen/typescript/mod.rs index 9ee30349..03f706e9 100644 --- a/crates/polyxml-core/src/codegen/typescript/mod.rs +++ b/crates/polyxml-core/src/codegen/typescript/mod.rs @@ -219,7 +219,13 @@ impl TypeScriptCodegen { let ordered = self.order_types(ir); for type_def in ordered { match type_def { - TypeDef::Simple(s) => self.emit_simple_type(&mut out, s), + TypeDef::Simple(s) => { + let mut simple = s.clone(); + if !simple.facets.patterns.is_empty() { + simple.base_type = super::primitive_base(&simple.base_type, ir).clone(); + } + self.emit_simple_type(&mut out, &simple) + } TypeDef::Enum(e) => self.emit_enum(&mut out, e), TypeDef::Union(u) => self.emit_union(&mut out, u), TypeDef::Struct(s) => self.emit_struct(&mut out, s, ir, &recursive_types), @@ -784,7 +790,10 @@ impl TypeScriptCodegen { zod_expr.push_str(&format!(".length({})", length)); } for pat in &facets.patterns { - zod_expr.push_str(&format!(".regex(new RegExp({:?}))", pat)); + zod_expr.push_str(&format!( + ".regex(new RegExp({:?}))", + format!("^(?:{pat})(?![\\s\\S])") + )); } } @@ -851,7 +860,10 @@ impl TypeScriptCodegen { actions.push(format!("v.length({})", length)); } for pat in &facets.patterns { - actions.push(format!("v.regex(/{}/)", pat)); + actions.push(format!( + "v.regex(/^(?:{})(?![\\s\\S])/)", + pat.replace('/', "\\/") + )); } } if is_num { @@ -920,8 +932,11 @@ impl TypeScriptCodegen { if let Some(length) = facets.length { opts.push(format!("minLength: {}, maxLength: {}", length, length)); } - for pat in &facets.patterns { - opts.push(format!("pattern: {:?}", pat)); + if facets.patterns.len() == 1 { + opts.push(format!( + "pattern: {:?}", + format!("^(?:{})(?![\\s\\S])", facets.patterns[0]) + )); } } if is_num { @@ -948,6 +963,19 @@ impl TypeScriptCodegen { expr.push(')'); } } + if is_string && facets.patterns.len() > 1 { + let schemas = facets + .patterns + .iter() + .map(|p| { + format!( + "Type.String({{ pattern: {:?} }})", + format!("^(?:{p})(?![\\s\\S])") + ) + }) + .collect::>(); + *expr = format!("Type.Intersect([{}, {}])", expr, schemas.join(", ")); + } } fn emit_docstring(&self, out: &mut String, doc: &str, indent: &str) { diff --git a/crates/polyxml-core/src/ir/mod.rs b/crates/polyxml-core/src/ir/mod.rs index 2ed25808..bd038a4c 100644 --- a/crates/polyxml-core/src/ir/mod.rs +++ b/crates/polyxml-core/src/ir/mod.rs @@ -249,6 +249,9 @@ pub struct RestrictionFacets { pub min_length: Option, pub max_length: Option, pub length: Option, + /// AND-combined derivation steps. Each entry is one regex, with alternatives + /// from the same restriction normalized to `(first)|(second)` (OR). + /// A singleton restriction retains its original regex verbatim. pub patterns: Vec, pub enumerations: Vec, pub white_space: Option, diff --git a/crates/polyxml-core/src/json.rs b/crates/polyxml-core/src/json.rs index 8773eed9..687a75b1 100644 --- a/crates/polyxml-core/src/json.rs +++ b/crates/polyxml-core/src/json.rs @@ -76,6 +76,14 @@ pub fn poly_value_to_json_value( schema: &ModelSchema, by_alias: bool, ) -> JsonValue { + // xsi:type dispatch (issue #53): a Record parsed as a concrete + // derivation iterates its own schema so variant-only fields survive + // transcoding (JSON carries no xsi:type marker). + let schema = match val { + PolyValue::Record { schema: rec, .. } if rec.name != schema.name => rec.as_ref(), + _ => schema, + }; + let get_field = |idx: usize, name: &str| -> Option<&PolyValue> { match val { PolyValue::Record { values, .. } => values.get(idx).and_then(|v| v.as_ref()), diff --git a/crates/polyxml-core/src/parser.rs b/crates/polyxml-core/src/parser.rs index 24e1e5b2..df8d472a 100644 --- a/crates/polyxml-core/src/parser.rs +++ b/crates/polyxml-core/src/parser.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::sync::Arc; use quick_xml::events::{BytesStart, Event}; @@ -9,6 +10,8 @@ use crate::error::{PolyXmlError, Result}; use crate::schema::{ModelSchema, ScalarType, ValueType}; use crate::value::PolyValue; +type NamespaceScope = Arc>; + pub(crate) struct StackFrame { schema: Arc, values: SmallVec<[Option; 8]>, @@ -101,6 +104,110 @@ fn is_nil_element(e: &BytesStart) -> bool { false } +/// Extend inherited namespace bindings for this element. Elements without +/// declarations share the existing map. +fn namespace_scope(parent: &NamespaceScope, e: &BytesStart) -> NamespaceScope { + let mut scope = None; + for attr in e.attributes().flatten() { + let key = attr.key.as_ref(); + if key == "xmlns" { + scope + .get_or_insert_with(|| (**parent).clone()) + .insert(String::new(), attr.value.as_ref().to_string()); + } else if let Some(prefix) = key.strip_prefix("xmlns:") { + scope + .get_or_insert_with(|| (**parent).clone()) + .insert(prefix.to_string(), attr.value.as_ref().to_string()); + } + } + scope.map(Arc::new).unwrap_or_else(|| Arc::clone(parent)) +} + +/// Raw unescaped value of the type attribute in the XML Schema Instance +/// namespace. The prefix is resolved from the element's namespace scope. +fn xsi_type_value(e: &BytesStart, scope: &HashMap) -> Result>> { + for attr in e.attributes().flatten() { + let raw_key = attr.key.as_ref(); + if raw_key == "xmlns" || raw_key.starts_with("xmlns:") { + continue; + } + if let Some(prefix) = raw_key.strip_suffix(":type") { + if scope + .get(prefix) + .is_some_and(|uri| uri == "http://www.w3.org/2001/XMLSchema-instance") + { + let unescaped = quick_xml::escape::unescape(attr.value.as_ref())?; + return Ok(Some(unescaped.into_owned().into_bytes())); + } + } + } + Ok(None) +} + +/// Resolve the concrete schema an element should be parsed with (issue #53). +/// +/// When the declared type has `xsi:type` derivations registered (or is +/// abstract), the wire attribute selects one; unknown types on an abstract +/// base error loudly instead of silently dropping concrete fields. Types +/// without dispatch information keep today's behavior of parsing as +/// declared, so a plain content attribute named `type` is never mistaken +/// for dispatch on a non-abstract type. +fn resolve_record_schema( + declared: &Arc, + e: &BytesStart, + scope: &NamespaceScope, +) -> Result> { + if !declared.is_abstract && !declared.has_variants() { + return Ok(Arc::clone(declared)); + } + let raw = match xsi_type_value(e, scope)? { + Some(raw) => raw, + None if !declared.is_abstract => return Ok(Arc::clone(declared)), + None => { + return Err(PolyXmlError::SchemaError(format!( + "abstract type '{}' requires xsi:type naming a concrete derivation", + declared.name + ))) + } + }; + let (prefix, local): (&[u8], &[u8]) = match raw.iter().position(|&b| b == b':') { + Some(i) => (&raw[..i], &raw[i + 1..]), + None => (b"", &raw[..]), + }; + let prefix = std::str::from_utf8(prefix).unwrap_or(""); + let namespace = scope.get(prefix).map(String::as_str); + if prefix.is_empty() || namespace.is_some() { + if let Some(variant) = declared.find_variant_qname(namespace.unwrap_or(""), local) { + if !variant.is_abstract { + return Ok(variant); + } + } + } + if declared.is_abstract { + let value = String::from_utf8_lossy(&raw); + return Err(PolyXmlError::SchemaError(if declared.has_variants() { + let known: Vec = declared + .variants() + .iter() + .map(|v| String::from_utf8_lossy(&v.xml_name).into_owned()) + .collect(); + format!( + "xsi:type=\"{}\" does not match any known derivation of '{}' (known: {})", + value, + declared.name, + known.join(", ") + ) + } else { + format!( + "xsi:type=\"{}\" targets abstract type '{}', which has no registered \ + derivations; deserialize the concrete type directly (escape hatch)", + value, declared.name + ) + })); + } + Ok(Arc::clone(declared)) +} + fn append_general_ref( e: &quick_xml::events::BytesRef, active_scalar: bool, @@ -146,10 +253,18 @@ impl XmlDeserializer { loop { match reader.read_event_into(&mut buf) { Ok(Event::Start(ref e)) => { - return Self::parse_sub_tree(&mut reader, root_schema, e, max_depth); + return Self::parse_sub_tree( + &mut reader, + root_schema, + e, + max_depth, + &Arc::new(HashMap::new()), + ); } Ok(Event::Empty(ref e)) => { - let mut frame = StackFrame::new(Arc::clone(&root_schema)); + let scope = namespace_scope(&Arc::new(HashMap::new()), e); + let schema = resolve_record_schema(&root_schema, e, &scope)?; + let mut frame = StackFrame::new(schema); Self::parse_attributes(e, &mut frame)?; return frame.finish(); } @@ -175,9 +290,13 @@ impl XmlDeserializer { root_schema: Arc, root_start: &BytesStart, max_depth: usize, + inherited_scope: &NamespaceScope, ) -> Result { let mut stack: Vec = Vec::with_capacity(16); - let mut root_frame = StackFrame::new(Arc::clone(&root_schema)); + let mut namespace_stack = vec![namespace_scope(inherited_scope, root_start)]; + let root_schema = + resolve_record_schema(&root_schema, root_start, namespace_stack.last().unwrap())?; + let mut root_frame = StackFrame::new(root_schema); Self::parse_attributes(root_start, &mut root_frame)?; stack.push(root_frame); @@ -189,6 +308,7 @@ impl XmlDeserializer { loop { match reader.read_event_into(&mut buf) { Ok(Event::Start(ref e)) => { + namespace_stack.push(namespace_scope(namespace_stack.last().unwrap(), e)); if unknown_depth > 0 { unknown_depth += 1; continue; @@ -235,14 +355,24 @@ impl XmlDeserializer { } } ValueType::Nested(sub_schema) => { - let mut frame = StackFrame::new(Arc::clone(sub_schema)); + let frame_schema = resolve_record_schema( + sub_schema, + e, + namespace_stack.last().unwrap(), + )?; + let mut frame = StackFrame::new(frame_schema); Self::parse_attributes(e, &mut frame)?; stack.push(frame); } _ => {} }, ValueType::Nested(sub_schema) => { - let mut frame = StackFrame::new(Arc::clone(sub_schema)); + let frame_schema = resolve_record_schema( + sub_schema, + e, + namespace_stack.last().unwrap(), + )?; + let mut frame = StackFrame::new(frame_schema); Self::parse_attributes(e, &mut frame)?; stack.push(frame); } @@ -256,6 +386,7 @@ impl XmlDeserializer { continue; } let local_name = e.local_name(); + let scope = namespace_scope(namespace_stack.last().unwrap(), e); let is_nil = is_nil_element(e); let current_schema = Arc::clone(&stack.last().unwrap().schema); @@ -283,7 +414,9 @@ impl XmlDeserializer { stack.last_mut().unwrap().push_list_item(field_idx, val); } ValueType::Nested(sub_schema) => { - let mut frame = StackFrame::new(Arc::clone(sub_schema)); + let frame_schema = + resolve_record_schema(sub_schema, e, &scope)?; + let mut frame = StackFrame::new(frame_schema); Self::parse_attributes(e, &mut frame)?; let instance = frame.finish()?; stack @@ -294,7 +427,8 @@ impl XmlDeserializer { _ => {} }, ValueType::Nested(sub_schema) => { - let mut frame = StackFrame::new(Arc::clone(sub_schema)); + let frame_schema = resolve_record_schema(sub_schema, e, &scope)?; + let mut frame = StackFrame::new(frame_schema); Self::parse_attributes(e, &mut frame)?; let instance = frame.finish()?; stack.last_mut().unwrap().values[field_idx] = Some(instance); @@ -347,6 +481,7 @@ impl XmlDeserializer { append_general_ref(e, active_scalar_field.is_some(), &mut text_buf, frame_tb)?; } Ok(Event::End(ref e)) => { + namespace_stack.pop(); if unknown_depth > 0 { unknown_depth -= 1; continue; @@ -432,6 +567,7 @@ pub struct XmlItemStream { target_tag: Vec, max_depth: usize, buf: Vec, + namespace_stack: Vec, } impl XmlItemStream { @@ -443,6 +579,7 @@ impl XmlItemStream { target_tag: target_tag.to_vec(), max_depth: DEFAULT_MAX_DEPTH, buf: Vec::new(), + namespace_stack: Vec::new(), } } @@ -461,26 +598,48 @@ impl XmlItemStream { if local.as_ref().as_bytes() == target_local || e.name().as_ref().as_bytes() == self.target_tag.as_slice() { + let inherited = self + .namespace_stack + .last() + .cloned() + .unwrap_or_else(|| Arc::new(HashMap::new())); let item = XmlDeserializer::parse_sub_tree( &mut self.reader, Arc::clone(&self.schema), e, self.max_depth, + &inherited, )?; return Ok(Some(item)); } + let inherited = self + .namespace_stack + .last() + .cloned() + .unwrap_or_else(|| Arc::new(HashMap::new())); + self.namespace_stack.push(namespace_scope(&inherited, e)); } Ok(Event::Empty(ref e)) => { let local = e.local_name(); if local.as_ref().as_bytes() == target_local || e.name().as_ref().as_bytes() == self.target_tag.as_slice() { - let mut frame = StackFrame::new(Arc::clone(&self.schema)); + let inherited = self + .namespace_stack + .last() + .cloned() + .unwrap_or_else(|| Arc::new(HashMap::new())); + let scope = namespace_scope(&inherited, e); + let schema = resolve_record_schema(&self.schema, e, &scope)?; + let mut frame = StackFrame::new(schema); XmlDeserializer::parse_attributes(e, &mut frame)?; return Ok(Some(frame.finish()?)); } } Ok(Event::Eof) => return Ok(None), + Ok(Event::End(_)) => { + self.namespace_stack.pop(); + } Err(err) => { return Err(PolyXmlError::XmlSyntaxError { position: self.reader.buffer_position(), diff --git a/crates/polyxml-core/src/schema.rs b/crates/polyxml-core/src/schema.rs index cebf0fa5..da75b055 100644 --- a/crates/polyxml-core/src/schema.rs +++ b/crates/polyxml-core/src/schema.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::sync::Arc; +use std::sync::{Arc, RwLock}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum FieldKind { @@ -80,6 +80,11 @@ pub struct ModelSchema { pub element_map: HashMap, usize>, pub attribute_map: HashMap, usize>, pub text_field: Option, + /// Declared `abstract="true"` in the source schema (issue #53). + pub is_abstract: bool, + /// Concrete derivations eligible for `xsi:type` dispatch. Python may + /// refresh this registry when subclasses are defined after first use. + variants: Arc>>>, } impl ModelSchema { @@ -87,6 +92,63 @@ impl ModelSchema { ModelSchemaBuilder::new(name) } + /// Register the concrete derivations eligible for `xsi:type` dispatch. + /// Replaces the registry when subclasses are discovered after first use. + pub fn set_variants(&self, variants: Vec>) { + *self.variants.write().unwrap_or_else(|p| p.into_inner()) = variants; + } + + /// Registered derivations for `xsi:type` dispatch (empty when none). + pub fn variants(&self) -> Vec> { + self.variants + .read() + .unwrap_or_else(|p| p.into_inner()) + .clone() + } + + /// Whether any `xsi:type` derivations are registered for this type. + pub fn has_variants(&self) -> bool { + !self + .variants + .read() + .unwrap_or_else(|p| p.into_inner()) + .is_empty() + } + + /// Look up a derivation by the QName local part of an `xsi:type` value. + pub fn find_variant(&self, local: &[u8]) -> Option> { + self.variants + .read() + .unwrap_or_else(|p| p.into_inner()) + .iter() + .find(|v| v.xml_name.as_slice() == local) + .cloned() + } + + /// Look up a derivation by its complete QName. + pub fn find_variant_qname(&self, namespace: &str, local: &[u8]) -> Option> { + self.variants + .read() + .unwrap_or_else(|p| p.into_inner()) + .iter() + .find(|v| { + v.xml_name.as_slice() == local && v.namespace.as_deref().unwrap_or("") == namespace + }) + .cloned() + } + + /// Whether `candidate` is a registered derivation of `self`. + pub fn matches_variant(&self, candidate: &ModelSchema) -> bool { + self.variants + .read() + .unwrap_or_else(|p| p.into_inner()) + .iter() + .any(|v| { + std::ptr::eq(v.as_ref(), candidate) + || (v.xml_name == candidate.xml_name && v.namespace == candidate.namespace) + }) + } + /// Construct a runtime `ModelSchema` from a compiled `SchemaIR`. pub fn from_ir( ir: &crate::ir::SchemaIR, @@ -207,6 +269,56 @@ impl ModelSchema { } } + fn build_field( + f: &crate::ir::FieldDef, + ir: &crate::ir::SchemaIR, + visited: &mut HashSet, + ) -> FieldSchema { + let kind = match f.kind { + crate::ir::FieldKind::Attribute => FieldKind::Attribute, + crate::ir::FieldKind::Element => FieldKind::Element, + crate::ir::FieldKind::Text => FieldKind::Text, + _ => FieldKind::Element, + }; + + let mut val_type = build_type(&f.type_ref, ir, visited); + if f.cardinality.is_list() && !matches!(val_type, ValueType::List(_)) { + val_type = ValueType::List(Box::new(val_type)); + } + + let mut field_schema = FieldSchema::new(&f.name, f.xml_name.as_bytes(), kind, val_type); + if let Some(ref ns) = f.namespace { + field_schema = field_schema.namespace(ns); + } + if f.cardinality.min_occurs > 0 && !f.cardinality.is_optional() { + field_schema = field_schema.required(); + } + field_schema + } + + /// Whether `d` transitively extends `base` via `xs:extension` (issue #53). + fn derives_from( + ir: &crate::ir::SchemaIR, + d: &crate::ir::StructDef, + base: &crate::ir::QName, + ) -> bool { + let mut seen: HashSet = HashSet::new(); + let mut cur = d.base_type.as_ref(); + while let Some(q) = cur { + if q == base { + return true; + } + if !seen.insert(q.clone()) { + break; + } + cur = match ir.types.get(q) { + Some(TypeDef::Struct(b)) => b.base_type.as_ref(), + _ => None, + }; + } + false + } + fn build_struct( s: &crate::ir::StructDef, ir: &crate::ir::SchemaIR, @@ -216,33 +328,65 @@ impl ModelSchema { if let Some(ref ns) = s.qname.namespace { builder = builder.namespace(ns); } - + builder = builder.is_abstract(s.is_abstract); + + // Flatten the xs:extension content model: base-chain fields + // precede the struct's own fields (mirrors the Java codegen). + let mut seen_bases: HashSet = HashSet::new(); + seen_bases.insert(s.qname.clone()); + let mut chain: Vec<&crate::ir::StructDef> = Vec::new(); + let mut cur = s.base_type.as_ref(); + while let Some(base_q) = cur { + if !seen_bases.insert(base_q.clone()) { + break; + } + match ir.types.get(base_q) { + Some(TypeDef::Struct(base_s)) => { + chain.push(base_s); + cur = base_s.base_type.as_ref(); + } + _ => break, + } + } + for base_s in chain.into_iter().rev() { + for f in &base_s.fields { + builder = builder.field(build_field(f, ir, visited)); + } + } for f in &s.fields { - let kind = match f.kind { - crate::ir::FieldKind::Attribute => FieldKind::Attribute, - crate::ir::FieldKind::Element => FieldKind::Element, - crate::ir::FieldKind::Text => FieldKind::Text, - _ => FieldKind::Element, - }; + builder = builder.field(build_field(f, ir, visited)); + } - let mut val_type = build_type(&f.type_ref, ir, visited); - if f.cardinality.is_list() && !matches!(val_type, ValueType::List(_)) { - val_type = ValueType::List(Box::new(val_type)); - } + let schema = builder.build(); - let mut field_schema = - FieldSchema::new(&f.name, f.xml_name.as_bytes(), kind, val_type); - if let Some(ref ns) = f.namespace { - field_schema = field_schema.namespace(ns); + // xsi:type dispatch registry (issue #53): every transitive + // derivation of this type, keyed by QName local part. + let derived: Vec = ir + .types + .iter() + .filter_map(|(q, td)| match td { + TypeDef::Struct(d) if d.qname != s.qname && derives_from(ir, d, &s.qname) => { + Some(q.clone()) + } + _ => None, + }) + .collect(); + let mut variants = Vec::with_capacity(derived.len()); + for dq in derived { + if visited.contains(&dq) { + continue; } - if f.cardinality.min_occurs > 0 && !f.cardinality.is_optional() { - field_schema = field_schema.required(); + visited.insert(dq.clone()); + if let Some(TypeDef::Struct(d)) = ir.types.get(&dq) { + variants.push(build_struct(d, ir, visited)); } - - builder = builder.field(field_schema); + visited.remove(&dq); + } + if !variants.is_empty() { + schema.set_variants(variants); } - builder.build() + schema } let mut visited = HashSet::new(); @@ -284,6 +428,7 @@ pub struct ModelSchemaBuilder { xml_name: Option>, namespace: Option, fields: Vec, + is_abstract: bool, } impl ModelSchemaBuilder { @@ -293,9 +438,16 @@ impl ModelSchemaBuilder { xml_name: None, namespace: None, fields: Vec::new(), + is_abstract: false, } } + /// Mark the type as `abstract="true"` (issue #53). + pub fn is_abstract(mut self, is_abstract: bool) -> Self { + self.is_abstract = is_abstract; + self + } + pub fn xml_name(mut self, xml_name: &[u8]) -> Self { self.xml_name = Some(xml_name.to_vec()); self @@ -353,6 +505,8 @@ impl ModelSchemaBuilder { element_map, attribute_map, text_field, + is_abstract: self.is_abstract, + variants: Arc::new(RwLock::new(Vec::new())), }) } } diff --git a/crates/polyxml-core/src/schema_parser/mod.rs b/crates/polyxml-core/src/schema_parser/mod.rs index d18df338..b93979c1 100644 --- a/crates/polyxml-core/src/schema_parser/mod.rs +++ b/crates/polyxml-core/src/schema_parser/mod.rs @@ -867,6 +867,11 @@ impl XsdParser { base_type = resolve_type_ref(&base, target_ns, prefixes); } } + "pattern" => { + if let Some(val) = get_attr_value(e, "value") { + facets.patterns.push(val); + } + } "enumeration" => { if let Some(val) = get_attr_value(e, "value") { enum_values.push(EnumValue { @@ -950,6 +955,18 @@ impl XsdParser { buf.clear(); } + // XSD same-restriction alternatives form ONE pattern facet. Preserve + // each derivation boundary as a separate entry during inheritance. + if facets.patterns.len() > 1 { + let alternatives = facets + .patterns + .iter() + .map(|p| format!("({p})")) + .collect::>() + .join("|"); + facets.patterns = vec![alternatives]; + } + // Issue #51 item 2: drop duplicate enumeration values (keep the first // occurrence) and disambiguate variant names that collide after // sanitization, so generated enums always compile. diff --git a/crates/polyxml-core/src/serializer.rs b/crates/polyxml-core/src/serializer.rs index 494286c4..6f0a95e8 100644 --- a/crates/polyxml-core/src/serializer.rs +++ b/crates/polyxml-core/src/serializer.rs @@ -9,6 +9,9 @@ use crate::error::{PolyXmlError, Result}; use crate::schema::{FieldKind, ModelSchema, ValueType}; use crate::value::PolyValue; +/// XML Schema Instance namespace, needed to write `xsi:type` selectors. +const XSI_NS: &str = "http://www.w3.org/2001/XMLSchema-instance"; + #[derive(Debug, Clone)] pub struct NamespaceContext { pub uri_to_prefix: HashMap, @@ -103,6 +106,19 @@ impl NamespaceContext { _ => {} } } + + // xsi:type dispatch (issue #53): variant namespaces must be in + // scope, and the XML Schema Instance namespace is required to + // write the xsi:type selector itself. + let variants = schema.variants(); + if !variants.is_empty() { + if !uris.iter().any(|u| u == XSI_NS) { + uris.push(XSI_NS.to_string()); + } + for variant in variants { + Self::collect_namespaces(&variant, uris); + } + } } pub fn qualify_element<'a>(&'a self, local_name: &'a str, ns: Option<&str>) -> Cow<'a, str> { @@ -221,6 +237,30 @@ impl XmlSerializer { } } + // xsi:type dispatch (issue #53): when the value is a Record built + // from a registered derivation of the declared schema, write the + // concrete variant's fields and re-emit the xsi:type selector. + let mut xsi_type: Option = None; + let schema = if let PolyValue::Record { schema: rec, .. } = value { + if schema.matches_variant(rec) { + xsi_type = match ns_ctx { + Some(ctx) => { + let name = std::str::from_utf8(&rec.xml_name)?; + Some( + ctx.qualify_element(name, rec.namespace.as_deref()) + .into_owned(), + ) + } + None => Some(String::from_utf8_lossy(&rec.xml_name).into_owned()), + }; + rec.as_ref() + } else { + schema + } + } else { + schema + }; + let get_field = |idx: usize, name: &str| -> Option<&PolyValue> { match value { PolyValue::Record { values, .. } => values.get(idx).and_then(|v| v.as_ref()), @@ -272,6 +312,27 @@ impl XmlSerializer { } } + // Re-emit the xsi:type selector after content attributes (issue #53). + if let Some(ref xsi_val) = xsi_type { + let attr_key = match ns_ctx { + Some(ctx) => { + let key = ctx.qualify_attribute("type", Some(XSI_NS)); + if key.as_ref() == "type" { + return Err(PolyXmlError::SerializationError( + "xsi:type requires a namespace prefix".into(), + )); + } + key.into_owned() + } + None => { + return Err(PolyXmlError::SerializationError( + "xsi:type requires namespaces to be enabled".into(), + )) + } + }; + elem.push_attribute((attr_key.as_str(), xsi_val.as_str())); + } + writer .write_event(Event::Start(elem)) .map_err(|e| PolyXmlError::SerializationError(e.to_string()))?; diff --git a/crates/polyxml-core/src/transcoder.rs b/crates/polyxml-core/src/transcoder.rs index 9e60e069..211dead7 100644 --- a/crates/polyxml-core/src/transcoder.rs +++ b/crates/polyxml-core/src/transcoder.rs @@ -92,7 +92,10 @@ fn parse_dynamic_scalar(s: &str) -> Value { /// Schema-less dynamic XML -> JSON transcoder. pub fn xml_to_json_dynamic(xml: &[u8], indent: Option) -> Result> { let mut reader = Reader::from_reader(Cursor::new(xml)); - reader.config_mut().trim_text(true); + // Do NOT enable `trim_text`: quick-xml trims *each* Text event, and text is + // split at entity references, so `x & y` would become `x&y` — losing + // interior spaces around the reference. `End` trims the assembled buffer + // instead, which is the only correct place to normalize whitespace. let mut buf = Vec::new(); let mut stack: Vec = Vec::new(); @@ -110,7 +113,13 @@ pub fn xml_to_json_dynamic(xml: &[u8], indent: Option) -> Result> })?; let key = attr.key.local_name().as_ref().to_string(); let raw_val = attr.value.as_ref(); - attrs.insert(format!("@{}", key), parse_dynamic_scalar(raw_val)); + let unescaped = quick_xml::escape::unescape(raw_val).map_err(|err| { + PolyXmlError::XmlSyntaxError { + position: reader.buffer_position(), + source: quick_xml::Error::Escape(err), + } + })?; + attrs.insert(format!("@{}", key), parse_dynamic_scalar(&unescaped)); } stack.push(DynamicFrame { tag_name: tag, @@ -135,6 +144,19 @@ pub fn xml_to_json_dynamic(xml: &[u8], indent: Option) -> Result> frame.text_buf.push_str(e.as_ref()); } } + Ok(Event::GeneralRef(ref e)) => { + if let Some(frame) = stack.last_mut() { + if e.is_char_ref() { + if let Some(ch) = e.resolve_char_ref().map_err(PolyXmlError::XmlError)? { + frame.text_buf.push(ch); + } + } else if let Some(val) = quick_xml::escape::resolve_xml_entity(e.as_ref()) { + frame.text_buf.push_str(val); + } else { + frame.text_buf.push_str(e.as_ref()); + } + } + } Ok(Event::Empty(ref e)) => { let tag = e.local_name().as_ref().to_string(); let mut attrs = Map::new(); @@ -145,7 +167,13 @@ pub fn xml_to_json_dynamic(xml: &[u8], indent: Option) -> Result> })?; let key = attr.key.local_name().as_ref().to_string(); let raw_val = attr.value.as_ref(); - attrs.insert(format!("@{}", key), parse_dynamic_scalar(raw_val)); + let unescaped = quick_xml::escape::unescape(raw_val).map_err(|err| { + PolyXmlError::XmlSyntaxError { + position: reader.buffer_position(), + source: quick_xml::Error::Escape(err), + } + })?; + attrs.insert(format!("@{}", key), parse_dynamic_scalar(&unescaped)); } let val = if attrs.is_empty() { Value::Null diff --git a/crates/polyxml-core/tests/test_issue51_audit.rs b/crates/polyxml-core/tests/test_issue51_audit.rs index 3a910084..52d27a04 100644 --- a/crates/polyxml-core/tests/test_issue51_audit.rs +++ b/crates/polyxml-core/tests/test_issue51_audit.rs @@ -394,8 +394,8 @@ fn issue51_item6_quick_xml_serde_skips_unknown_elements() { } // --------------------------------------------------------------------------- -// Item 7: xsi:type polymorphic dispatch — intentionally untested here -// (scoped as a separate enhancement issue; unsupported today). +// Item 7: xsi:type polymorphic dispatch — implemented as issue #53; +// see `tests/test_issue53_xsi_type.rs`. // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- @@ -451,9 +451,9 @@ fn issue51_item8_derived_simple_type_inherits_base_patterns() { py.contains(r#"AfterValidator(_polyxml_patterns(r"[A-Z]{6}[A-Z0-9]{3}", r"[A-Z]{6}"))"#), "both patterns must be AND-combined via AfterValidator:\n{py}" ); - // Single-pattern types keep the plain Field(pattern=...) kwarg. + // Single-pattern types keep Field(pattern=...), with full-value anchors. assert!( - py.contains(r#"Field(pattern=r"[A-Z]{6}")"#), + py.contains(r#"Field(pattern=r"\A(?:[A-Z]{6})\z")"#), "single-pattern Field kwarg missing:\n{py}" ); } diff --git a/crates/polyxml-core/tests/test_issue53_xsi_type.rs b/crates/polyxml-core/tests/test_issue53_xsi_type.rs new file mode 100644 index 00000000..76cc5ef6 --- /dev/null +++ b/crates/polyxml-core/tests/test_issue53_xsi_type.rs @@ -0,0 +1,352 @@ +//! Issue #53: `xsi:type` polymorphic dispatch for abstract complexTypes. +//! +//! Locks in the runtime registry strategy: `ModelSchema` carries the +//! concrete derivations of a type (keyed by QName local part), the parser +//! selects one from the wire `xsi:type` attribute, and the serializer +//! re-emits the selector so round trips preserve the concrete type. + +use std::io::Cursor; +use std::sync::Arc; + +use polyxml::schema::{ModelSchema, ValueType}; +use polyxml::schema_parser::XsdParser; +use polyxml::value::PolyValue; +use polyxml::XmlItemStream; + +const VEHICLE_XSD: &str = r#" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"#; + +fn schema_for(root: Option<&str>) -> Arc { + let ir = XsdParser::new().parse_str(VEHICLE_XSD).expect("parse XSD"); + ModelSchema::from_ir(&ir, root).expect("build runtime schema") +} + +/// Declared schema of the `Root` document's `vehicle` field. +fn declared_vehicle(root: &Arc) -> Arc { + let idx = root + .fields + .iter() + .position(|f| f.name == "vehicle") + .expect("vehicle field"); + match &root.fields[idx].val_type { + ValueType::Nested(s) => Arc::clone(s), + other => panic!("expected nested vehicle schema, got {other:?}"), + } +} + +fn rec_field<'a>(value: &'a PolyValue, field_name: &str) -> Option<&'a PolyValue> { + match value { + PolyValue::Record { schema, values } => { + let idx = schema.fields.iter().position(|f| f.name == field_name)?; + values.get(idx).and_then(|v| v.as_ref()) + } + _ => None, + } +} + +fn rec_schema(value: &PolyValue) -> &ModelSchema { + match value { + PolyValue::Record { schema, .. } => schema, + other => panic!("expected Record, got {other:?}"), + } +} + +fn assert_str(value: Option<&PolyValue>, expected: &str, ctx: &str) { + match value { + Some(PolyValue::String(s)) => assert_eq!(s, expected, "wrong value for {ctx}"), + other => panic!("expected string {expected:?} for {ctx}, got {other:?}"), + } +} + +fn schema_local(schema: &ModelSchema) -> String { + String::from_utf8_lossy(&schema.xml_name).into_owned() +} + +#[test] +fn issue53_from_ir_flattens_base_fields_and_registers_variants() { + let root = schema_for(Some("Root")); + let declared = declared_vehicle(&root); + + assert!(declared.is_abstract, "Vehicle must be marked abstract"); + assert!( + declared.has_variants(), + "Vehicle must register its derivations" + ); + let keys: Vec = declared + .variants() + .iter() + .map(|v| schema_local(v)) + .collect(); + assert_eq!(keys, ["Car", "Truck"], "variant keys by QName local part"); + + // The declared (abstract) type keeps only its own content model. + let base_fields: Vec<&str> = declared.fields.iter().map(|f| f.name.as_str()).collect(); + assert_eq!(base_fields, ["id"], "abstract base keeps only base fields"); + + // Derived runtime schemas flatten the extension chain (base first). + let car = declared.find_variant(b"Car").expect("Car variant"); + let car_fields: Vec<&str> = car.fields.iter().map(|f| f.name.as_str()).collect(); + assert_eq!( + car_fields, + ["id", "doors"], + "Car must inherit id before its own doors" + ); + assert!(!car.is_abstract, "concrete derivation is not abstract"); + + let truck = declared.find_variant(b"Truck").expect("Truck variant"); + let truck_fields: Vec<&str> = truck.fields.iter().map(|f| f.name.as_str()).collect(); + assert_eq!(truck_fields, ["id", "payload"]); +} + +#[test] +fn issue53_nested_xsi_type_dispatch_and_round_trip() { + let root = schema_for(Some("Root")); + let xml = br#" + V14 + S1ore + + "#; + + let value = polyxml::deserialize(xml, Arc::clone(&root)).expect("dispatch parse"); + + let vehicle = rec_field(&value, "vehicle").expect("vehicle value"); + assert_eq!(schema_local(rec_schema(vehicle)), "Car"); + assert_str(rec_field(vehicle, "id"), "V1", "vehicle.id (inherited)"); + match rec_field(vehicle, "doors") { + Some(PolyValue::Int(i)) => assert_eq!(*i, 4, "vehicle.doors"), + other => panic!("expected int doors, got {other:?}"), + } + + let spares = rec_field(&value, "spare").expect("spare list"); + let items = match spares { + PolyValue::List(items) => items, + other => panic!("expected spare list, got {other:?}"), + }; + assert_eq!(items.len(), 2); + assert_eq!(schema_local(rec_schema(&items[0])), "Truck"); + assert_str( + rec_field(&items[0], "payload"), + "ore", + "spare.payload (inherited nesting)", + ); + assert_eq!(schema_local(rec_schema(&items[1])), "Car"); + + // Round trip: xsi:type selectors are re-emitted for every dispatch. + let out = polyxml::serialize_with_options("Root", &value, &root, None, Some(true), None) + .expect("serialize"); + let out_str = String::from_utf8_lossy(&out); + assert!(out_str.contains("xsi:type="), "missing selector: {out_str}"); + assert!(out_str.contains("Car"), "missing Car selector: {out_str}"); + assert!( + out_str.contains("Truck"), + "missing Truck selector: {out_str}" + ); + assert!( + out_str.contains("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""), + "missing xsi declaration: {out_str}" + ); + + let reparsed = polyxml::deserialize(&out, Arc::clone(&root)).expect("re-parse"); + assert_eq!( + reparsed, value, + "round trip must preserve dispatched records" + ); +} + +#[test] +fn issue53_root_xsi_type_dispatch_including_empty_element() { + let root = schema_for(Some("Vehicle")); + assert!(root.is_abstract); + assert!(root.has_variants()); + + let xml = br#"T9ore"#; + let value = polyxml::deserialize(xml, Arc::clone(&root)).expect("root dispatch"); + assert_eq!(schema_local(rec_schema(&value)), "Truck"); + assert_str(rec_field(&value, "id"), "T9", "root id"); + assert_str(rec_field(&value, "payload"), "ore", "root payload"); + + let out = polyxml::serialize_with_options("Vehicle", &value, &root, None, Some(true), None) + .expect("serialize root"); + let out_str = String::from_utf8_lossy(&out); + assert!( + out_str.starts_with(""#; + let value = polyxml::deserialize(empty, root).expect("empty dispatch"); + assert_eq!(schema_local(rec_schema(&value)), "Car"); +} + +#[test] +fn issue53_unknown_xsi_type_on_abstract_type_errors() { + let root = schema_for(Some("Root")); + let xml = br#"X"#; + let err = polyxml::deserialize(xml, root).expect_err("unknown derivation must fail"); + let msg = err.to_string(); + assert!( + msg.contains("xsi:type=\"t:Plane\"") && msg.contains("known derivation"), + "error must name the offending selector: {msg}" + ); + assert!( + msg.contains("'Vehicle'") && msg.contains("Car, Truck"), + "error must name the base and known derivations: {msg}" + ); +} + +#[test] +fn issue53_abstract_type_without_derivations_errors() { + let xsd = r#" + + + + + + + "#; + let ir = XsdParser::new().parse_str(xsd).expect("parse XSD"); + let root = ModelSchema::from_ir(&ir, Some("Solo")).expect("build runtime schema"); + assert!(root.is_abstract); + assert!(!root.has_variants()); + + let xml = br#"X"#; + let err = polyxml::deserialize(xml, root).expect_err("must fail loudly"); + let msg = err.to_string(); + assert!( + msg.contains("no registered derivations") && msg.contains("escape hatch"), + "error must explain the escape hatch: {msg}" + ); +} + +#[test] +fn issue53_plain_type_attribute_is_not_mistaken_for_dispatch() { + // A content attribute literally named "type" on a non-abstract type + // must never be treated as an xsi:type selector. + let xsd = r#" + + + + + + + + "#; + let ir = XsdParser::new().parse_str(xsd).expect("parse XSD"); + let root = ModelSchema::from_ir(&ir, Some("Tagged")).expect("build runtime schema"); + + let xml = br#""#; + let value = polyxml::deserialize(xml, Arc::clone(&root)).expect("parse as declared"); + assert_str(rec_field(&value, "type"), "gear", "content attribute"); + assert_str(rec_field(&value, "label"), "L", "label"); + + let out = polyxml::serialize_with_options("Tagged", &value, &root, None, None, None) + .expect("serialize"); + let out_str = String::from_utf8_lossy(&out); + assert!( + !out_str.contains("xsi:type="), + "no selector may be invented for plain types: {out_str}" + ); +} + +#[test] +fn issue53_iterparse_dispatches_each_streamed_item() { + let root = schema_for(Some("Root")); + let declared = declared_vehicle(&root); + let xml = br#" + A2 + Bp + + "#; + + let mut stream = XmlItemStream::new(Cursor::new(&xml[..]), declared, b"vehicle"); + let first = stream.next_item().expect("first item").expect("item 1"); + assert_eq!(schema_local(rec_schema(&first)), "Car"); + match rec_field(&first, "doors") { + Some(PolyValue::Int(i)) => assert_eq!(*i, 2, "streamed doors"), + other => panic!("expected int doors, got {other:?}"), + } + + let second = stream.next_item().expect("second item").expect("item 2"); + assert_eq!(schema_local(rec_schema(&second)), "Truck"); + + let third = stream.next_item().expect("third item").expect("item 3"); + assert_eq!(schema_local(rec_schema(&third)), "Car"); + + assert!(stream.next_item().expect("eof").is_none()); +} + +#[test] +fn abstract_type_requires_concrete_selector() { + let root = schema_for(Some("Vehicle")); + for xml in [ + b"".as_slice(), + b"X".as_slice(), + ] { + let err = polyxml::deserialize(xml, Arc::clone(&root)).unwrap_err(); + assert!(err.to_string().contains("requires xsi:type"), "{err}"); + } +} + +#[test] +fn xsi_type_resolves_namespace_inherited_from_parent() { + let root = schema_for(Some("Root")); + let valid = br#"X2"#; + assert!(polyxml::deserialize(valid, Arc::clone(&root)).is_ok()); + let wrong = br#"X2"#; + assert!(polyxml::deserialize(wrong, Arc::clone(&root)).is_err()); + let wrong_attribute = br#"X2"#; + assert!(polyxml::deserialize(wrong_attribute, root).is_err()); +} + +#[test] +fn variant_serialization_requires_namespaces() { + let root = schema_for(Some("Vehicle")); + let xml = br#"X2"#; + let value = polyxml::deserialize(xml, Arc::clone(&root)).unwrap(); + let err = polyxml::serialize_with_options("Vehicle", &value, &root, None, Some(false), None) + .unwrap_err(); + assert!(err.to_string().contains("requires namespaces"), "{err}"); +} diff --git a/crates/polyxml-core/tests/test_java_codegen.rs b/crates/polyxml-core/tests/test_java_codegen.rs index 992d2477..fbeebcf8 100644 --- a/crates/polyxml-core/tests/test_java_codegen.rs +++ b/crates/polyxml-core/tests/test_java_codegen.rs @@ -883,3 +883,163 @@ public class Main {{ public static void main(String[] args) throws Exception {{ ); } } + +// --------------------------------------------------------------------------- +// xsd:extension in plain record mode (the default): records cannot `extends`, +// so inherited fields must be inlined as components. Previously only the +// builder/direct-codec modes ran the base walk, silently dropping inherited +// fields from the default record output. +// --------------------------------------------------------------------------- + +/// Slice one record's component header from `record Name(` through `) {`. +fn record_header<'a>(code: &'a str, name: &str) -> &'a str { + let marker = format!("record {name}("); + let start = code + .find(&marker) + .unwrap_or_else(|| panic!("expected {marker:?} in:\n{code}")); + let rest = &code[start..]; + let end = rest + .find(") {") + .unwrap_or_else(|| panic!("unterminated record {name}:\n{code}")); + &rest[..end + 3] +} + +fn extension_schema() -> &'static str { + r#" + + + + + + + + + + + + + + + + + + + + + + + + + + + + "# +} + +#[test] +fn test_java_plain_record_inlines_base_fields() { + let ir = polyxml::schema_parser::XsdParser::new() + .parse_str(extension_schema()) + .expect("parse extension schema"); + // Defaults: use_records=true with no builder and no direct codec — the + // mode that used to bypass the base walk entirely. + let generator = JavaCodegen::new(JavaOptions { + package_name: String::new(), + ..Default::default() + }); + let models = generator.generate_module(&ir, "Models"); + + let message = record_header(&models, "Message"); + assert!( + message.contains("int id"), + "record Message dropped the inherited id attribute:\n{message}" + ); + assert!( + message.contains("String text"), + "own text missing:\n{message}" + ); + assert!(message.contains("count"), "own count missing:\n{message}"); + let id_at = message.find("int id").expect("id"); + let text_at = message.find("String text").expect("text"); + assert!( + id_at < text_at, + "base components must precede derived ones:\n{message}" + ); + + // simpleContent chain: `unit` inherited, `value` present exactly once — + // a suffixed duplicate would still match the " value" prefix below. + let derived = record_header(&models, "PreciseMeasurement"); + assert!( + derived.contains("unit"), + "record PreciseMeasurement dropped the inherited unit attribute:\n{derived}" + ); + assert!( + derived.contains("precision"), + "own precision missing:\n{derived}" + ); + assert_eq!( + derived.matches(" value").count(), + 1, + "`value` must be shadowed by the derived declaration, not duplicated:\n{derived}" + ); +} + +#[test] +fn test_java_plain_record_extension_javac_execute() { + let ir = polyxml::schema_parser::XsdParser::new() + .parse_str(extension_schema()) + .expect("parse extension schema"); + let generator = JavaCodegen::new(JavaOptions { + package_name: String::new(), + ..Default::default() + }); + let dir = tempdir().unwrap(); + fs::write( + dir.path().join("Models.java"), + generator.generate_module(&ir, "Models"), + ) + .unwrap(); + fs::write( + dir.path().join("Main.java"), + r#"import java.util.Optional; +public class Main { + public static void main(String[] args) { + var m = new Models.Message(5, "hello", Optional.of(3)); + if (m.id() != 5) throw new AssertionError("inherited id attribute lost"); + if (!m.text().equals("hello")) throw new AssertionError("own text lost"); + if (m.count().orElse(-1) != 3) throw new AssertionError("own count lost"); + var p = new Models.PreciseMeasurement( + Optional.of("km"), + new Models.Measurement(new java.math.BigDecimal("2.5"), Optional.empty()), + Optional.of(3)); + if (!p.unit().orElse("").equals("km")) throw new AssertionError("inherited unit lost"); + if (p.precision().orElse(-1) != 3) throw new AssertionError("own precision lost"); + } +}"#, + ) + .unwrap(); + if Command::new("javac").arg("-version").output().is_err() { + return; + } + let result = Command::new("javac") + .current_dir(dir.path()) + .args(["Models.java", "Main.java"]) + .output() + .unwrap(); + assert!( + result.status.success(), + "{}", + String::from_utf8_lossy(&result.stderr) + ); + let result = Command::new("java") + .current_dir(dir.path()) + .args(["-cp", ".", "Main"]) + .output() + .unwrap(); + assert!( + result.status.success(), + "{}", + String::from_utf8_lossy(&result.stderr) + ); +} diff --git a/crates/polyxml-core/tests/test_pattern_codegen.rs b/crates/polyxml-core/tests/test_pattern_codegen.rs new file mode 100644 index 00000000..e8f1911f --- /dev/null +++ b/crates/polyxml-core/tests/test_pattern_codegen.rs @@ -0,0 +1,364 @@ +//! Issue #54: execute generated validators, not just regex source assertions. +use polyxml::codegen::*; +use polyxml::ir::{QName, RestrictionFacets, SchemaIR, TypeDef}; +use polyxml::schema_parser::XsdParser; +use std::{fs, path::Path, process::Command}; +use tempfile::tempdir; + +const SCHEMA: &str = r#" + + letters + + + + + + + + + + + + + + + + +"#; + +fn schema() -> SchemaIR { + let dir = tempdir().unwrap(); + let path = dir.path().join("pattern.xsd"); + fs::write(&path, SCHEMA).unwrap(); + XsdParser::new().parse_file(&path).unwrap() +} +fn patterns<'a>(ir: &'a SchemaIR, name: &str) -> &'a [String] { + match &ir.types[&QName::new(None::, name)] { + TypeDef::Simple(s) => &s.facets.patterns, + _ => panic!("expected simple type"), + } +} +fn run(cmd: &mut Command) { + let output = cmd.output().unwrap(); + assert!( + output.status.success(), + "{cmd:?}\n{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); +} +fn available(program: &str) -> bool { + Command::new(program).arg("--version").output().is_ok() +} + +#[test] +fn pattern_restrictions_preserve_or_groups_and_inherited_and_steps() { + let ir = schema(); + assert_eq!(patterns(&ir, "ZBase"), ["([A-Z]+)|([0-9]+)"]); + assert_eq!( + patterns(&ir, "Derived"), + ["([A-Z]{3})|([0-9]{3})", "([A-Z]+)|([0-9]+)"] + ); + assert_eq!(patterns(&ir, "Mixed").len(), 3); + assert_eq!(patterns(&ir, "Single"), ["[A-Z]+"]); + // Facets (not the whole IR, whose QName map keys serde_json rejects) must + // survive serialization with their grouping intact. + let facets = match &ir.types[&QName::new(None::, "Mixed")] { + TypeDef::Simple(s) => &s.facets, + _ => panic!("expected simple type"), + }; + let serialized = serde_json::to_string(facets).unwrap(); + let restored: RestrictionFacets = serde_json::from_str(&serialized).unwrap(); + assert_eq!(&restored.patterns, &facets.patterns); +} + +#[test] +fn generated_go_pattern_validation_and_xml_roundtrip() { + if !available("go") { + return; + } + let dir = tempdir().unwrap(); + fs::write(dir.path().join("go.mod"), "module patterns\n\ngo 1.22\n").unwrap(); + fs::write( + dir.path().join("models.go"), + GoCodegen::new(GoOptions::default()).generate_module(&schema()), + ) + .unwrap(); + fs::write(dir.path().join("models_test.go"), r#"package models +import("testing"; "encoding/xml") +func TestPatterns(t *testing.T) { + for _, value := range []string{"ZZZ", "999"} { + if err := Mixed(value).Validate(); err != nil { t.Fatal(value, err) } + var doc Document + if err := xml.Unmarshal([]byte(""+value+""), &doc); err != nil { t.Fatal(err) } + if err := doc.Validate(); err != nil { t.Fatal(err) } + if _, err := xml.Marshal(doc); err != nil { t.Fatal(err) } + } + for _, value := range []string{"ABC", "123", "Z", "9", "abc", "xZZZy", ""} { + if Mixed(value).Validate() == nil { t.Fatal("accepted", value) } + var doc Document + if xml.Unmarshal([]byte(""+value+""), &doc) == nil { t.Fatal("read accepted", value) } + if _, err := xml.Marshal(Document{Value: Mixed(value)}); err == nil { t.Fatal("write accepted", value) } + } + if ZBase("ABC").Validate()!=nil || ZBase("123").Validate()!=nil { t.Fatal("OR") } + bad := Derived("x") + if (Document{Value: Mixed("ZZZ"), Optional: &bad}).Validate()==nil {t.Fatal("optional")} + if (Document{Value: Mixed("ZZZ"), Items: []Derived{bad}}).Validate()==nil {t.Fatal("list")} +} +"#).unwrap(); + run(Command::new("go").arg("test").current_dir(dir.path())); +} + +#[test] +fn generated_cpp_pattern_validation() { + if !available("g++") { + return; + } + let dir = tempdir().unwrap(); + fs::write( + dir.path().join("models.hpp"), + CppCodegen::new(CppOptions::default()).generate_header(&schema()), + ) + .unwrap(); + fs::write(dir.path().join("main.cpp"), r#"#include "models.hpp" +#include +using namespace polyxml::generated; +int main() { + for (auto value : {"ZZZ", "999"}) { assert(validate_Mixed_patterns(value)); Document doc; doc.value=value; assert(doc.validate()); } + for (auto value : {"ABC", "123", "Z", "9", "abc", "xZZZy", ""}) { assert(!validate_Mixed_patterns(value)); Document doc; doc.value=value; assert(!doc.validate()); } + assert(validate_ZBase_patterns("ABC")); assert(validate_ZBase_patterns("123")); + Document doc; doc.value="ZZZ"; doc.optional="x"; assert(!doc.validate()); + doc.optional.reset(); doc.items.push_back("x"); assert(!doc.validate()); +} +"#).unwrap(); + run(Command::new("g++") + .args(["-std=c++20", "main.cpp", "-o", "check"]) + .current_dir(dir.path())); + run(&mut Command::new(dir.path().join("check"))); +} + +#[test] +fn generated_rust_pattern_validation_and_xml_roundtrip() { + let dir = tempdir().unwrap(); + fs::create_dir(dir.path().join("src")).unwrap(); + let core = Path::new(env!("CARGO_MANIFEST_DIR")); + fs::write( + dir.path().join("Cargo.toml"), + format!( + r#"[package] +name="pattern-test" +version="0.0.0" +edition="2021" +[dependencies] +polyxml={{path={:?}}} +quick-xml={{version="0.42",features=["serialize"]}} +serde={{version="1",features=["derive"]}} +serde_json="1" +regex="1" +"#, + core + ), + ) + .unwrap(); + fs::write( + dir.path().join("src/models.rs"), + RustCodegen::new(RustOptions::default()).generate_module(&schema()), + ) + .unwrap(); + fs::write( + dir.path().join("src/main.rs"), + r#"mod models; +use models::*; +fn main() { + for value in ["ZZZ", "999"] { + assert!(validate_Mixed_patterns(value).is_ok()); + let xml=format!("{value}"); + let doc=Document::from_xml(&xml).unwrap(); + assert!(doc.to_xml().is_ok()); + } + for value in ["ABC", "123", "Z", "9", "abc", "xZZZy", ""] { + assert!(validate_Mixed_patterns(value).is_err()); + let xml=format!("{value}"); + assert!(Document::from_xml(&xml).is_err()); + let doc=Document { value: value.into(), ..Default::default() }; + assert!(doc.to_xml().is_err()); + } + assert!(Document::from_xml("").is_err()); + assert!(Document::from_xml("").is_err()); + assert!(validate_ZBase_patterns("ABC").is_ok()); + assert!(validate_ZBase_patterns("123").is_ok()); + assert!(Flag::from_xml("true").is_ok()); + assert!(Flag::from_xml("0").is_ok()); + assert!(Flag::from_xml("garbage").is_err()); +} +"#, + ) + .unwrap(); + run(Command::new("cargo") + .args(["run", "--quiet", "--offline"]) + .env( + "CARGO_TARGET_DIR", + core.join("../../target/pattern-codegen-tests"), + ) + .current_dir(dir.path())); +} + +#[test] +fn generated_python_pattern_validation() { + let python = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../.venv/bin/python"); + if !python.exists() { + return; + } + let dir = tempdir().unwrap(); + fs::write( + dir.path().join("models.py"), + PythonCodegen::new(PythonOptions { + backend: PythonBackend::Pydantic, + ..Default::default() + }) + .generate_module(&schema()), + ) + .unwrap(); + fs::write( + dir.path().join("check.py"), + r#"from models import Mixed, Derived, ZBase +from pydantic import TypeAdapter, ValidationError +mixed = TypeAdapter(Mixed) +for value in ('ZZZ', '999'): + assert mixed.validate_python(value) == value +for value in ('ABC', '123', 'Z', '9', 'abc', 'xZZZy', ''): + try: + mixed.validate_python(value) + except ValidationError: + pass + else: + raise AssertionError(value) +for value in ('ABC', '123'): + assert TypeAdapter(ZBase).validate_python(value) == value +"#, + ) + .unwrap(); + run(Command::new(python).arg("check.py").current_dir(dir.path())); +} + +#[test] +fn generated_java_pattern_validation_and_direct_codecs() { + if !available("javac") { + return; + } + let dir = tempdir().unwrap(); + let generator = JavaCodegen::new(JavaOptions { + package_name: "patterns".into(), + emit_direct_codec: true, + ..Default::default() + }); + let mut files = Vec::new(); + for (name, contents) in generator.generate_files(&schema()) { + fs::write(dir.path().join(&name), contents).unwrap(); + files.push(name); + } + fs::write(dir.path().join("Check.java"), r#"package patterns; +import java.io.*; +public class Check { + public static void main(String[] args) throws Exception { + for (String value : new String[]{"ZZZ", "999"}) { + new Mixed(value); + var parsed = MixedCodec.readXml(new ByteArrayInputStream((""+value+"").getBytes())); + if (!parsed.value().equals(value)) throw new AssertionError(); + MixedCodec.writeXml(parsed, new ByteArrayOutputStream()); + } + for (String value : new String[]{"ABC", "123", "Z", "9", "abc", "xZZZy", ""}) { + try { new Mixed(value); throw new AssertionError(value); } + catch (IllegalArgumentException expected) { } + } + new ZBase("ABC"); new ZBase("123"); + } +} +"#).unwrap(); + files.push("Check.java".into()); + run(Command::new("javac") + .args(["-d", "."]) + .args(&files) + .current_dir(dir.path())); + run(Command::new("java") + .args(["-cp", ".", "patterns.Check"]) + .current_dir(dir.path())); +} + +#[test] +fn generated_csharp_pattern_validation() { + if !available("dotnet") { + return; + } + let dir = tempdir().unwrap(); + fs::write(dir.path().join("Check.csproj"), r#"Exenet8.0enableenable"#).unwrap(); + fs::write( + dir.path().join("Models.cs"), + CSharpCodegen::new(CSharpOptions::default()).generate_module(&schema()), + ) + .unwrap(); + fs::write(dir.path().join("Program.cs"), r#"using Generated; +using System.ComponentModel.DataAnnotations; +static bool Valid(IValidatableObject value) => !value.Validate(new ValidationContext(value)).Any(); +foreach(var value in new[]{"ZZZ", "999"}) if(!Valid(new Mixed(value))) throw new Exception(value); +foreach(var value in new[]{"ABC", "123", "Z", "9", "abc", "xZZZy", ""}) if(Valid(new Mixed(value))) throw new Exception(value); +if(!Valid(new ZBase("ABC")) || !Valid(new ZBase("123"))) throw new Exception("OR"); +"#).unwrap(); + run(Command::new("dotnet") + .args(["run", "--project", "Check.csproj", "--verbosity", "quiet"]) + .current_dir(dir.path())); +} + +#[test] +fn generated_typescript_pattern_validation_all_backends() { + // Optional installed toolchain: npm install --prefix zod@3 valibot@1 + // @sinclair/typebox@0.34 typescript@5; set POLYXML_TS_TEST_MODULES=/node_modules. + let Ok(modules) = std::env::var("POLYXML_TS_TEST_MODULES") else { + return; + }; + let dir = tempdir().unwrap(); + #[cfg(unix)] + std::os::unix::fs::symlink(&modules, dir.path().join("node_modules")).unwrap(); + for (name, backend) in [ + ("zod", TypeScriptBackend::Zod), + ("valibot", TypeScriptBackend::Valibot), + ("typebox", TypeScriptBackend::TypeBox), + ] { + fs::write( + dir.path().join(format!("{name}.ts")), + TypeScriptCodegen::new(TypeScriptOptions { + backend, + ..Default::default() + }) + .generate_module(&schema()), + ) + .unwrap(); + } + fs::write(dir.path().join("check.ts"), r#"import { MixedSchema as z, ZBaseSchema as zb } from './zod'; +import { MixedSchema as v, ZBaseSchema as vb } from './valibot'; +import { MixedSchema as t, ZBaseSchema as tb } from './typebox'; +import * as valibot from 'valibot'; +import { Value } from '@sinclair/typebox/value'; +for(const value of ['ZZZ','999']) { + if(!z.safeParse(value).success || !valibot.safeParse(v,value).success || !Value.Check(t,value)) throw Error(value); +} +for(const value of ['ABC','123','Z','9','abc','xZZZy','']) { + if(z.safeParse(value).success || valibot.safeParse(v,value).success || Value.Check(t,value)) throw Error(value); +} +for(const value of ['ABC','123']) { + if(!zb.safeParse(value).success || !valibot.safeParse(vb,value).success || !Value.Check(tb,value)) throw Error('OR'); +} +"#).unwrap(); + run(Command::new(Path::new(&modules).join(".bin/tsc")) + .args([ + "check.ts", + "--target", + "es2020", + "--module", + "commonjs", + "--strict", + "--skipLibCheck", + ]) + .current_dir(dir.path())); + run(Command::new("node").arg("check.js").current_dir(dir.path())); +} diff --git a/crates/polyxml-core/tests/test_python_codegen.rs b/crates/polyxml-core/tests/test_python_codegen.rs index 1878d6fa..1672cbcc 100644 --- a/crates/polyxml-core/tests/test_python_codegen.rs +++ b/crates/polyxml-core/tests/test_python_codegen.rs @@ -233,7 +233,7 @@ fn test_python_pydantic_codegen_with_facets() { assert!(code.contains("type Age = Annotated[int, Field(ge=0, le=120)]")); assert!(code.contains("class User(BaseModel):")); assert!(code.contains("model_config = ConfigDict(defer_build=True, populate_by_name=True)")); - assert!(code.contains("username: str = Field(..., json_schema_extra={\"type\": \"Element\", \"name\": \"username\", \"json_name\": \"username\"}, min_length=3, max_length=20, pattern=r\"^[a-zA-Z0-9_]+$\")")); + assert!(code.contains("username: str = Field(..., json_schema_extra={\"type\": \"Element\", \"name\": \"username\", \"json_name\": \"username\"}, min_length=3, max_length=20, pattern=r\"\\A(?:^[a-zA-Z0-9_]+$)\\z\")")); assert!(code.contains( "user_age: Age = Field(..., alias=\"age\", serialization_alias=\"age\", json_schema_extra={\"type\": \"Element\", \"name\": \"age\", \"json_name\": \"age\"})" )); @@ -434,3 +434,50 @@ fn test_python_generated_tag_and_custom_header() { assert!(custom_code .starts_with("# ruff: noqa\n# mypy: ignore-errors\n\n# @generated by PolyXML Compiler")); } + +#[test] +fn test_python_abstract_meta_emission() { + // Issue #53: abstract complexTypes mark Meta.abstract so the runtime + // can raise a clear error for xsi:type values with no derivations. + let mut ir = SchemaIR::new().with_target_namespace("urn:veh"); + ir.add_type(TypeDef::Struct(StructDef { + qname: QName::new(Some("urn:veh"), "Vehicle"), + base_type: None, + is_abstract: true, + fields: vec![FieldDef::new( + "id", + "id", + FieldKind::Element, + TypeRef::Primitive(PrimitiveType::String), + )], + documentation: None, + })); + ir.add_type(TypeDef::Struct(StructDef { + qname: QName::new(Some("urn:veh"), "Car"), + base_type: Some(QName::new(Some("urn:veh"), "Vehicle")), + is_abstract: false, + fields: vec![FieldDef::new( + "doors", + "doors", + FieldKind::Element, + TypeRef::Primitive(PrimitiveType::Int), + )], + documentation: None, + })); + + let code = PythonCodegen::new(PythonOptions::default()).generate_module(&ir); + + assert!( + code.contains("class Vehicle:"), + "abstract base must still emit:\n{code}" + ); + assert_eq!( + code.matches("abstract = True").count(), + 1, + "only the abstract type may set Meta.abstract:\n{code}" + ); + assert!( + code.contains("class Car(Vehicle):"), + "concrete derivation must inherit the base:\n{code}" + ); +} diff --git a/crates/polyxml-core/tests/test_rust_codegen.rs b/crates/polyxml-core/tests/test_rust_codegen.rs index d04d35e8..c02e8469 100644 --- a/crates/polyxml-core/tests/test_rust_codegen.rs +++ b/crates/polyxml-core/tests/test_rust_codegen.rs @@ -5,6 +5,21 @@ use polyxml::ir::{ Cardinality, EnumDef, EnumValue, FieldDef, FieldKind, PrimitiveType, QName, SchemaIR, StructDef, TypeDef, TypeRef, UnionBranch, UnionDef, }; +use polyxml::schema_parser::XsdParser; + +/// Slice a generated struct or `impl` body from `start` up to the first +/// closing brace at column 0, panicking when `start` is absent. Scoping +/// assertions to one body keeps them from passing against a sibling type. +fn section<'a>(code: &'a str, start: &str) -> &'a str { + let from = code + .find(start) + .unwrap_or_else(|| panic!("expected {start:?} in generated code:\n{code}")); + let rest = &code[from..]; + match rest.find("\n}\n") { + Some(i) => &rest[..i + 3], + None => rest, + } +} #[test] fn test_rust_identifier_sanitization() { @@ -134,6 +149,7 @@ fn test_rust_zero_copy_codegen() { emit_root_aliases: true, emit_codecs: false, emit_rkyv: false, + phf: false, custom_header: None, }); @@ -186,6 +202,7 @@ fn test_rust_owned_codegen() { emit_root_aliases: true, emit_codecs: false, emit_rkyv: false, + phf: false, custom_header: None, }); @@ -577,3 +594,539 @@ fn test_rust_rkyv_derives() { assert!(code.contains("#[cfg_attr(feature = \"rkyv\", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]")); assert!(code.contains("#[cfg_attr(feature = \"rkyv\", rkyv(check_bytes))]")); } + +// --------------------------------------------------------------------------- +// xsd:extension base-field flattening: Rust structs have no inheritance, so a +// derived type must inline the base chain's fields everywhere — struct body, +// lifetime analysis, and both codec directions. +// --------------------------------------------------------------------------- + +/// Lifetime edge case: `Middle` owns no string fields, so it only earns its +/// `<'a>` if inherited fields participate in `compute_types_with_lifetime`. +fn extension_xsd() -> &'static str { + r#" + + + + + + + + + + + + + + + + + + + + + + + + + + "# +} + +#[test] +fn test_rust_extension_inlines_base_fields() { + let ir = XsdParser::new() + .parse_str(extension_xsd()) + .expect("parse extension schema"); + let code = RustCodegen::new(RustOptions::default()).generate_module(&ir); + + let base = section(&code, "pub struct Base"); + assert!(base.contains("pub label: Cow<'a, str>")); + assert!(base.contains("pub kind: Option>")); + + let middle = section(&code, "pub struct Middle"); + assert!( + middle.contains("pub struct Middle<'a> {"), + "inherited string field must propagate the lifetime parameter:\n{middle}" + ); + for inherited in [ + "pub label: Cow<'a, str>,", + "pub kind: Option>,", + ] { + assert!( + middle.contains(inherited), + "Middle dropped inherited field {inherited:?}:\n{middle}" + ); + } + assert!(middle.contains("pub rank: i32,")); + assert!(middle.contains("pub active: Option,")); + // Root-first ordering: base fields precede derived ones. + let label_at = middle.find("pub label:").expect("label"); + let rank_at = middle.find("pub rank:").expect("rank"); + assert!(label_at < rank_at, "base fields must come first:\n{middle}"); + + // Three-level chain: Leaf sees Base + Middle + its own field. + let leaf = section(&code, "pub struct Leaf"); + for expected in [ + "pub label: Cow<'a, str>,", + "pub kind: Option>,", + "pub rank: i32,", + "pub active: Option,", + "pub note: Option>,", + ] { + assert!( + leaf.contains(expected), + "Leaf missing {expected:?}:\n{leaf}" + ); + } +} + +#[test] +fn test_rust_extension_codecs_decode_and_encode_base_fields() { + let ir = XsdParser::new() + .parse_str(extension_xsd()) + .expect("parse extension schema"); + let code = RustCodegen::new(RustOptions::default()).generate_module(&ir); + + let middle_impl = section(&code, "impl<'a> Middle"); + // Decode: variable slots, element dispatch, and attribute dispatch for + // every inherited member, plus enforcement of required base fields. + for expected in [ + "let mut var_label = None;", + "let mut var_kind = None;", + "let mut var_rank = None;", + "\"label\" => {", + "\"kind\" => {", + "\"rank\" => {", + "\"active\" => {", + "Missing required field 'label'", + ] { + assert!( + middle_impl.contains(expected), + "Middle codec missing {expected:?}:\n{middle_impl}" + ); + } + // Encode: inherited element and attribute both written back out. + for expected in [ + "BytesStart::new(\"label\")", + "BytesText::new(self.label.as_ref())", + "push_attribute((\"kind\"", + "BytesStart::new(\"rank\")", + ] { + assert!( + middle_impl.contains(expected), + "Middle encode missing {expected:?}:\n{middle_impl}" + ); + } + + let leaf_impl = section(&code, "impl<'a> Leaf"); + for expected in [ + "let mut var_label = None;", + "let mut var_rank = None;", + "\"label\" => {", + "\"rank\" => {", + "Missing required field 'label'", + "BytesStart::new(\"label\")", + "push_attribute((\"kind\"", + ] { + assert!( + leaf_impl.contains(expected), + "Leaf codec missing {expected:?}:\n{leaf_impl}" + ); + } +} + +#[test] +fn test_rust_simple_content_extension_shadow_rule() { + // simpleContent extension re-declares the base `value` field; flattening + // must inherit the base attributes without emitting a `value_2` duplicate. + let xsd = r#" + + + + + + + + + + + + + + + "#; + + let ir = XsdParser::new() + .parse_str(xsd) + .expect("parse simpleContent schema"); + let code = RustCodegen::new(RustOptions::default()).generate_module(&ir); + + let derived = section(&code, "pub struct PreciseMeasurement"); + assert!( + derived.contains("pub unit: Option>,"), + "inherited unit attribute missing:\n{derived}" + ); + assert!( + derived.contains("pub precision: Option,"), + "own precision attribute missing:\n{derived}" + ); + assert_eq!( + derived.matches("pub value").count(), + 1, + "value must be shadowed by the derived declaration, not duplicated:\n{derived}" + ); + assert!( + !derived.contains("value_2"), + "shadow rule must prevent suffixed duplicates:\n{derived}" + ); + + let derived_impl = section(&code, "impl<'a> PreciseMeasurement"); + assert!(derived_impl.contains("\"unit\" => {")); + assert!(derived_impl.contains("\"precision\" => {")); + assert!(derived_impl.contains("push_attribute((\"unit\"")); + assert!(derived_impl.contains("push_attribute((\"precision\"")); +} + +#[test] +fn test_rust_extension_base_cycle_cut() { + // A circular extension is invalid XSD but must not hang or duplicate + // fields when handed to the generator directly. + let mut ir = SchemaIR::new().with_target_namespace("urn:cycle"); + let qa = QName::new(Some("urn:cycle"), "Alpha"); + let qb = QName::new(Some("urn:cycle"), "Beta"); + + ir.add_type(TypeDef::Struct(StructDef { + qname: qa.clone(), + base_type: Some(qb.clone()), + is_abstract: false, + fields: vec![FieldDef::new( + "alphaField", + "alphaField", + FieldKind::Element, + TypeRef::Primitive(PrimitiveType::String), + )], + documentation: None, + })); + ir.add_type(TypeDef::Struct(StructDef { + qname: qb.clone(), + base_type: Some(qa.clone()), + is_abstract: false, + fields: vec![FieldDef::new( + "betaField", + "betaField", + FieldKind::Element, + TypeRef::Primitive(PrimitiveType::Int), + )], + documentation: None, + })); + + let codegen = RustCodegen::new(RustOptions::default()); + let code = codegen.generate_module(&ir); + + let alpha = section(&code, "pub struct Alpha"); + assert_eq!( + alpha.matches("pub beta_field").count(), + 1, + "cycle-merged field must appear exactly once:\n{alpha}" + ); + assert_eq!(alpha.matches("pub alpha_field").count(), 1); + assert!(alpha.contains("pub struct Alpha<'a> {")); + + let beta = section(&code, "pub struct Beta"); + assert_eq!( + beta.matches("pub alpha_field").count(), + 1, + "cycle-merged field must appear exactly once:\n{beta}" + ); + assert_eq!(beta.matches("pub beta_field").count(), 1); +} + +#[test] +fn test_rust_simple_content_text_codec() { + let xsd = r#" + + + + + + + + + + + + + + + + + + + + + + +"#; + + let ir = XsdParser::new() + .parse_str(xsd) + .expect("parse simpleContent schema"); + let code = RustCodegen::new(RustOptions::default()).generate_module(&ir); + + // Numeric base: decode parses the element's text content into `value` and + // encode writes it back out. Before the fix, `value` was never assigned + // during decode, so every simpleContent instance failed with + // "Missing required field 'value'". + let measurement = section(&code, "impl<'a> Measurement"); + for expected in [ + "read_element_text(reader, start.local_name().as_ref())", + "var_value = Some(val)", + "BytesText::new(&self.value.to_string())", + ] { + assert!( + measurement.contains(expected), + "Measurement text codec missing {expected:?}:\n{measurement}" + ); + } + + // Patterned string base: the decoded text flows through the inherited + // facets (`validate_patterns`) before it is accepted. + let ticket = section(&code, "impl<'a> Ticket"); + for expected in [ + "read_element_text(reader, start.local_name().as_ref())", + "var_value = Some(text)", + "validate_patterns", + ] { + assert!( + ticket.contains(expected), + "Ticket text codec missing {expected:?}:\n{ticket}" + ); + } +} + +/// quick-xml splits text at entity references and CDATA (`a & b` arrives as +/// Text/GeneralRef/Text), so the reader must *accumulate* segments: the old +/// last-wins helper kept only the final one, silently corrupting any element or +/// simpleContent value containing an entity, char ref, or CDATA section. +#[test] +fn test_rust_read_element_text_accumulates_refs_and_cdata() { + let ir = XsdParser::new() + .parse_str(extension_xsd()) + .expect("parse extension schema"); + + for zero_copy in [true, false] { + let code = RustCodegen::new(RustOptions { + zero_copy, + ..Default::default() + }) + .generate_module(&ir); + let helper = section(&code, "fn read_element_text"); + for expected in [ + "Event::CData(c)", + "Event::GeneralRef(r)", + "r.is_char_ref()", + "r.resolve_char_ref()", + "resolve_xml_entity(r.as_ref())", + ] { + assert!( + helper.contains(expected), + "zero_copy={zero_copy} helper missing {expected:?}:\n{helper}" + ); + } + // Segments append instead of overwriting the previous one. + if zero_copy { + assert!( + helper.contains("text.to_mut().push_str(&raw)"), + "zero_copy helper must append segments:\n{helper}" + ); + } else { + assert!( + helper.contains("text.push_str(&unescaped)"), + "owned helper must append segments:\n{helper}" + ); + assert!( + !helper.contains("text = unescaped.into_owned()"), + "last-wins overwrite resurfaced:\n{helper}" + ); + } + } +} + +/// `--feature phf` swaps both child-event match sites for a phf_codegen-built +/// perfect-hash table plus a per-struct id enum; the default must stay a plain +/// string `match` (preserve-defaults invariant). +#[test] +fn test_rust_phf_dispatch_emission() { + let ir = XsdParser::new() + .parse_str(extension_xsd()) + .expect("parse extension schema"); + let code = RustCodegen::new(RustOptions { + phf: true, + ..Default::default() + }) + .generate_module(&ir); + + // Per-struct id enum + phf_codegen table ahead of each impl. + let base_enum = section(&code, "enum __BaseElementId"); + assert!(base_enum.contains("Label,"), "{base_enum}"); + assert!(code.contains( + "static __BASE_ELEMENT_DISPATCH: ::phf::Map<&'static str, __BaseElementId> = ::phf::Map {" + )); + assert!(code.contains("(\"label\", __BaseElementId::Label)")); + + // Inherited element fields land in the derived struct's table too. + assert!(code.contains( + "static __LEAF_ELEMENT_DISPATCH: ::phf::Map<&'static str, __LeafElementId> = ::phf::Map {" + )); + assert!(code.contains("(\"rank\", __LeafElementId::Rank)")); + assert!(code.contains("(\"note\", __LeafElementId::Note)")); + + // Both event sites route through the table: 3 structs x (Start + Empty). + assert_eq!(code.matches(".get(e.local_name().as_ref())").count(), 6); + assert!(code.contains("Some(&__BaseElementId::Label) => {")); + assert!(code.contains("Some(&__LeafElementId::Note) => {")); + + // Default output is untouched by the feature. + let default_code = RustCodegen::new(RustOptions::default()).generate_module(&ir); + assert!(!default_code.contains("ELEMENT_DISPATCH")); + assert!(!default_code.contains("::phf::")); + assert!(default_code.contains("Event::Start(e) => match e.local_name().as_ref() {")); +} + +/// Union-typed fields group every branch tag onto ONE dispatch variant. +#[test] +fn test_rust_phf_dispatch_union_groups_branch_tags() { + let mut ir = SchemaIR::new(); + + ir.add_type(TypeDef::Struct(StructDef { + qname: QName::local("Card"), + base_type: None, + is_abstract: false, + fields: vec![FieldDef::new( + "number", + "number", + FieldKind::Element, + TypeRef::Primitive(PrimitiveType::String), + )], + documentation: None, + })); + ir.add_type(TypeDef::Struct(StructDef { + qname: QName::local("Cash"), + base_type: None, + is_abstract: false, + fields: vec![FieldDef::new( + "amount", + "amount", + FieldKind::Element, + TypeRef::Primitive(PrimitiveType::Decimal), + )], + documentation: None, + })); + ir.add_type(TypeDef::Union(UnionDef { + qname: QName::local("PaymentMethod"), + branches: vec![ + UnionBranch { + variant_name: "Card".into(), + xml_name: "card".into(), + namespace: None, + type_ref: TypeRef::Named(QName::local("Card")), + documentation: None, + }, + UnionBranch { + variant_name: "Cash".into(), + xml_name: "cash".into(), + namespace: None, + type_ref: TypeRef::Named(QName::local("Cash")), + documentation: None, + }, + ], + documentation: None, + })); + ir.add_type(TypeDef::Struct(StructDef { + qname: QName::local("Order"), + base_type: None, + is_abstract: false, + fields: vec![FieldDef::new( + "method", + "method", + FieldKind::Element, + TypeRef::Named(QName::local("PaymentMethod")), + )], + documentation: None, + })); + + let code = RustCodegen::new(RustOptions { + phf: true, + ..Default::default() + }) + .generate_module(&ir); + + assert!(code.contains("(\"card\", __OrderElementId::Method)")); + assert!(code.contains("(\"cash\", __OrderElementId::Method)")); + assert_eq!( + code.matches("Some(&__OrderElementId::Method) => {").count(), + 2 + ); +} + +/// Attribute and element sharing one XML name: the element's rust name +/// uniquifies to `code_2` everywhere (struct decl, `var_` bindings, dispatch +/// table entry, and match arm must all agree — a bare-string arm inside the +/// `Option` scrutinee would not compile). +#[test] +fn test_rust_phf_dispatch_uniquifies_like_field_metas() { + let mut ir = SchemaIR::new(); + ir.add_type(TypeDef::Struct(StructDef { + qname: QName::local("Collision"), + base_type: None, + is_abstract: false, + fields: vec![ + FieldDef::new( + "code", + "code", + FieldKind::Attribute, + TypeRef::Primitive(PrimitiveType::String), + ), + FieldDef::new( + "code", + "code", + FieldKind::Element, + TypeRef::Primitive(PrimitiveType::String), + ), + ], + documentation: None, + })); + + let code = RustCodegen::new(RustOptions { + phf: true, + ..Default::default() + }) + .generate_module(&ir); + + assert!( + code.contains("pub code_2:"), + "element code_2 missing from struct:\n{code}" + ); + assert!( + code.contains("(\"code\", __CollisionElementId::Code2)"), + "table entry not keyed on the uniquified name:\n{code}" + ); + assert!( + code.contains("Some(&__CollisionElementId::Code2) => {"), + "arm missing or mismatched with the table:\n{code}" + ); + let start = code + .find("Event::Start(e) => match __COLLISION_ELEMENT_DISPATCH") + .expect("phf scrutinee missing"); + assert!( + !code[start..].contains(" \"code\" => {"), + "bare-string arm leaked into the Option match:\n{code}" + ); +} diff --git a/crates/polyxml-core/tests/test_transcoder.rs b/crates/polyxml-core/tests/test_transcoder.rs index 51b6afd5..ec039c2a 100644 --- a/crates/polyxml-core/tests/test_transcoder.rs +++ b/crates/polyxml-core/tests/test_transcoder.rs @@ -151,3 +151,33 @@ fn test_transcoder_errors() { let err = json_to_xml(b"{ not json }", None, None, None, None, None).unwrap_err(); assert!(err.to_string().contains("Invalid JSON")); } + +/// quick-xml splits text at entity references (Text/GeneralRef/Text) and reports +/// CDATA as its own event. The transcoder used to fall through to `_ => {}` for +/// general refs (dropping them) and left attribute entities raw, corrupting +/// xml -> json -> xml round trips. +#[test] +fn test_schemaless_preserves_refs_cdata_and_attr_entities() { + let xml = br#"x & y]]>AB"#; + let json_bytes = xml_to_json(xml, None, None, true).expect("xml_to_json"); + let json_str = String::from_utf8(json_bytes).unwrap(); + + for expected in [ + "\"a\":\"x & y\"", + "\"b\":\"raw & \"", + "\"c\":\"AB\"", + "\"@name\":\"A & B\"", + ] { + assert!( + json_str.contains(expected), + "missing {expected} in:\n{json_str}" + ); + } + + let roundtrip = + json_to_xml(json_str.as_bytes(), None, None, None, None, None).expect("json_to_xml"); + let rt = String::from_utf8(roundtrip).unwrap(); + for expected in ["x & y", "A & B"] { + assert!(rt.contains(expected), "roundtrip lost {expected} in:\n{rt}"); + } +} diff --git a/crates/polyxml-core/tests/test_ts_codegen.rs b/crates/polyxml-core/tests/test_ts_codegen.rs index bcbae4c2..f878630a 100644 --- a/crates/polyxml-core/tests/test_ts_codegen.rs +++ b/crates/polyxml-core/tests/test_ts_codegen.rs @@ -296,7 +296,7 @@ fn test_ts_zod_schema_generation() { assert!(code.contains("import { z } from \"zod\";")); // Verify PostalCodeSchema - assert!(code.contains("export const PostalCodeSchema = z.string().min(3).max(10).regex(new RegExp(\"^[A-Z0-9]+$\"));")); + assert!(code.contains("export const PostalCodeSchema = z.string().min(3).max(10).regex(new RegExp(\"^(?:^[A-Z0-9]+$)(?![\\\\s\\\\S])\"));")); // Verify AgeSchema assert!(code.contains("export const AgeSchema = z.number().int().gte(18).lte(120);")); @@ -501,7 +501,7 @@ fn test_ts_valibot_schema_generation() { assert!(code.contains("import * as v from \"valibot\";")); // Verify simple type facets with v.pipe - assert!(code.contains("export const PostalCodeSchema = v.pipe(v.string(), v.minLength(3), v.maxLength(10), v.regex(/^[A-Z0-9]+$/));")); + assert!(code.contains("export const PostalCodeSchema = v.pipe(v.string(), v.minLength(3), v.maxLength(10), v.regex(/^(?:^[A-Z0-9]+$)(?![\\s\\S])/));")); // Verify enum picklist assert!(code.contains("export const StatusSchema = v.picklist([\"active\", \"inactive\"]);")); diff --git a/crates/polyxml-python/python/polyxml/__init__.py b/crates/polyxml-python/python/polyxml/__init__.py index ee4aad53..1c3c5af7 100644 --- a/crates/polyxml-python/python/polyxml/__init__.py +++ b/crates/polyxml-python/python/polyxml/__init__.py @@ -142,6 +142,7 @@ def iterparse[T]( def serialize( obj: object, *, + target_type: type[object] | None = None, indent: int | None = None, namespaces: bool | None = None, ns_map: dict[str, str] | None = None, @@ -150,6 +151,12 @@ def serialize( Args: obj: Python dataclass or Pydantic model instance. + target_type: Optional declared base type for ``xsi:type`` polymorphic + dispatch. When given, a concrete subclass instance is serialized + using the base type's element name and re-emits an ``xsi:type`` + selector naming the concrete type, so polymorphic round trips + preserve the wire form. When None (default), the instance's own + concrete type is used. indent: Optional indentation size in spaces for pretty-printing. namespaces: Optional boolean toggle to enable or disable XML namespace prefix resolution and root xmlns attribute generation. When None (default), @@ -160,7 +167,13 @@ def serialize( Returns: UTF-8 encoded XML bytes representing the model instance. """ - return _serialize(obj, indent=indent, namespaces=namespaces, ns_map=ns_map) + return _serialize( + obj, + target_type=target_type, + indent=indent, + namespaces=namespaces, + ns_map=ns_map, + ) def serialize_json( diff --git a/crates/polyxml-python/src/lib.rs b/crates/polyxml-python/src/lib.rs index bdd6b798..21064759 100644 --- a/crates/polyxml-python/src/lib.rs +++ b/crates/polyxml-python/src/lib.rs @@ -6,7 +6,8 @@ use pyo3::prelude::*; use pyo3::types::{PyBytes, PyDict, PyList, PyString, PyTuple, PyType}; use pyo3::IntoPyObjectExt; -use std::collections::HashMap; +use std::cell::Cell; +use std::collections::{HashMap, HashSet}; use std::sync::{Arc, RwLock}; use polyxml::schema::{FieldKind, FieldSchema, ModelSchema, ScalarType, ValueType}; @@ -37,6 +38,46 @@ struct CachedSchemaMeta { static SCHEMA_CACHE: RwLock>>> = RwLock::new(None); // Cache keyed by schema name to resolve nested Python types during deserialization static CLASS_BY_SCHEMA: RwLock>>> = RwLock::new(None); +thread_local! { static DISCOVERING_VARIANTS: Cell = const { Cell::new(false) }; } + +fn refresh_variants(cls: &Bound<'_, PyType>, meta: &CachedSchemaMeta) { + DISCOVERING_VARIANTS.with(|busy| { + if busy.replace(true) { + return; + } + let mut seen = HashSet::new(); + refresh_variant_tree(cls, &meta.schema, &mut seen); + busy.set(false); + }); +} + +fn refresh_variant_tree( + cls: &Bound<'_, PyType>, + schema: &Arc, + seen: &mut HashSet, +) { + if !seen.insert(Arc::as_ptr(schema) as usize) { + return; + } + schema.set_variants(discover_variants(cls)); + for field in &schema.fields { + let nested = match &field.val_type { + ValueType::Nested(nested) => Some(nested), + ValueType::List(inner) => match inner.as_ref() { + ValueType::Nested(nested) => Some(nested), + _ => None, + }, + _ => None, + }; + if let Some(nested) = nested { + if let Some(meta) = lookup_cached_meta(&nested.name) { + if let Ok(child_cls) = meta.py_cls.bind(cls.py()).cast::() { + refresh_variant_tree(child_cls, &meta.schema, seen); + } + } + } + } +} fn lookup_cached_meta(schema_name: &str) -> Option> { let class_map = CLASS_BY_SCHEMA.read().unwrap_or_else(|p| p.into_inner()); @@ -228,6 +269,13 @@ fn extract_schema_from_class<'py>( } } } + // Issue #53: abstract types raise a clear error when xsi:type names + // a derivation the runtime does not know. + if let Ok(abstract_val) = meta_cls.getattr("abstract") { + if abstract_val.extract::().unwrap_or(false) { + builder = builder.is_abstract(true); + } + } } let mut cached_fields = Vec::new(); @@ -390,7 +438,10 @@ fn get_or_create_schema_meta<'py>(cls: &Bound<'py, PyType>) -> PyResult(cls: &Bound<'py, PyType>) -> PyResult) -> Vec> { + let mut out = Vec::new(); + let mut seen: HashSet = HashSet::new(); + collect_subclass_schemas(cls, &mut out, &mut seen); + out +} + +fn collect_subclass_schemas( + cls: &Bound<'_, PyType>, + out: &mut Vec>, + seen: &mut HashSet, +) { + let Ok(subclasses) = cls.call_method0("__subclasses__") else { + return; + }; + let Ok(items) = subclasses.try_iter() else { + return; + }; + for item in items.flatten() { + let Ok(sub) = item.cast::() else { + continue; + }; + if !seen.insert(sub.as_ptr() as usize) { + continue; + } + let usable = sub.hasattr("__dataclass_fields__").unwrap_or(false) + || sub.hasattr("model_fields").unwrap_or(false); + if usable { + if let Ok(meta) = get_or_create_schema_meta(sub) { + out.push(Arc::clone(&meta.schema)); + } + } + collect_subclass_schemas(sub, out, seen); + } +} + fn get_or_create_schema<'py>(cls: &Bound<'py, PyType>) -> PyResult<(Arc, PyObject)> { let meta = get_or_create_schema_meta(cls)?; Ok((Arc::clone(&meta.schema), meta.py_cls.clone_ref(cls.py()))) @@ -582,6 +677,19 @@ fn poly_value_to_py<'py>( schema: rec_schema, values, } => { + // xsi:type dispatch (issue #53): the record was parsed as a + // concrete derivation of the declared type, so construct it with + // the derivation's Python class. + if let ValueType::Nested(ref declared) = val_type { + if declared.matches_variant(rec_schema) { + if let Some(variant_meta) = lookup_cached_meta(&rec_schema.name) { + let variant_cls = variant_meta.py_cls.bind(py).clone(); + let variant_vt = ValueType::Nested(Arc::clone(rec_schema)); + return poly_value_to_py(py, val, &variant_vt, Some(&variant_cls), None); + } + } + } + let meta_opt = if let ValueType::Nested(ref s) = val_type { lookup_cached_meta(&s.name) } else { @@ -856,6 +964,18 @@ fn py_to_poly_value<'py>( meta_opt: Option<&CachedSchemaMeta>, ) -> PyResult { if let Some(meta) = meta_opt { + // xsi:type dispatch (issue #53): a concrete subclass instance in a + // field declared as the base type builds the derivation's record so + // the serializer re-emits xsi:type and keeps every concrete field. + let obj_type = obj.get_type(); + if obj_type.as_ptr() != meta.py_cls.as_ptr() { + if let Ok(actual_meta) = get_or_create_schema_meta(&obj_type) { + if meta.schema.matches_variant(&actual_meta.schema) { + return py_to_poly_value(py, obj, &actual_meta.schema, Some(&actual_meta)); + } + } + } + let mut values: Vec> = vec![None; schema.fields.len()]; for (i, field) in schema.fields.iter().enumerate() { @@ -1101,16 +1221,22 @@ impl XmlIterator { } #[pyfunction] -#[pyo3(signature = (obj, indent=None, namespaces=None, ns_map=None))] +#[pyo3(signature = (obj, target_type=None, indent=None, namespaces=None, ns_map=None))] fn serialize<'py>( py: Python<'py>, obj: Bound<'py, PyAny>, + target_type: Option>, indent: Option, namespaces: Option, ns_map: Option>, ) -> PyResult> { - let cls = obj.get_type(); - let meta = get_or_create_schema_meta(&cls)?; + // With `target_type`, serialize against the declared base type so + // xsi:type dispatch round-trips the element name (issue #53); without + // it, the instance's own concrete type is used. + let meta = match &target_type { + Some(t) => get_or_create_schema_meta(t)?, + None => get_or_create_schema_meta(&obj.get_type())?, + }; let poly_val = py_to_poly_value(py, &obj, &meta.schema, Some(&meta))?; let root_name = std::str::from_utf8(&meta.schema.xml_name).unwrap_or(meta.schema.name.as_str()); diff --git a/crates/polyxml-python/tests/test_xsi_type.py b/crates/polyxml-python/tests/test_xsi_type.py new file mode 100644 index 00000000..f3b38cab --- /dev/null +++ b/crates/polyxml-python/tests/test_xsi_type.py @@ -0,0 +1,274 @@ +"""Issue #53: xsi:type polymorphic dispatch through the Python runtime. + +Covers both directions: parsing wire xsi:type selectors into concrete +subclasses (discovered from the Python class hierarchy), and re-emitting +those selectors on serialize so round trips preserve the concrete type. +""" + +import json +from dataclasses import dataclass, field + +import pytest +from pydantic import BaseModel, Field + +import polyxml + +# --------------------------------------------------------------- +# Dataclass fixtures: abstract Vehicle base with Car/Truck derivations +# --------------------------------------------------------------- + + +@dataclass +class Vehicle: + class Meta: + name = "Vehicle" + abstract = True + + id: str = field(metadata={"type": "Element", "name": "id"}) + + +@dataclass +class Car(Vehicle): + class Meta: + name = "Car" + + doors: int = field(metadata={"type": "Element", "name": "doors"}) + + +@dataclass +class Truck(Vehicle): + class Meta: + name = "Truck" + + payload: str = field(metadata={"type": "Element", "name": "payload"}) + + +@dataclass +class Fleet: + class Meta: + name = "Fleet" + + vehicle: Vehicle = field( + metadata={"type": "Element", "name": "vehicle"}, + default_factory=Vehicle, + ) + spares: list[Vehicle] = field( + metadata={"type": "Element", "name": "spare"}, + default_factory=list, + ) + + +@dataclass +class LonelyBase: + class Meta: + name = "LonelyBase" + abstract = True + + id: str = field(metadata={"type": "Element", "name": "id"}) + + +@dataclass +class Tagged: + class Meta: + name = "Tagged" + + label: str = field(metadata={"type": "Element", "name": "label"}) + type: str = field(metadata={"type": "Attribute", "name": "type"}, default="") + + +FLEET_XML = ( + b'' + b'V14' + b'S1ore' + b'S22' + b"" +) + + +def make_fleet() -> Fleet: + return Fleet( + vehicle=Car(id="V1", doors=4), + spares=[ + Truck(id="S1", payload="ore"), + Car(id="S2", doors=2), + ], + ) + + +def test_deserialize_nested_xsi_type_dispatch(): + fleet = polyxml.deserialize(FLEET_XML, Fleet) + + assert type(fleet.vehicle) is Car + assert fleet.vehicle.id == "V1" # inherited base field + assert fleet.vehicle.doors == 4 + + assert type(fleet.spares) is list + assert type(fleet.spares[0]) is Truck + assert fleet.spares[0].payload == "ore" + assert type(fleet.spares[1]) is Car + assert fleet.spares[1].doors == 2 + + +def test_nested_xsi_type_round_trip(): + fleet = make_fleet() + + out = polyxml.serialize(fleet) + assert b'xsi:type="Car"' in out, out + assert b'xsi:type="Truck"' in out, out + assert b'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' in out, out + + reparsed = polyxml.deserialize(out, Fleet) + assert reparsed == fleet + + +def test_root_xsi_type_dispatch(): + xml = b'T9ore' + + vehicle = polyxml.deserialize(xml, Vehicle) + assert type(vehicle) is Truck + assert vehicle.id == "T9" + assert vehicle.payload == "ore" + + +def test_root_round_trip_with_target_type(): + truck = Truck(id="T9", payload="ore") + + out = polyxml.serialize(truck, target_type=Vehicle) + assert out.startswith(b"X' + + with pytest.raises(ValueError, match="known derivation"): + polyxml.deserialize(xml, Fleet) + + +def test_abstract_without_derivations_raises(): + xml = b'X' + + with pytest.raises(ValueError, match="no registered derivations"): + polyxml.deserialize(xml, LonelyBase) + + with pytest.raises(ValueError, match="escape hatch"): + polyxml.deserialize(xml, LonelyBase) + + +def test_plain_type_attribute_not_dispatched(): + # A content attribute literally named "type" on a non-abstract, + # derivation-free model must never be treated as an xsi:type selector. + res = polyxml.deserialize(b'', Tagged) + assert res.type == "gear" + assert res.label == "L" + + out = polyxml.serialize(res) + assert b"xsi:type=" not in out, out + assert b'type="gear"' in out, out + + +# --------------------------------------------------------------- +# Pydantic fixtures: discovery also walks BaseModel subclass trees +# --------------------------------------------------------------- + + +class PVehicle(BaseModel): + id: str = Field(..., json_schema_extra={"type": "Element"}) + + +class PCar(PVehicle): + doors: int = Field(..., json_schema_extra={"type": "Element"}) + + +class PTruck(PVehicle): + payload: str = Field(..., json_schema_extra={"type": "Element"}) + + +class PFleet(BaseModel): + vehicle: PVehicle = Field(..., json_schema_extra={"type": "Element"}) + + +def test_pydantic_xsi_type_dispatch_and_round_trip(): + xml = b'V92' + + fleet = polyxml.deserialize(xml, PFleet) + assert type(fleet.vehicle) is PCar + assert fleet.vehicle.id == "V9" + assert fleet.vehicle.doors == 2 + + out = polyxml.serialize(fleet) + assert b'xsi:type="PCar"' in out, out + + reparsed = polyxml.deserialize(out, PFleet) + assert reparsed == fleet + + +def test_iterparse_xsi_type_dispatch(): + xml = ( + b'' + b'A2' + b'Bp' + b"" + ) + + items = list(polyxml.iterparse(xml, Vehicle, tag="vehicle")) + assert [type(v) for v in items] == [Car, Truck] + assert items[0].doors == 2 + assert items[1].payload == "p" + + +def test_xml_to_json_keeps_variant_fields(): + xml = b'V14' + + out = polyxml.xml_to_json(xml, target_type=Fleet) + data = json.loads(out) + assert data["vehicle"]["id"] == "V1" + assert data["vehicle"]["doors"] == 4 + + +def test_subclass_defined_after_base_was_cached_dispatches(): + @dataclass + class LateBase: + class Meta: + name = "LateBase" + abstract = True + + id: str = field(metadata={"type": "Element", "name": "id"}) + + @dataclass + class LateFleet: + class Meta: + name = "LateFleet" + + vehicle: LateBase = field(metadata={"type": "Element", "name": "vehicle"}) + + xml = b'XY' + with pytest.raises(ValueError): + polyxml.deserialize(xml, LateBase) + fleet_xml = b'XY' + with pytest.raises(ValueError): + polyxml.deserialize(fleet_xml, LateFleet) + + @dataclass + class LateChild(LateBase): + class Meta: + name = "LateChild" + + extra: str = field(metadata={"type": "Element", "name": "extra"}) + + value = polyxml.deserialize(xml, LateBase) + assert isinstance(value, LateChild) + assert value.extra == "Y" + fleet = polyxml.deserialize(fleet_xml, LateFleet) + assert isinstance(fleet.vehicle, LateChild) diff --git a/docs/benchmarks.md b/docs/benchmarks.md index 4df776bf..cd25a60b 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -99,6 +99,14 @@ When storing XML dataclasses and Pydantic models in embedded transactional key-v The benchmark suite is reusable and version-controlled. +> 🛡️ **Memory safety**: the suite entry points below run under +> [`scripts/memcap.sh`](https://github.com/nth-bailey/PolyXML/blob/main/scripts/memcap.sh), +> which caps each run at 60% of available RAM in an isolated cgroup (kernel +> OOM-kills only the runaway build; the host stays responsive — `ulimit` +> fallback where systemd is unavailable). Wrap any ad-hoc +> `cargo bench --release` the same way; tune with `POLYXML_MEMCAP_PCT`, opt +> out with `POLYXML_MEMCAP_DISABLE=1`. + ### Run All Benchmarks (Rust + Python) ```bash @@ -109,6 +117,7 @@ The benchmark suite is reusable and version-controlled. ```bash cargo bench --bench core_benchmarks +cargo bench --bench tag_dispatch # dispatch strategy suite: docs/benchmarks/rust-phf-dispatch.md ``` ### Run Python Comparative Benchmarks CLI diff --git a/docs/benchmarks/rust-phf-dispatch.md b/docs/benchmarks/rust-phf-dispatch.md new file mode 100644 index 00000000..62a2c1f0 --- /dev/null +++ b/docs/benchmarks/rust-phf-dispatch.md @@ -0,0 +1,282 @@ +# Rust Tag Dispatch: `match` vs `HashMap` vs `phf` (issue #49) + +> Status: benchmark results below are produced on a Linux x86-64 host with a +> functional PMU (`perf_event_paranoid = 2`). Commands are reproducible with +> stock tooling; see [Reproducing](#reproducing). + +## What changed + +Rust codegen gained an opt-in **`--feature phf`** flag that swaps the generated +child-element dispatch from a linear string `match` to a compile-time perfect +hash table built with `phf_codegen`: + +```bash +polyxml generate schema.xsd --lang rust --feature phf +# or in polyxml.toml: +# [[generate]] +# target = "rust" +# features = ["phf"] +``` + +> **Note on flag shape**: issue #49 originally proposed a standalone `--phf` +> flag; per the issue #50 unification decision (see the +> `polyxml-codegen-workflow` skill §9), all Rust opt-ins are exposed through the +> repeatable `--feature` array instead. `--feature phf` is the supported form. + +For every struct with element children the generator emits: + +```rust +/// Perfect-hash element dispatch id for `Address` (`--feature phf`). +#[derive(Clone, Copy)] +enum __AddressElementId { Street, City, ... } + +static __ADDRESS_ELEMENT_DISPATCH: ::phf::Map<&'static str, __AddressElementId> = ::phf::Map { + key: ..., disps: &[...], entries: &[("city", __AddressElementId::City), ...], +}; +``` + +and both `Event::Start` and `Event::Empty` arm dispatches become +`__ADDRESS_ELEMENT_DISPATCH.get(e.local_name().as_ref())` lookups. Union-typed +fields map every branch tag onto one variant. Default output (no feature) is +byte-identical to before — locked by `test_rust_phf_dispatch_emission`. + +Consuming crates must add `phf = "0.14"` to their `Cargo.toml`. + +## Methodology + +* **Framework**: Criterion.rs (`benches/tag_dispatch.rs`, issue acceptance + criterion #2). +* **Scale tiers** (issue-defined): small **16**, medium **120**, large **600** + elements, plus an extra-large **1500** tier added here so the + `match`→`phf` crossover is *measured* directly rather than extrapolated + (the issue's hypothesis put the break-even at 50 elements — see + [Auto-threshold](#auto-threshold-recommendation)). Tags are seeded + two-word pseudo-ISO-20022 vocabulary + (`scripts/gen_tag_dispatch_fixtures.py`, 10-45 byte names) so LLVM's + length-bucketed memcmp chains are neither favored nor gamed. +* **Baselines** (issue-defined): + 1. `match tag { "..." => ... }` — LLVM lowers these to length-bucketed + vectorized/memcmp chains, not jump tables (byte-array keys can't jump-table). + 2. Runtime `HashMap<&str, u32>` (SipHash-1-3, std default). + 3. Compile-time `phf::Map<&str, u32>`. +* **Hit and miss paths** are measured separately per strategy + (`*_hit` = full-tier sweep, throughput reported in tags/s; + `*_miss` = lookup of an unknown sentinel tag). +* **Tokenization is intentionally excluded**: XML tokenization cost is + identical across dispatch strategies, so only the lookup — the part the + feature changes — is measured. Issue metric "1,000,000 tags" maps to + tags/s × elapsed time below. + +## Results — throughput (Criterion) + +Host: AMD Ryzen 5 4500, Linux x86-64 (WSL2), rustc 1.98.1, Criterion 0.8. +All times are per full-tier sweep unless noted; throughput is tags/second as +reported by `Throughput::Elements`. + +### Hit path (lookup of a tag present in the tier) + +| Tier | `match` hit | hit thrpt | `HashMap` hit | hit thrpt | `phf` hit | hit thrpt | +| :--- | ---: | ---: | ---: | ---: | ---: | ---: | +| small (16) | **52.1 ns** | 307.1 M tags/s | 275.0 ns | 58.2 M tags/s | 440.3 ns | 36.3 M tags/s | +| medium (120) | **658.3 ns** | 182.3 M tags/s | 2.063 µs | 58.2 M tags/s | 3.232 µs | 37.1 M tags/s | +| large (600) | 11.141 µs | 53.9 M tags/s | **10.613 µs** | 56.5 M tags/s | 15.843 µs | 37.9 M tags/s | +| large (1500) | 75.590 µs | 19.8 M tags/s | **27.044 µs** | 55.5 M tags/s | 40.352 µs | 37.2 M tags/s | + +### Miss path (lookup of an unknown sentinel tag) + +| Tier | `match` miss | `HashMap` miss | `phf` miss | +| :--- | ---: | ---: | ---: | +| small (16) | **5.36 ns** | 18.67 ns | 21.40 ns | +| medium (120) | **7.07 ns** | 17.69 ns | 22.02 ns | +| large (600) | 26.77 ns | **17.14 ns** | 21.06 ns | +| large (1500) | 46.19 ns | **17.64 ns** | 21.39 ns | + +Interpretation: + +* **Small (16) & medium (120)**: LLVM's length-bucketed `memcmp` chain wins + decisively — `match` is 5.3× faster than `HashMap` and 8.4× faster than + `phf` at 16 tags (52 ns vs 440 ns per sweep). This inverts issue #49's + hypothesis that `phf` would already win at ≥50 elements: on this + toolchain it does not, because `phf` pays a fixed ~27 ns/tag (SipHash-1-3 + + displacement probe) while small `memcmp` chains are a handful of + predictable vectorized compares. +* **Large (600)**: the crossover happens between tiers. `HashMap` edges + ahead of `match` (10.6 µs vs 11.1 µs) while `phf` (15.8 µs) is still + behind both — the linear `memcmp` chain cost has grown super-linearly + with the number of distinct string lengths, but not yet past `phf`'s + constant per-tag cost. +* **Large (1500)**: `match` collapses to 19.8 M tags/s (75.6 µs — 3.8× + slower than at 600 tags scaled, the classic i-cache/branch-predictor + blow-up of hundreds of `memcmp` arms). `phf` (40.4 µs) finally beats it + by 1.87× while staying perfectly flat at ~37 M tags/s across **all** + tiers. `HashMap` stays fastest on hits (27.0 µs) — it hashes once and + probes a SIMD-friendly control-byte table — but it is a *runtime* build, + i.e. only a baseline, not a shippable static-dispatch option. +* **Misses**: `match` degrades linearly with tier size (5.4 ns → 46.2 ns, + since a miss walks the whole chain) while both hash structures are flat + (`HashMap` ~17–19 ns, `phf` ~21–22 ns constant). XML deserialization + only dispatches tags that exist in the schema, so the miss path matters + mainly for schema-validation workloads. + +## Results — hardware counters (`perf stat`, issue acceptance criterion #3) + +The canonical command (requires the `perf` binary): + +```bash +perf stat -e cycles,instructions,branches,branch-misses,L1-icache-load-misses \ + ./target/release/deps/tag_dispatch- --bench '' +``` + +This host lacks the `perf` binary, so counters are read through the same PMU +via `perf_event_open` using the committed harness (same events, same +semantics, userspace-only as required by `perf_event_paranoid = 2`): + +```bash +scripts/perf_stat.sh -- ./target/release/deps/tag_dispatch- --bench '' +``` + +Counters are for a **full sweep of the tier** (warm process, summed over +Criterion's iterations by the harness — compare rows within a tier, not +across tiers). IPC = instructions / cycles; miss% = branch-misses / branches. + +| Tier | Strategy | cycles | instructions | branches | branch-misses | L1I misses | IPC | miss% | +| :--- | :--- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 16 | match | 36.0 B | 93.2 B | 20.9 B | 13.0 M | 74.9 K | 2.587 | 0.062% | +| 16 | hashmap | 41.8 B | 126.4 B | 11.5 B | 12.9 M | 109 K | 3.024 | 0.112% | +| 16 | phf | 37.6 B | 94.7 B | 7.8 B | 13.0 M | 103 K | 2.519 | 0.168% | +| 120 | match | 44.8 B | 124.1 B | 21.2 B | 17.1 M | 332 K | 2.770 | 0.081% | +| 120 | hashmap | 40.5 B | 122.6 B | 11.1 B | 13.1 M | 124 K | 3.030 | 0.118% | +| 120 | phf | 36.4 B | 92.3 B | 7.5 B | 14.8 M | 106 K | 2.539 | 0.196% | +| 600 | match | 46.3 B | 121.7 B | 16.6 B | 87.1 M | 12.5 M | 2.630 | 0.526% | +| 600 | hashmap | 45.4 B | 135.0 B | 12.2 B | 13.8 M | 123 K | 2.974 | 0.113% | +| 600 | phf | 39.7 B | 102.5 B | 8.3 B | 13.3 M | 106 K | 2.582 | 0.160% | +| 1500 | match | 44.1 B | 97.1 B | 12.0 B | 205.1 M | 92.1 M | 2.203 | 1.704% | +| 1500 | hashmap | 37.4 B | 107.0 B | 9.7 B | 24.9 M | 134 K | 2.860 | 0.257% | +| 1500 | phf | 44.3 B | 111.2 B | 9.0 B | 35.0 M | 130 K | 2.508 | 0.388% | + +Interpretation: + +* **The issue's two predicted failure modes are confirmed for `match`, but + only at scale.** From 120 → 1500 tags, `match`'s branch-miss rate grows + 21× (0.081% → 1.704%) and its L1 instruction-cache misses explode + **277×** (332 K → 92.1 M) as hundreds of string-literal `memcmp` arms + stop fitting in the 32 KiB I-cache and the predictor saturates — exactly + the "branch misprediction & i-cache thrash" mechanism #49 predicted for + large tag counts. Both hash structures stay ~flat by comparison + (`phf` L1I: 103 K → 130 K over the same range). +* **`phf` is the lowest-work dispatch of the three**: fewest branches at + every tier (7.5–9.0 B vs 16–21 B for `match` at small/medium — one hash + instead of a compare chain), and instruction counts *fall* as tiers grow + because loop overhead amortizes. Its disadvantage is raw latency per + lookup (SipHash + probe ≈ 27 ns/tag), not scalability — visible as the + lowest-but-stable IPC (≈2.5, load-dependent probe stalls). +* **`HashMap` runs at the highest IPC (≈2.9–3.0)** thanks to hashbrown's + branch-poor SIMD control-byte scan, but pays the most instructions at + small tiers (126.4 B vs 93.2 B for `match` at 16 tags). +* At 1500 tags `match` executes **1.7 branch-misses per 100 branches and + ~710× more I-cache misses than `phf`** (92.1 M vs 130 K) — the wall-clock + collapse in the throughput table (19.8 M vs 37.2 M tags/s) is these + counters, not extra work. + +## Compilation time & binary size + +**Small schema (completed).** Same standalone consumer crate (detached from +the workspace, `--release`), rebuilt after `touch`-ing only the generated +file; section sizes via `size`/`size -A` on the example binary: + +| Variant | gen-rebuild time | first build incl. `phf` dep | `.text` | `.rodata` | `.data` | `.bss` | +| :--- | ---: | ---: | ---: | ---: | ---: | ---: | +| `match` (default) | 2.51–2.54 s | — | 2 590 640 | 367 768 | 271 608 | 3 048 | +| `phf` | 2.53–2.55 s | 2.75 s | 2 591 728 | 367 912 | 271 928 | 1 640 | +| **Δ** | +0.02 s (noise) | +0.2 s one-time | **+1 088 (+0.04 %)** | **+144 (+0.04 %)** | +320 | −1 408 | + +**Large tiers (600 / 1500 elements): not measured — deferred to +#55.** The encounter, for the record: + +* Compiling the generated consumer crate at **600 *and* 1500 elements — + both the `match` *and* the `phf` variant — exceeds ~3.2 GiB for a single + `rustc`** (observed in-cgroup anon-RSS at the OOM kill: 3.19–3.21 GiB, + right at the cap of 60 % of 5.4 GiB available on the 7.7 GiB reference + host). Uncapped attempts swap-thrashed the host into freezing (two WSL + restarts) before `scripts/memcap.sh` existed; every capped attempt since + was kernel-OOM-killed *inside its own cgroup* with the host untouched — + by design. +* Because the memory blow-up hits **both variants at the same tier**, it + scales with field count (type-checking/monomorphizing the 600- and + 1500-field `decode_xml` paths), **not** with `match`-arm count — so no + `match`-vs-`phf` compile-memory conclusion can be drawn from it either + way. Re-establishing the compile-time and `.text`/`.rodata` growth curves + at these tiers needs a host where one `rustc` may safely use ≥ 4 GiB and + is tracked in #55. + +Interpretation (small schema): + +* **`phf` dispatch is effectively free at build scale**: +0.04 % `.text`, + +144 bytes `.rodata` (the perfect-hash tables live in read-only data), + and a rebuild delta within run-to-run noise. The only real build cost is + a one-time ~0.2 s to compile the `phf` dependency. +* This matches the runtime story: the table itself is tiny — what differs + between the strategies is *how each lookup walks it*, not how much code + or data gets emitted for a typical schema. + +## Auto-threshold recommendation + +**Enable `--feature phf` for schemas with ≥ ~900 element tags on a struct; +keep the default `match` below that.** + +Derivation (per-tag hit cost, linear interpolation between the measured +tiers): `match` costs 18.6 ns/tag at 600 tags and 50.4 ns/tag at 1500 +(tags/s × tier size), while `phf` is flat at ≈26.9 ns/tag at every tier. +Solving `18.6 + (50.4−18.6)·(N−600)/900 = 26.9` gives the `match`→`phf` +hit crossover at **N ≈ 835**; we round up to **900** for measurement noise +and schema drift. Notes: + +* This is a *recommendation*, not an auto-enable: the feature stays + opt-in (`--feature phf` / `features = ["phf"]`) per the issue #50 + unification decision — PolyXML does not silently change emitted code. +* The threshold is **~18× higher than issue #49's hypothesized 50-element + break-even** on this toolchain (rustc 1.98.1 / LLVM): small and medium + tiers keep winning for `match` at 5–8× margin. Trust the measurement, + not the hypothesis — re-run `cargo bench --bench tag_dispatch` on your + toolchain before adopting a different number. +* `phf`'s real win at ≥900 tags is **predictability**: flat ~37 M tags/s + and flat counters from 16 → 1500 tags, whereas `match` degrades 3.8× + (and its counters, catastrophically) with no upper bound in sight. +* Miss-heavy workloads (schema validation of unknown tags) cross over much + earlier: `match` misses degrade linearly (5.4 → 46.2 ns) while `phf` + misses are flat (~21 ns), intersecting around **≈380 tags**. If your + workload dispatches unknown tags on a hot path, prefer `phf` from ~400. +* The runtime `HashMap` baseline beats `phf` on hits at every tier, but it + cannot ship as static dispatch (it requires a runtime build of the table + per deserializer instance); it is reported only as a reference point. + +## Reproducing + +Every command below can be wrapped in `scripts/memcap.sh` (default: cap the +run at 60% of available RAM in an isolated cgroup, swap off) so a large-tier +build or bench cannot freeze a small host — the benchmark entry points +(`benchmarks/run_all.sh`, `benchmarks/cli/benchmark.sh`, +`scripts/perf_stat.sh`) already re-exec through it. Set +`POLYXML_MEMCAP_DISABLE=1` to opt out. + +```bash +# 1. Throughput (all tiers/strategies, ~3 minutes) +scripts/memcap.sh cargo bench -p polyxml --bench tag_dispatch + +# 2. Hardware counters for one strategy/tier (perf_stat.sh caps itself; +# pass --bench or Criterion runs test mode and you measure startup) +BIN=$(ls -t target/release/deps/tag_dispatch-* | grep -v '\.d$' | head -1) +scripts/perf_stat.sh -- "$BIN" --bench 'tag_dispatch/large_600/phf_hit' + +# 3. Regenerate tag fixtures (deterministic seed) +python3 scripts/gen_tag_dispatch_fixtures.py + +# 4. Generated-crate compile time / size comparison: build the same consumer +# crate twice (default output vs --feature phf + phf dep), timed after +# touching only the generated file; inspect with `size` (.text/.rodata). +# Run each build through scripts/memcap.sh: at 600 and 1500 elements +# EVERY variant's rustc needs >=3.2 GiB and will be OOM-killed at the +# default cap (by design — the host must stay responsive). Those tiers +# are deferred to #55; the small-schema numbers above are +# the ones measured within the cap. +``` diff --git a/docs/guides/compiler.md b/docs/guides/compiler.md index a905052a..4ea4b6a8 100644 --- a/docs/guides/compiler.md +++ b/docs/guides/compiler.md @@ -50,7 +50,7 @@ polyxml generate --lang python --out ./generated/python schema.xsd polyxml generate --lang python --backend pydantic-v2 --out ./generated/python schema.xsd # Generate zero-copy Rust models with inherent streaming codecs -polyxml generate --lang rust --zero-copy --codecs --out ./generated/rust schema.xsd +polyxml generate --lang rust --feature zero-copy --codecs --out ./generated/rust schema.xsd # Generate all 7 languages simultaneously polyxml generate \ @@ -69,26 +69,69 @@ polyxml generate \ | Option | Flag | Description | Default | |---|---|---|---| -| **Target Language** | `-l`, `--lang` | Target language (`python`, `rust`, `cpp`, `java`, `typescript`, `go`, `csharp`). Can be specified multiple times. | Required | -| **Output Directory** | `-o`, `--out` | Target directory for generated source files. | `.` | +| **Target Language** | `-l`, `--lang` | Target language (`python`, `rust`, `cpp`, `java`, `typescript`, `go`, `csharp`). Can be specified multiple times. | `python` | +| **Output Directory** | `-o`, `--out` | Target directory for generated source files. | `generated` | +| **Model Style** | `--style` | Target-specific type representation; see the table below. | Existing target default | +| **Enhancements** | `--feature NAME` | Repeatable; also accepts comma-separated names. | None added | | **Target Backend** | `-b`, `--backend` | Target backend (`dataclass`/`pydantic-v2` for Python; `standard`/`jackson` for Java; `standard`/`glaze` for C++; `none`/`zod`/`valibot`/`typebox` for TypeScript; `standard`/`easyjson`/`sonic` for Go). | Target default | | **Compilation Mode** | `-m`, `--mode` | Target packaging mode (`header` or `modules` for C++). | Target default | -| **Rust Zero-Copy** | `--zero-copy` | Use `Cow<'a, str>` string slices instead of owned `String`. | `true` | -| **Rust rkyv** | `--rkyv` | Derive `rkyv::{Archive, Serialize, Deserialize}` for zero-copy wire format serialization. | `false` | | **Streaming Codecs**| `--codecs` | Emit inherent zero-copy streaming XML serializers and deserializers. | `true` | -| **Validation Schemas** | `--zod` | Emit runtime Zod validation schemas for TypeScript (equivalent to `--backend zod`). | `false` | -| **C# Source-Gen** | `--source-gen` | Emit Native AOT compile-time `JsonSerializerContext` for C#. | `false` | -| **C# Record Kind** | `--record-kind` | C# record representation (`class` or `struct`). | `class` | -| **Model Style (Java/C#)** | `--style` | Model representation: `record` (default), `pojo` for JavaBeans (alias `class`), or `class` for mutable C# classes. | `record` | -| **Java Builders** | `--builder` | Emit fluent `Type.builder()...build()` factories for Java models (records and POJOs alike). | `false` | -| **Java Codec** | `--codec` | Java XML binding: `annotation` (default) or `direct`, which adds reflection-free StAX companion `TypeCodec` classes. Rejects wildcards, `xs:anyType`, and runtime `xsi:type` dispatch. | `annotation` | | **Package / Namespace** | `-p`, `--package` | Namespace or package name for Java, Go, C#, or C++. | Target default | | **Custom Header** | `--custom-header` | Custom comment, license, or linter directive text to prepend to generated files. | `None` | -| **Dry Run** | `--dry-run` | Parse and print generated output without writing to disk. | `false` | -| **Format** | `--format` | Automatically format generated code using host toolchains (`ruff`, `cargo fmt`, `clang-format`, `gofmt`). | `true` | +| **Dry Run** | `--dry-run` | Validate options and parse schemas without writing to disk. | `false` | +| **Format** | `--format` | Automatically format generated code using host toolchains (`ruff`, `cargo fmt`, `clang-format`, `gofmt`). | `false` | --- +### Backend, style, and feature compatibility + +Options apply to every language selected in one invocation. Use `polyxml.toml` +when targets need different options. Invalid values, unsupported combinations, +and conflicting options fail before schema parsing or output creation, +including with `--dry-run`. + +| Target | Backends (first is default) | Styles | Features | +| --- | --- | --- | --- | +| Python | `dataclass`, `pydantic` | `dataclass` (dataclass backend only) | `slots`, `kw-only` (dataclass backend only) | +| Rust | `standard` | — | `zero-copy`, `rkyv`, `phf` | +| TypeScript | `interfaces`, `zod`, `valibot`, `typebox` | — | — | +| Java | `standard`, `jackson` | `record` (default), `pojo` (alias `class`) | `builder`, `direct-codec` | +| C# | `standard`, `source-gen` | `record-class` (default), `record-struct`, `class` (mutable); legacy aliases `record`, `pojo` | — | +| C++ | `standard`, `glaze` | — | — | +| Go | `standard`, `easyjson`, `sonic` | — | — | + +Defaults are unchanged: Rust zero-copy and Python slots/keyword-only fields are +already enabled. Future features such as `aot`, Python plain `class`, +and C# mutable `struct` are rejected until their generators support them. + +```bash +polyxml generate schema.xsd --lang rust --feature zero-copy --feature rkyv +polyxml generate schema.xsd --lang java --style pojo --feature builder,direct-codec +polyxml generate schema.xsd --lang csharp --backend source-gen --style record-struct +``` + +The same options work in both `[[generate]]` and `[codegen.]`: + +```toml +[codegen.java] +output = "generated/java" +backend = "jackson" +style = "pojo" +features = ["builder", "direct-codec"] +``` + +The legacy hidden flags `--zod`, `--source-gen`, `--record-kind`, `--rkyv`, +`--builder`, and `--codec`, along with their `polyxml.toml` counterparts, have +been removed: clap rejects the flags outright and unknown manifest fields are +errors, so the unified options above are the only spellings. For owned Rust +strings, `--zero-copy=false` (or `zero_copy = false` in the manifest) remains +available as a first-class option; combining it with `--feature zero-copy` is +an error. + +`polyxml generate` without schema paths delegates to the manifest. Put target +options in that manifest; command-line generation overrides are rejected rather +than silently ignored. + ### 2. `polyxml build` Declaratively compile complex, multi-schema enterprise projects using a workspace manifest (`polyxml.toml`): @@ -116,9 +159,8 @@ codecs = true [[generate]] target = "rust" output = "src/generated/rust" -zero_copy = true +features = ["zero-copy", "rkyv"] codecs = true -rkyv = true [[generate]] target = "java" @@ -126,8 +168,7 @@ output = "src/generated/java" package = "com.enterprise.banking.iso20022" backend = "jackson" # style = "pojo" # record (default) | pojo (JavaBeans, alias class) -# builder = true # fluent Java builders -# codec = "direct" # reflection-free StAX companion codecs +# features = ["builder", "direct-codec"] [[generate]] target = "typescript" @@ -150,8 +191,8 @@ backend = "sonic" target = "csharp" output = "src/generated/csharp" namespace = "Enterprise.Banking.Iso20022" -source_gen = true -record_kind = "struct" +backend = "source-gen" +style = "record-struct" ``` --- @@ -240,3 +281,32 @@ Explore full-scale enterprise examples demonstrating `polyxml.toml` manifests an Compiles the European **CEN SIRI v2.0** and **NeTEx** schemas and bridges live Google GTFS-Realtime feeds across all 7 languages. + +### Shell completion + +Generate and install a completion script for your shell: + +```bash +# Bash: add this to ~/.bashrc +source <(polyxml completions bash) + +# Zsh: save _polyxml into a directory already on $fpath, then run compinit +polyxml completions zsh > /your/completion/directory/_polyxml + +# Fish +mkdir -p ~/.config/fish/completions +polyxml completions fish > ~/.config/fish/completions/polyxml.fish +``` + +Backend, style, and feature suggestions use the CLI's validation rules and the +selected `--lang` (including aliases). With multiple languages, completion only +suggests values accepted by every selected target. Without `--lang`, it uses the +Python default. Hidden deprecated flags are not suggested. Bash also completes +comma-separated features; repeat `--feature` for portable completion across shells. + +### CLI startup benchmark + +Run `./benchmarks/cli/benchmark.sh` with `hyperfine` installed. It builds the +release CLI and measures help rendering plus argument validation and parsing of +an empty schema. See [the benchmark methodology](../../benchmarks/cli/README.md) +for measurement limits and output location. diff --git a/docs/guides/csharp.md b/docs/guides/csharp.md index d213ebf9..06449e15 100644 --- a/docs/guides/csharp.md +++ b/docs/guides/csharp.md @@ -48,7 +48,7 @@ namespace = "Enterprise.Banking.Iso20022" ## Mutable classes ```bash -polyxml generate schema.xsd --lang csharp --style class --source-gen --out generated +polyxml generate schema.xsd --lang csharp --style class --backend source-gen --out generated ``` `--style pojo` is an alias for `class`; `--style record` retains the default. @@ -70,12 +70,13 @@ entity.Status = "PROCESSED"; output = "generated/csharp" namespace = "Enterprise.Models" style = "class" -source_gen = true +backend = "source-gen" ``` -These fields also work in `[[generate]]`. `--record-kind struct` cannot be -combined with mutable class style. `--builder` and `--codec` apply to Java; -C# uses object initializers and its standard XML/JSON serializers. +These fields also work in `[[generate]]`. `--style record-struct` and +`--style class` are mutually exclusive values of the same option. Java's +`--feature builder,direct-codec` options do not apply; C# uses object +initializers and its standard XML/JSON serializers. --- @@ -208,12 +209,12 @@ Customer restored = JsonSerializer.Deserialize(jsonString)!; assert(restored.Name == customer.Name); ``` -### Compile-Time Source Generation (`--source-gen`) +### Compile-Time Source Generation (`--backend source-gen`) For Native AOT, high-throughput microservices, and reflection-free environments, PolyXML can emit a compile-time `JsonSerializerContext`: ```bash -polyxml generate --lang csharp --source-gen --record-kind struct --out ./src/Generated schema.xsd +polyxml generate --lang csharp --backend source-gen --style record-struct --out ./src/Generated schema.xsd ``` This generates `[JsonSourceGenerationOptions]` and `[JsonSerializable(typeof(T))]` annotations: @@ -234,9 +235,9 @@ string json = JsonSerializer.Serialize(customer, CustomerJsonContext.Default.Cus Customer restored = JsonSerializer.Deserialize(json, CustomerJsonContext.Default.Customer); ``` -### Record Structs (`--record-kind struct`) +### Record Structs (`--style record-struct`) -By default, PolyXML emits reference `record class` types. For zero-allocation, cache-friendly scenarios where data contracts are small or short-lived, pass `--record-kind struct`: +By default, PolyXML emits reference `record class` types. For zero-allocation, cache-friendly scenarios where data contracts are small or short-lived, pass `--style record-struct`: ```csharp public readonly record struct Customer( diff --git a/docs/guides/java.md b/docs/guides/java.md index 4b8840d6..0d5a4fd4 100644 --- a/docs/guides/java.md +++ b/docs/guides/java.md @@ -10,9 +10,9 @@ description: Ultra-fast Java XML processing using Java 22 Foreign Function & Mem Records remain the default. Select mutable JavaBeans for setter-based frameworks: ```bash -polyxml generate schema.xsd --lang java --style pojo --builder --out generated -polyxml generate schema.xsd --lang java --style pojo --backend jackson --builder --out generated -polyxml generate schema.xsd --lang java --style record --builder --out generated +polyxml generate schema.xsd --lang java --style pojo --feature builder --out generated +polyxml generate schema.xsd --lang java --style pojo --backend jackson --feature builder --out generated +polyxml generate schema.xsd --lang java --style record --feature builder --out generated ``` `--style class` is an alias for `pojo`. Mutable complex types have a public no-argument @@ -49,10 +49,11 @@ is introduced. ## Direct streaming XML codecs ```bash -polyxml generate schema.xsd --lang java --style pojo --builder --codec direct --out generated +polyxml generate schema.xsd --lang java --style pojo --feature builder,direct-codec --out generated ``` -`--codec annotation` is the default and emits only models. `direct` adds a companion +Annotation-based codecs are the default and emit only models. +`--feature direct-codec` adds a companion `TypeNameCodec.java` for every generated type, with statically linked getters/setters, constructors, and nested codecs. It works with either record or POJO models and can be combined with Jackson annotations. It uses StAX from the JDK, without reflection @@ -97,9 +98,8 @@ target = "java" output = "generated/java" package = "com.enterprise.models" style = "pojo" -builder = true +features = ["builder", "direct-codec"] backend = "jackson" -codec = "direct" ``` The same options work under `[codegen.java]`. See the diff --git a/docs/guides/polymorphism.md b/docs/guides/polymorphism.md new file mode 100644 index 00000000..2efd2d69 --- /dev/null +++ b/docs/guides/polymorphism.md @@ -0,0 +1,145 @@ +# Polymorphic Types & `xsi:type` Dispatch + +XSD type hierarchies built from `` (and +`xsi:type` wire selectors) are dispatched by PolyXML's **dynamic runtime** — +the shared Rust engine behind the native API, `polyxml-python`, and (as it +gains nested schemas) `polyxml-js`. This page documents the strategy, its +round-trip behavior, and the known limitations of the static codegen paths. + +--- + +## 🧭 Strategy: a type registry on `ModelSchema` + +Every runtime `ModelSchema` carries: + +- `is_abstract` — the source schema marked the type `abstract="true"`. +- a **`variants` registry** — the concrete derivations eligible for + dispatch, each keyed by its **QName local part** (e.g. `Car` for + `urn:veh:Car`). + +The registry is populated from whichever schema source built the model: + +| Source | How variants are discovered | +|---|---| +| **XSD** (`ModelSchema::from_ir`) | All *transitive* `xs:extension` derivations of the type are built recursively from the `SchemaIR`. Derived schemas **flatten the full base-chain content model**: inherited fields precede the type's own fields. | +| **Python classes** (PyO3) | A `__subclasses__()` walk of the dataclass/Pydantic hierarchy, keyed by `Meta.name` (falling back to the class name). Generated Python already emits real class inheritance (`class Car(Vehicle):`), so inherited fields and metadata flow automatically. | + +`xsi:type` is resolved as a QName using namespace declarations in scope at +the element. The attribute itself must be bound to the XML Schema Instance +namespace. A type with the same local name in a different namespace does not +select a registered variant. + +--- + +## 📥 Parse behavior + +Dispatch is applied at **every frame creation point**: the document root +(`Start` and `Empty`), nested elements, list items, and streamed +`iterparse` records. + +- `xsi:type` names a registered derivation → the element is parsed with the + concrete schema, so **base fields and concrete fields both survive**. +- An abstract declaration without `xsi:type` is rejected. +- `xsi:type` names an unknown type on an **abstract** base → a loud + `ValueError` listing the known derivations: + ``` + xsi:type="t:Plane" does not match any known derivation of 'Vehicle' (known: Car, Truck) + ``` +- An abstract type with **no registered derivations** fails with an + explicit escape-hatch message telling you to deserialize the concrete + type directly. +- Everything else is **tolerant**: non-abstract types without derivations + never scan for `xsi:type` at all (zero-overhead fast path), so a plain + content attribute literally named `type` is never mistaken for a + selector; unknown selectors on non-abstract types parse as declared. + +--- + +## 📤 Serialize behavior + +When a value is a record built from a **registered variant** of the +declared schema, the serializer writes the concrete type's fields and +re-emits the selector (`xsi:type="t:Car"`), declaring the XML Schema +Instance namespace at the root automatically. + +Python has two entry points: + +```python +# Serialize against the concrete type (default): the element is . +polyxml.serialize(car) + +# Serialize against the declared base: the element stays +# and xsi:type="Car" is re-emitted — a faithful wire round trip. +polyxml.serialize(car, target_type=Vehicle) +``` + +### Round trip + +```python +from dataclasses import dataclass, field +import polyxml + +@dataclass +class Vehicle: + class Meta: + name = "Vehicle" + abstract = True + id: str = field(metadata={"type": "Element", "name": "id"}) + +@dataclass +class Car(Vehicle): + class Meta: + name = "Car" + doors: int = field(metadata={"type": "Element", "name": "doors"}) + +@dataclass +class Fleet: + class Meta: + name = "Fleet" + vehicle: Vehicle = field(metadata={"type": "Element", "name": "vehicle"}) + +xml = b'V14' + +fleet = polyxml.deserialize(xml, Fleet) # fleet.vehicle is a Car +assert fleet.vehicle.doors == 4 # concrete field preserved +out = polyxml.serialize(fleet) # b'... xsi:type="Car" ...' +assert polyxml.deserialize(out, Fleet) == fleet +``` + +The same registry is reachable from pure Rust: + +```rust +let ir = polyxml::schema_parser::XsdParser::new().parse_str(xsd)?; +let schema = polyxml::schema::ModelSchema::from_ir(&ir, Some("Root"))?; +let value = polyxml::deserialize(xml, schema.clone())?; // dispatched Record +let out = polyxml::serialize_with_options("Root", &value, &schema, None, Some(true), None)?; +``` + +--- + +## ⚠️ Known limitations & escape hatches + +1. **Generated static codecs do not dispatch.** The compiled serde/quick-xml + codecs (Rust, Java, …) bind each element to one declared type; the Java + codec rejects `xsi:type` outright. *Escape hatch:* deserialize the + concrete type directly, or route polymorphic payloads through the + dynamic runtime (`polyxml::deserialize`, `polyxml.deserialize`). +2. **`polyxml-js` is scalar-flat today.** Its schema binding exposes only + scalar fields, so complex-typed (and therefore polymorphic) fields are + unreachable from JS yet — the dispatch itself lives in the shared core, + so no binding change will be needed when nested schemas land. +3. **JSON transcoding drops the selector.** `xml_to_json` keeps every + concrete field (it iterates the record's own schema), but JSON carries + no `xsi:type` marker, so JSON → XML cannot recover the wire selector. +4. **Discovery timing (Python).** Subclass discovery runs when the base + class's schema is first created. Define/import concrete subclasses + before the first `deserialize` call — ordinary module import order + already guarantees this. +5. **Unknown derivations fail loudly.** An `xsi:type` naming a type the + registry doesn't hold is an error (never silent truncation). *Escape + hatch:* import the module that defines the missing subclass, or + deserialize with the concrete type. + +Regression coverage lives in +`crates/polyxml-core/tests/test_issue53_xsi_type.rs` (Rust, 7 cases) and +`crates/polyxml-python/tests/test_xsi_type.py` (Python, 11 cases). diff --git a/docs/guides/rust.md b/docs/guides/rust.md index 7be8f6b5..b13a3b51 100644 --- a/docs/guides/rust.md +++ b/docs/guides/rust.md @@ -358,12 +358,12 @@ let restored_xml = json_to_xml( --- -## 9. Zero-Copy Binary Wire Format Serialization via rkyv (`--rkyv`) +## 9. Zero-Copy Binary Wire Format Serialization via rkyv (`--feature rkyv`) For inter-process communication (IPC), shared memory, network wire protocols, and high-frequency messaging where even JSON and binary serialization overhead is unacceptable, PolyXML supports zero-copy deserialization via **`rkyv`**: ```bash -polyxml generate --lang rust --rkyv --out ./src/generated schema.xsd +polyxml generate --lang rust --feature rkyv --out ./src/generated schema.xsd ``` This generates `#[cfg_attr(feature = "rkyv", ...)]` attributes on structs, enums, and choices: diff --git a/docs/index.md b/docs/index.md index 84874225..6def2bb9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -127,5 +127,6 @@ Explore complete, production-ready example repositories showcasing PolyXML in mi - Check out the [5-Minute Multi-Language Quickstart](quickstart.md) to see PolyXML in action. - Read about our [XSD-to-Code Generator & CLI Toolchain](guides/compiler.md). +- Learn how [Polymorphic Types & `xsi:type` Dispatch](guides/polymorphism.md) work at runtime. - Read about our [Architecture & Streaming Design](architecture.md). - Explore [Performance & Benchmarks](benchmarks.md). diff --git a/docs/why-polyxml.md b/docs/why-polyxml.md index b445738b..b5bbd44a 100644 --- a/docs/why-polyxml.md +++ b/docs/why-polyxml.md @@ -51,7 +51,7 @@ PolyXML eliminates intermediate DOM allocations entirely: ### 3. Modern Language Idioms (2024–2026) vs. 20-Year-Old Code Generation Most legacy compilers were architected during the Java 5 / C++98 era. They generate sprawling boilerplate: -- **Immutable by default, mutable on request**: PolyXML generates immutable Java 21+ `record` types and `sealed interface` choice models that support compiler-enforced pattern matching without default branches. When a legacy framework requires JavaBeans, `--style pojo --builder` emits no-arg classes with getters/setters and fluent builders instead — same facets, same codecs. +- **Immutable by default, mutable on request**: PolyXML generates immutable Java 21+ `record` types and `sealed interface` choice models that support compiler-enforced pattern matching without default branches. When a legacy framework requires JavaBeans, `--style pojo --feature builder` emits no-arg classes with getters/setters and fluent builders instead — same facets, same codecs. - **No more raw pointers or Xerces**: PolyXML generates clean C++20 value types, `std::variant`, and C++20 concepts with zero external runtime dependencies. - **No more untyped Python bags**: PolyXML generates `@dataclass(slots=True, kw_only=True)` and Pydantic v2 models leveraging Python 3.12 PEP 695 type aliases (`type Sku = ...`) and PEP 604 union syntax (`TypeA | TypeB`). @@ -168,7 +168,7 @@ Across more than 600 official test groups from Sun Microsystems, Microsoft, and | If you are using... | PolyXML gives you... | | :--- | :--- | -| **JAXB / `xjc` in Java** | Immutable Java 21+ records, sealed interface choices, zero reflection overhead, and Project Panama FFI — or drop-in JavaBeans with fluent builders and zero-reflection StAX codecs (`--style pojo --builder --codec direct`) for existing codebases. | +| **JAXB / `xjc` in Java** | Immutable Java 21+ records, sealed interface choices, zero reflection overhead, and Project Panama FFI — or drop-in JavaBeans with fluent builders and zero-reflection StAX codecs (`--style pojo --feature builder,direct-codec`) for existing codebases. | | **CodeSynthesis in C++** | Modern C++20 value types, `std::variant`, zero Apache Xerces dependency, zero UTF-16 transcoding overhead, and a permissive MIT license. | | **`xsdata` in Python** | **10x faster** XML parsing, **23.5x faster** XML serialization, **9.5x faster** native JSON, 100% drop-in replacement (`JsonSerializer`, `JsonParser`), and direct C/Rust transcoding (`xml_to_json`, `json_to_xml`). | | **`xsd-parser` in Rust** | A battle-tested compiler that doesn't panic on complex schemas, with automatic Tarjan `Box` cycle breaks, inherent streaming XML codecs, and native `.to_json_string()` codecs. | diff --git a/scripts/gen_tag_dispatch_fixtures.py b/scripts/gen_tag_dispatch_fixtures.py new file mode 100755 index 00000000..8efb51ef --- /dev/null +++ b/scripts/gen_tag_dispatch_fixtures.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +"""Generate benches/support/tag_dispatch_fixtures.rs (issue #49). + +The Criterion suite in benches/tag_dispatch.rs compares three tag-dispatch +strategies (string `match`, runtime HashMap, compile-time phf::Map) at the +schema scale tiers defined in the issue. The per-tier tag lists and match +functions are too large to hand-maintain, so they are generated here with a +fixed seed and committed alongside the bench. + +Usage: + python3 scripts/gen_tag_dispatch_fixtures.py +""" + +from __future__ import annotations + +import random +from pathlib import Path + +OUT = ( + Path(__file__).resolve().parents[1] + / "crates" + / "polyxml-core" + / "benches" + / "support" + / "tag_dispatch_fixtures.rs" +) + +# Issue #49 scale tiers: small (10-25), medium (80-150), large (500-1500). +# Both ends of the large range are sampled so the match->phf crossover (if any) +# is measured rather than extrapolated. +TIERS = [16, 120, 600, 1500] +SEED = 490490 + +# Realistic enterprise-tag vocabulary; lengths ~12-45 bytes so LLVM's +# length-bucketed memcmp chains are not artificially favored. +WORDS = [ + "Settlement", + "Instruction", + "Party", + "Account", + "Amount", + "Currency", + "ValueDate", + "Reference", + "Status", + "Identification", + "Transmission", + "Reconciliation", + "Clearing", + "Margin", + "Collateral", + "Beneficiary", + "Remittance", + "Routing", + "Authentication", + "Sequence", + "Version", + "Channel", + "Priority", + "Category", + "Adjustment", + "Position", + "Statement", + "Transaction", +] +SUFFIXES = [ + "", + "", + "Code", + "Type", + "Flag", + "Indicator", + "Date", + "Id", + "Count", + "1", + "2", + "Sub", + "Report", +] + + +def make_tags(n: int, seed: int) -> list[str]: + rng = random.Random(seed) + # Two-word composition keeps the space (28*28*13 = 10k+) well above the + # largest tier; the guard turns any future space shortfall into a clear + # error instead of an infinite loop. + attempts = 0 + tags: set[str] = set() + while len(tags) < n: + tags.add(rng.choice(WORDS) + rng.choice(WORDS) + rng.choice(SUFFIXES)) + attempts += 1 + if attempts > n * 100: + raise RuntimeError(f"tag space too small for tier {n} ({len(tags)} unique)") + return sorted(tags) + + +def emit(tier: int, tags: list[str], out: list[str]) -> None: + n = len(tags) + out.append(f"// --- Tier {n} ({'small' if n <= 25 else 'medium' if n <= 150 else 'large'}) ---") + out.append(f"pub const TAGS_{n}: [&str; {n}] = [") + for t in tags: + out.append(f' "{t}",') + out.append("];") + out.append("") + out.append(f"pub fn match_dispatch_{n}(tag: &str) -> Option {{") + out.append(" match tag {") + for i, t in enumerate(tags): + out.append(f' "{t}" => Some({i}),') + out.append(" _ => None,") + out.append(" }") + out.append("}") + out.append("") + out.append(f"pub static PHF_{n}: phf::Map<&'static str, u32> = phf::phf_map! {{") + for i, t in enumerate(tags): + out.append(f' "{t}" => {i}u32,') + out.append("};") + out.append("") + + +def main() -> None: + header = [ + "// GENERATED by scripts/gen_tag_dispatch_fixtures.py — do not edit.", + "// Tag lists, string-match baselines, and phf tables for the issue #49", + "// tag_dispatch Criterion bench. Regenerate with:", + "// python3 scripts/gen_tag_dispatch_fixtures.py", + "", + "#![allow(clippy::all)]", + "", + ] + out = list(header) + for tier in TIERS: + emit(tier, make_tags(tier, SEED + tier), out) + OUT.parent.mkdir(parents=True, exist_ok=True) + OUT.write_text("\n".join(out)) + print(f"wrote {OUT} ({len(out)} lines)") + + +if __name__ == "__main__": + main() diff --git a/scripts/memcap.sh b/scripts/memcap.sh new file mode 100755 index 00000000..06cd4280 --- /dev/null +++ b/scripts/memcap.sh @@ -0,0 +1,140 @@ +#!/bin/sh +# memcap.sh - run a command under a hard RAM cap. +# +# Benchmarks and codegen smoke tests build large generated crates; on small +# dev machines (e.g. a 7.7-GiB WSL box) an uncapped `cargo build --release` +# can exhaust RAM, drive the host into swap-thrash and freeze it. This +# wrapper caps the command's whole process tree so an over-limit build is +# OOM-killed *inside its own cgroup* (exit 137) while the host stays up. +# +# Usage: scripts/memcap.sh [args...] +# +# Environment: +# POLYXML_MEMCAP_PCT Percentage of currently-available RAM the command +# may use (default: 60). Integer 1-100. +# POLYXML_MEMCAP_BACKEND auto (default) | systemd | ulimit | none +# auto - Linux with a systemd --user manager: +# kernel-enforced MemoryMax (preferred); +# otherwise fall back to ulimit. +# systemd - require a systemd --user scope or fail +# (useful for asserting the hard cap). +# ulimit - force the address-space fallback +# (no systemd, containers, macOS). +# none - run uncapped. +# POLYXML_MEMCAP_DISABLE Set to 1 to run uncapped (same as backend=none). +# POLYXML_MEMCAP_LEVEL Nesting counter; set automatically so wrappers +# (e.g. perf_stat.sh) can re-enter safely without +# double-capping. +# +# Notes: +# * The percentage is recomputed against *available* RAM at each invocation. +# * The systemd path sets MemorySwapMax=0 so capped work can never page the +# host into thrash: hitting the cap fails fast (kernel OOM kill) instead +# of grinding to a halt. +# * The ulimit path caps address space (RLIMIT_AS), which is best-effort: +# it reliably stops runaway allocations but counts reserved -not- resident +# pages, so it may be stricter than the systemd cap. +set -eu + +usage() { + echo "usage: $0 [args...]" >&2 + exit 2 +} + +[ "$#" -gt 0 ] || usage + +pct="${POLYXML_MEMCAP_PCT:-60}" +backend="${POLYXML_MEMCAP_BACKEND:-auto}" + +case "$pct" in + '' | *[!0-9]*) + echo "memcap: POLYXML_MEMCAP_PCT must be an integer (got '$pct')" >&2 + exit 2 + ;; +esac +if [ "$pct" -lt 1 ] || [ "$pct" -gt 100 ]; then + echo "memcap: POLYXML_MEMCAP_PCT must be 1-100 (got '$pct')" >&2 + exit 2 +fi + +# Mark ourselves as "inside a cap" BEFORE any exec-out below: wrapped +# scripts re-exec through memcap when POLYXML_MEMCAP_LEVEL is unset, so the +# disable/fallback paths must still set it or they would loop forever. +level=$((${POLYXML_MEMCAP_LEVEL:-0} + 1)) +export POLYXML_MEMCAP_LEVEL="$level" + +if [ "${POLYXML_MEMCAP_DISABLE:-0}" = "1" ] || [ "$backend" = "none" ]; then + exec "$@" +fi + +# --- How much RAM may we take? ------------------------------------------- +os="$(uname -s)" +avail_kb="" + +if [ "$os" = "Linux" ] && [ -r /proc/meminfo ]; then + avail_kb="$(awk '/^MemAvailable:/ {print $2; exit}' /proc/meminfo)" +fi +if [ -z "$avail_kb" ] && [ "$os" = "Darwin" ]; then + # Best effort: free + inactive + speculative pages (macOS reclaims the + # latter two on demand, so they are effectively available). + page_size="$(sysctl -n hw.pagesize 2>/dev/null || echo 4096)" + avail_kb="$(vm_stat 2>/dev/null | awk -v ps="$page_size" ' + /Pages free:/ {gsub(/\./, "", $3); free = $3} + /Pages inactive:/ {gsub(/\./, "", $3); inact = $3} + /Pages speculative:/{gsub(/\./, "", $3); spec = $3} + END {if (free == "") exit 1; printf "%d", (free + inact + spec) * ps / 1024}')" || avail_kb="" +fi +if [ -z "$avail_kb" ] || [ "$avail_kb" -le 0 ] 2>/dev/null; then + # Last resort: total RAM. + if [ "$os" = "Darwin" ]; then + total_bytes="$(sysctl -n hw.memsize 2>/dev/null || echo 0)" + avail_kb=$((total_bytes / 1024)) + elif [ -r /proc/meminfo ]; then + avail_kb="$(awk '/^MemTotal:/ {print $2; exit}' /proc/meminfo)" + fi +fi +if [ -z "$avail_kb" ] || [ "$avail_kb" -le 0 ] 2>/dev/null; then + echo "memcap: cannot determine available memory; running uncapped" >&2 + exec "$@" +fi + +cap_kb=$((avail_kb * pct / 100)) +cap_mib=$((cap_kb / 1024)) +avail_mib=$((avail_kb / 1024)) + +# --- Backend: kernel-enforced cgroup (preferred) -------------------------- +use_systemd=0 +if [ "$backend" = "systemd" ] || + { [ "$backend" = "auto" ] && [ "$os" = "Linux" ] && command -v systemd-run >/dev/null 2>&1; }; then + # Probe the *user* manager once. Works in systemd distros and CI images + # with a session bus; fails in bare containers/chroots -> fall back. + if systemd-run --user --scope --quiet -p MemoryMax=64M -- /bin/true \ + /dev/null 2>&1; then + use_systemd=1 + elif [ "$backend" = "systemd" ]; then + echo "memcap: systemd --user scope unavailable but POLYXML_MEMCAP_BACKEND=systemd was requested" >&2 + exit 2 + fi +fi + +if [ "$use_systemd" = 1 ]; then + echo "memcap: <=${cap_mib} MiB (${pct}% of ${avail_mib} MiB available) [systemd MemoryMax, swap off]" >&2 + exec systemd-run --user --scope --quiet \ + --setenv=POLYXML_MEMCAP_LEVEL="$level" \ + -p "MemoryMax=${cap_kb}K" \ + -p MemorySwapMax=0 \ + -- "$@" +fi + +# --- Fallback: RLIMIT_AS -------------------------------------------------- +if [ "$backend" = "auto" ] || [ "$backend" = "ulimit" ]; then + echo "memcap: <=${cap_mib} MiB (${pct}% of ${avail_mib} MiB available) [ulimit -v fallback]" >&2 + if ulimit -v "$cap_kb" 2>/dev/null; then + exec "$@" + fi + echo "memcap: could not lower RLIMIT_AS (hard limit too low?); running uncapped" >&2 + exec "$@" +fi + +echo "memcap: unknown POLYXML_MEMCAP_BACKEND '$backend'" >&2 +exit 2 diff --git a/scripts/perf_stat.c b/scripts/perf_stat.c new file mode 100644 index 00000000..a1b8cfac --- /dev/null +++ b/scripts/perf_stat.c @@ -0,0 +1,150 @@ +/* perf_stat: perf_event_open-based hardware counter harness for hosts without + * the `perf` binary (issue #49: "Profile branch misses and IPC using perf + * stat"). Counts for a command: cycles, instructions, branches, branch-misses, + * and L1-icache-load-misses — the same events + * `perf stat -e cycles,instructions,branches,branch-misses,L1-icache-load-misses` + * would report. Userspace-only (perf_event_paranoid >= 2 compatible). + * + * Events are attached directly to the forked child pid (perf-stat style) + * rather than via inherit=1: inherited child counts do not reliably + * propagate to the parent's read() on this kernel, which silently reported + * only parent noise (~2.5M cycles regardless of workload). + * + * Build & run: + * gcc -O2 -o /tmp/perf_stat scripts/perf_stat.c + * /tmp/perf_stat -- ./target/release/deps/tag_dispatch- + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static long open_ev(pid_t pid, __u64 config, __u32 type) { + struct perf_event_attr pe; + memset(&pe, 0, sizeof(pe)); + pe.type = type; + pe.size = sizeof(pe); + pe.config = config; + pe.disabled = 1; /* enabled together right before child release */ + pe.exclude_kernel = 1; /* required at perf_event_paranoid >= 2 */ + return syscall(__NR_perf_event_open, &pe, pid, -1, -1, 0); +} + +struct ev { + const char *name; + long fd; +}; + +int main(int argc, char **argv) { + if (argc < 3 || strcmp(argv[1], "--") != 0) { + fprintf(stderr, "usage: perf_stat -- [args...]\n"); + return 2; + } + + int sync[2]; + if (pipe(sync) != 0) { + perror("pipe"); + return 4; + } + + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + return 4; + } + if (pid == 0) { + /* Block until the parent has the counters armed on this pid. */ + close(sync[1]); + char c; + if (read(sync[0], &c, 1) != 1) { + _exit(4); + } + close(sync[0]); + execvp(argv[2], &argv[2]); + perror("exec"); + _exit(127); + } + + close(sync[0]); + + const __u64 l1i_read_miss = + (__u64)PERF_COUNT_HW_CACHE_L1I | + ((__u64)PERF_COUNT_HW_CACHE_OP_READ << 8) | + ((__u64)PERF_COUNT_HW_CACHE_RESULT_MISS << 16); + + struct ev evs[] = { + {"cycles", open_ev(pid, PERF_COUNT_HW_CPU_CYCLES, PERF_TYPE_HARDWARE)}, + {"instructions", open_ev(pid, PERF_COUNT_HW_INSTRUCTIONS, PERF_TYPE_HARDWARE)}, + {"branches", open_ev(pid, PERF_COUNT_HW_BRANCH_INSTRUCTIONS, PERF_TYPE_HARDWARE)}, + {"branch-misses", open_ev(pid, PERF_COUNT_HW_BRANCH_MISSES, PERF_TYPE_HARDWARE)}, + {"L1-icache-load-misses", open_ev(pid, l1i_read_miss, PERF_TYPE_HW_CACHE)}, + }; + const int n = (int)(sizeof(evs) / sizeof(evs[0])); + + int avail = 0; + for (int i = 0; i < n; i++) { + if (evs[i].fd < 0) { + fprintf(stderr, "perf_stat: %s unavailable (%s)\n", evs[i].name, + strerror(errno)); + } else { + avail++; + ioctl(evs[i].fd, PERF_EVENT_IOC_ENABLE, 0); + } + } + if (avail == 0) { + fprintf(stderr, "perf_stat: no PMU counters available\n"); + /* Don't strand the child. */ + (void)!write(sync[1], "x", 1); + close(sync[1]); + int st = 0; + waitpid(pid, &st, 0); + return 3; + } + + /* Release the child: counters are attached and enabled. */ + if (write(sync[1], "x", 1) != 1) { + perror("write"); + return 4; + } + close(sync[1]); + + int status = 0; + waitpid(pid, &status, 0); + for (int i = 0; i < n; i++) { + if (evs[i].fd >= 0) { + ioctl(evs[i].fd, PERF_EVENT_IOC_DISABLE, 0); + } + } + + unsigned long long vals[5] = {0, 0, 0, 0, 0}; + for (int i = 0; i < n; i++) { + if (evs[i].fd >= 0 && + read(evs[i].fd, &vals[i], sizeof(vals[i])) != (ssize_t)sizeof(vals[i])) { + vals[i] = 0; + } + } + + fprintf(stderr, "\n"); + for (int i = 0; i < n; i++) { + if (evs[i].fd >= 0) { + fprintf(stderr, "%18llu %s\n", vals[i], evs[i].name); + } + } + if (vals[0] > 0) { + fprintf(stderr, "\nIPC: %.3f", (double)vals[1] / (double)vals[0]); + if (vals[2] > 0) { + fprintf(stderr, " branch-miss rate: %.4f%%", + 100.0 * (double)vals[3] / (double)vals[2]); + } + fprintf(stderr, "\n"); + } + + return WIFEXITED(status) ? WEXITSTATUS(status) : 128 + WTERMSIG(status); +} diff --git a/scripts/perf_stat.sh b/scripts/perf_stat.sh new file mode 100755 index 00000000..2717d722 --- /dev/null +++ b/scripts/perf_stat.sh @@ -0,0 +1,17 @@ +#!/bin/sh +# Compile-and-run wrapper for scripts/perf_stat.c (see that file for usage). +# Usage: scripts/perf_stat.sh -- [args...] +set -eu +here="$(cd "$(dirname "$0")" && pwd)" +# Run the countered command under a RAM cap (scripts/memcap.sh) so runaway +# workloads can't freeze small hosts; the LEVEL guard makes the re-exec +# idempotent. POLYXML_MEMCAP_DISABLE=1 opts out. Note: pass --bench to +# Criterion binaries, otherwise they run test mode (~0.2s) and the counters +# only measure process startup. +if [ -z "${POLYXML_MEMCAP_LEVEL:-}" ]; then + exec "$here/memcap.sh" "$here/$(basename "$0")" "$@" +fi +bin="${TMPDIR:-/tmp}/polyxml_perf_stat.$$" +trap 'rm -f "$bin"' EXIT +gcc -O2 -o "$bin" "$here/perf_stat.c" +exec "$bin" "$@" diff --git a/scripts/test_codegen.sh b/scripts/test_codegen.sh index d26895c9..72a32d50 100755 --- a/scripts/test_codegen.sh +++ b/scripts/test_codegen.sh @@ -26,7 +26,8 @@ cargo test -p polyxml \ --test test_java_codegen \ --test test_ts_codegen \ --test test_go_codegen \ - --test test_csharp_codegen + --test test_csharp_codegen \ + --test test_pattern_codegen echo -e "${BLUE}==> [3/3] Running PolyXML CLI code generation & manifest integration tests...${NC}" cargo test -p polyxml-cli --test test_cli diff --git a/scripts/verify_codegen.sh b/scripts/verify_codegen.sh index 503da0aa..ff5d35ce 100755 --- a/scripts/verify_codegen.sh +++ b/scripts/verify_codegen.sh @@ -57,10 +57,11 @@ EOF echo -e "${BLUE}==> Testing code generation across all 7 target ecosystems...${NC}" -# 1. Rust (zero-copy + rkyv) -echo -n " • Rust (--zero-copy --rkyv)... " -"${POLYXML_BIN}" generate --lang rust --zero-copy --rkyv --out "${TMP_DIR}/rs" "${SCHEMA_FILE}" >/dev/null +# 1. Rust (zero-copy + rkyv + perfect-hash dispatch) +echo -n " • Rust (--feature zero-copy,rkyv,phf)... " +"${POLYXML_BIN}" generate --lang rust --feature zero-copy,rkyv,phf --out "${TMP_DIR}/rs" "${SCHEMA_FILE}" >/dev/null test -f "${TMP_DIR}/rs/test_schema.rs" +grep -q "ELEMENT_DISPATCH" "${TMP_DIR}/rs/test_schema.rs" echo -e "${GREEN}OK${NC}" # 2. Python (dataclass & pydantic) @@ -98,7 +99,7 @@ echo -e "${GREEN}OK${NC}" # Mutable Java models and direct codecs. echo -n " • Java (POJO + builder + direct codec)... " -"${POLYXML_BIN}" generate --lang java --style pojo --builder --codec direct --out "${TMP_DIR}/java_pojo" "${SCHEMA_FILE}" >/dev/null +"${POLYXML_BIN}" generate --lang java --style pojo --feature builder,direct-codec --out "${TMP_DIR}/java_pojo" "${SCHEMA_FILE}" >/dev/null test -f "${TMP_DIR}/java_pojo/TreeNodeCodec.java" if command -v javac >/dev/null 2>&1; then javac -d "${TMP_DIR}/java_classes" "${TMP_DIR}/java_pojo/"*.java @@ -144,13 +145,13 @@ test -f "${TMP_DIR}/cs_class/TestSchema.cs" echo -e "${GREEN}OK${NC}" echo -n " • C# (record struct + source-gen)... " -"${POLYXML_BIN}" generate --lang csharp --record-kind struct --source-gen --namespace Smoke --out "${TMP_DIR}/cs_struct" "${SCHEMA_FILE}" >/dev/null +"${POLYXML_BIN}" generate --lang csharp --style record-struct --backend source-gen --namespace Smoke --out "${TMP_DIR}/cs_struct" "${SCHEMA_FILE}" >/dev/null test -f "${TMP_DIR}/cs_struct/TestSchema.cs" echo -e "${GREEN}OK${NC}" echo -n " • C# (mutable class + source-gen)... " -"${POLYXML_BIN}" generate --lang csharp --style class --source-gen --out "${TMP_DIR}/cs_class" "${SCHEMA_FILE}" >/dev/null +"${POLYXML_BIN}" generate --lang csharp --style class --backend source-gen --out "${TMP_DIR}/cs_class" "${SCHEMA_FILE}" >/dev/null test -f "${TMP_DIR}/cs_class/TestSchema.cs" echo -e "${GREEN}OK${NC}" echo -e "${GREEN}✨ Multi-target smoke verification passed completely across all 7 ecosystems!${NC}"