From cbc10b192fd805b07b059b801960824df0a37b50 Mon Sep 17 00:00:00 2001 From: Arthur LE MOIGNE Date: Fri, 12 Jan 2024 16:08:56 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Generate=20message=20from=20FIX=204?= =?UTF-8?q?x=20XML=20spec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Content: - Generator code structure. - Type system with trait. - Getter & setters for basic fields. - Constructor with required params. - Group generation. - Components management. - Constructor from existing message. - Sub package for all FIX 4XX specs. - Unit tests ❤️ + CI. - Polish the code and update doc 😁. Extra stuff to implements in new PR: - Enum with all possible messages + MessageCracker function like. - Explain why FIX5.0 is not generated in this crate and how to generate it. --- .github/workflows/ci.yml | 3 + Cargo.lock | 81 + Cargo.toml | 12 +- README.md | 8 + doc/ABOUT.md | 15 +- quickfix-ffi/Cargo.toml | 2 +- quickfix-msg-gen/Cargo.toml | 18 + quickfix-msg-gen/src/converter.rs | 58 + quickfix-msg-gen/src/lib.rs | 759 +++ quickfix-msg-gen/src/model.rs | 43 + quickfix-msg-gen/tests/test_generator.rs | 41 + quickfix-msg40/Cargo.toml | 17 + quickfix-msg40/build.rs | 17 + quickfix-msg40/src/FIX40.xml | 862 +++ quickfix-msg40/src/lib.rs | 2 + quickfix-msg40/tests/test_usage.rs | 156 + quickfix-msg41/Cargo.toml | 17 + quickfix-msg41/build.rs | 17 + quickfix-msg41/src/FIX41.xml | 1282 +++++ quickfix-msg41/src/lib.rs | 2 + quickfix-msg42/Cargo.toml | 17 + quickfix-msg42/build.rs | 17 + quickfix-msg42/src/FIX42.xml | 2743 +++++++++ quickfix-msg42/src/lib.rs | 2 + quickfix-msg43/Cargo.toml | 17 + quickfix-msg43/build.rs | 17 + quickfix-msg43/src/FIX43.xml | 4230 ++++++++++++++ quickfix-msg43/src/lib.rs | 2 + quickfix-msg44/Cargo.toml | 17 + quickfix-msg44/build.rs | 17 + quickfix-msg44/src/FIX44.xml | 6600 ++++++++++++++++++++++ quickfix-msg44/src/lib.rs | 2 + quickfix-spec-parser/Cargo.toml | 9 +- quickfix/src/lib.rs | 4 + 34 files changed, 17097 insertions(+), 9 deletions(-) create mode 100644 quickfix-msg-gen/Cargo.toml create mode 100644 quickfix-msg-gen/src/converter.rs create mode 100644 quickfix-msg-gen/src/lib.rs create mode 100644 quickfix-msg-gen/src/model.rs create mode 100644 quickfix-msg-gen/tests/test_generator.rs create mode 100644 quickfix-msg40/Cargo.toml create mode 100644 quickfix-msg40/build.rs create mode 100644 quickfix-msg40/src/FIX40.xml create mode 100644 quickfix-msg40/src/lib.rs create mode 100644 quickfix-msg40/tests/test_usage.rs create mode 100644 quickfix-msg41/Cargo.toml create mode 100644 quickfix-msg41/build.rs create mode 100644 quickfix-msg41/src/FIX41.xml create mode 100644 quickfix-msg41/src/lib.rs create mode 100644 quickfix-msg42/Cargo.toml create mode 100644 quickfix-msg42/build.rs create mode 100644 quickfix-msg42/src/FIX42.xml create mode 100644 quickfix-msg42/src/lib.rs create mode 100644 quickfix-msg43/Cargo.toml create mode 100644 quickfix-msg43/build.rs create mode 100644 quickfix-msg43/src/FIX43.xml create mode 100644 quickfix-msg43/src/lib.rs create mode 100644 quickfix-msg44/Cargo.toml create mode 100644 quickfix-msg44/build.rs create mode 100644 quickfix-msg44/src/FIX44.xml create mode 100644 quickfix-msg44/src/lib.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b2f6d2..da4b943 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,7 @@ jobs: submodules: true - run: rustup toolchain install 1.70.0 - run: rustup override set 1.70.0 + - run: rustup component add rustfmt - uses: Swatinem/rust-cache@v2 - run: rustc --version - name: Build debug @@ -40,6 +41,7 @@ jobs: submodules: true - run: rustup toolchain install 1.70.0 - run: rustup override set 1.70.0 + - run: rustup component add rustfmt - uses: Swatinem/rust-cache@v2 - name: Install dependencies run: | @@ -57,6 +59,7 @@ jobs: submodules: true - run: rustup toolchain install 1.70.0 - run: rustup override set 1.70.0 + - run: rustup component add rustfmt - uses: Swatinem/rust-cache@v2 - name: Install dependencies run: | diff --git a/Cargo.lock b/Cargo.lock index f2b60cf..74e7803 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,12 +36,36 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + [[package]] name = "fs_extra" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -102,6 +126,57 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "quickfix-msg-gen" +version = "0.1.0" +dependencies = [ + "bytes", + "convert_case", + "itertools", + "quickfix", + "quickfix-spec-parser", +] + +[[package]] +name = "quickfix-msg40" +version = "0.1.0" +dependencies = [ + "quickfix", + "quickfix-msg-gen", +] + +[[package]] +name = "quickfix-msg41" +version = "0.1.0" +dependencies = [ + "quickfix", + "quickfix-msg-gen", +] + +[[package]] +name = "quickfix-msg42" +version = "0.1.0" +dependencies = [ + "quickfix", + "quickfix-msg-gen", +] + +[[package]] +name = "quickfix-msg43" +version = "0.1.0" +dependencies = [ + "quickfix", + "quickfix-msg-gen", +] + +[[package]] +name = "quickfix-msg44" +version = "0.1.0" +dependencies = [ + "quickfix", + "quickfix-msg-gen", +] + [[package]] name = "quickfix-spec-parser" version = "0.1.0" @@ -157,6 +232,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index a389e8d..892481d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,13 @@ [workspace] resolver = "2" -members = ["quickfix-ffi", "quickfix-spec-parser", "quickfix"] +members = [ + "quickfix-ffi", + "quickfix-msg-gen", + "quickfix-spec-parser", + "quickfix", + "quickfix-msg40", + "quickfix-msg41", + "quickfix-msg42", + "quickfix-msg43", + "quickfix-msg44", +] diff --git a/README.md b/README.md index 785a873..181d37e 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ This project is an **unofficial** binding between [quickfix](https://github.com/ - Provide basic and safe API wrapper above [quickfix](https://github.com/quickfix/quickfix) library. - Run on any hardware and operating system supported by Rust Tier 1 (Windows 7+, MacOS 10.12+ & Linux). - Message decoding / encoding including run-time validation. +- Supports FIX versions 4x (version 5x can be build locally from XML spec file). +- Spec driven run-time message validation. +- Spec driven code generation of type-safe FIX messages, fields, and repeating groups. - Session state storage options: SQL, File, In Memory. - Logging options: stdout, stderr, [log](https://crates.io/crates/log) or any other crate if you implement your own trait. @@ -90,6 +93,10 @@ If you want to use it in your project, just add this to your `Cargo.toml` config quickfix = { git = "https://github.com/arthurlm/quickfix-rs.git" } ``` +**NOTE**: I am personally not using for now the generated message struct. +I know they works fine thanks to unit tests and can be used in production code. +Feedback on this part are welcomed ! + ## Build requirements Following package must be install to build the library: @@ -97,3 +104,4 @@ Following package must be install to build the library: - `cmake` - a C++ compiler (with C++17 support) - `rustup` / `rustc` / `cargo` (obviously 😉) +- `rustfmt` for auto generated messages from spec. diff --git a/doc/ABOUT.md b/doc/ABOUT.md index 18c364a..c706464 100644 --- a/doc/ABOUT.md +++ b/doc/ABOUT.md @@ -35,15 +35,18 @@ What I do **not** plan to bind from this crate: 4. Autotools build toolchain. Just use `cmake` once and for all ! - We are in 2023+ and not targeting OS from the 70s. + We are in 2023+ and not targeting OS from the 80s. -5. Struct to bind messages from XML spec. +5. FIX 5x messages generated code. - Most of the time, vendors / brokers have custom field that do not match auto-generated struct. - To me they are not relevant most of the time. + FIX 5x XML definition is a little bit weird ... + For example: + - In [MatchType](https://www.onixs.biz/fix-dictionary/5.0/tagNum_574.html) some tag is defined multiple times. + Generated enum are so inconsistent and cannot be safely generated. + - There is no header / trailer in spec files (see: [FIX50.xml](https://github.com/quickfix/quickfix/blob/0b88788710b6b9767440cd430bf24c6b6e2080a2/spec/FIX50.xml#L2)). + - There are probably other incompatibility but I stopped here ... - Moreover it is so simple to just create an Rust enum / struct that match your current model. - Having all this messaging generated stuff just make the code more complicated for most of the need. + You can edit XML spec to your need and create a package with desired spec locally. 6. All binding of `LogFactory`. diff --git a/quickfix-ffi/Cargo.toml b/quickfix-ffi/Cargo.toml index c6b5cc0..0e85b50 100644 --- a/quickfix-ffi/Cargo.toml +++ b/quickfix-ffi/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/arthurlm/quickfix-rs" license = "MIT" keywords = ["quickfix", "fix-protocol", "finance"] categories = ["external-ffi-bindings"] -rust-version = "1.65.0" +rust-version = "1.70.0" exclude = ["examples"] [dependencies] diff --git a/quickfix-msg-gen/Cargo.toml b/quickfix-msg-gen/Cargo.toml new file mode 100644 index 0000000..88ca4e8 --- /dev/null +++ b/quickfix-msg-gen/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "quickfix-msg-gen" +version = "0.1.0" +authors = ["Arthur LE MOIGNE"] +edition = "2021" +description = "FIX code generator from XML spec file" +repository = "https://github.com/arthurlm/quickfix-rs" +license = "MIT" +keywords = ["quickfix", "fix-protocol", "finance", "code-generator"] +categories = ["development-tools::build-utils"] +rust-version = "1.70.0" + +[dependencies] +quickfix-spec-parser = { path = "../quickfix-spec-parser", version = "0.1.0" } +quickfix = { path = "../quickfix", version = "0.1.0" } +convert_case = "0.6.0" +itertools = "0.12.0" +bytes = "1.5.0" diff --git a/quickfix-msg-gen/src/converter.rs b/quickfix-msg-gen/src/converter.rs new file mode 100644 index 0000000..ec9b50f --- /dev/null +++ b/quickfix-msg-gen/src/converter.rs @@ -0,0 +1,58 @@ +use std::collections::HashSet; + +use quickfix_spec_parser::{FieldValue, FixSpec, Message}; + +use crate::{FixCodeSpec, MessageField, MessageGroup, MessageSpec, SubComponent}; + +pub fn convert_spec(src: &FixSpec) -> FixCodeSpec { + FixCodeSpec { + field_specs: src.field_specs.clone(), + headers: convert_field_value_list(src, &src.headers), + trailers: convert_field_value_list(src, &src.trailers), + messages: convert_messages(src, &src.messages), + } +} + +fn convert_field_value_list(spec: &FixSpec, values: &[FieldValue]) -> Vec { + let mut output = Vec::with_capacity(values.len()); + + // Convert recursively `FieldValue` to `SubComponent`. + for value in values { + match value { + FieldValue::Field(x) => output.push(SubComponent::Field(MessageField { + name: x.name.clone(), + required: x.required, + })), + FieldValue::Group(x) => output.push(SubComponent::Group(MessageGroup { + name: x.name.clone(), + components: convert_field_value_list(spec, &x.values), + })), + FieldValue::Component(component) => { + let component_spec = spec + .component_specs + .iter() + .find(|x| x.name == component.name) + .expect("Cannot find component"); + + output.extend(convert_field_value_list(spec, &component_spec.values)); + } + } + } + + // Remove duplicate: example FIX4.3 -> RegistrationInstructions -> OwnershipType. + let mut uniques = HashSet::new(); + output.retain(|x| uniques.insert(x.name().to_string())); + + output +} + +fn convert_messages(spec: &FixSpec, messages: &[Message]) -> Vec { + messages + .iter() + .map(|message| MessageSpec { + name: message.name.clone(), + msg_type: message.msg_type.clone(), + components: convert_field_value_list(spec, &message.values), + }) + .collect() +} diff --git a/quickfix-msg-gen/src/lib.rs b/quickfix-msg-gen/src/lib.rs new file mode 100644 index 0000000..a6ffb38 --- /dev/null +++ b/quickfix-msg-gen/src/lib.rs @@ -0,0 +1,759 @@ +use std::{ + fs, + io::{self, Write}, + path::Path, + process::{self, Stdio}, +}; + +use crate::converter::convert_spec; +use crate::model::*; + +use bytes::Bytes; +use convert_case::{Case, Casing}; +use itertools::Itertools; + +mod converter; +mod model; + +use quickfix_spec_parser::{FieldSpec, FieldType}; + +trait FieldAccessorGenerator { + fn getter_prefix_text(&self) -> &'static str; + fn setter_prefix_text(&self) -> &'static str; + fn caller_suffix_text(&self) -> &'static str; +} + +pub fn generate, D: AsRef>( + src: S, + dst: D, + begin_string: &str, +) -> io::Result<()> { + let spec_data = fs::read(src)?; + let spec = quickfix_spec_parser::parse_spec(&spec_data).expect("Cannot parse FIX spec"); + let spec = convert_spec(&spec); + + // Generate the code. + println!("Generating code ..."); + let mut output = String::with_capacity(5 << 20); // 5Mo initial buffer + generate_root(&mut output, begin_string); + generate_field_ids(&mut output, &spec.field_specs); + generate_field_types(&mut output, &spec.field_specs); + generate_headers(&mut output, &spec.headers); + generate_trailers(&mut output, &spec.trailers); + generate_messages(&mut output, &spec.messages); + + // Spawn a rustfmt daemon. + let mut rustfmt = process::Command::new("rustfmt") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn()?; + + // Send the code to rustfmt. + println!("Formatting code ..."); + let mut rustfmt_in = rustfmt.stdin.take().expect("Fail to take rustfmt stdin"); + rustfmt_in.write_all(output.as_bytes())?; + rustfmt_in.flush()?; + drop(rustfmt_in); // Avoid infinite waiting ! + + // Check output and write result. + let rustfmt_out = rustfmt.wait_with_output()?; + if !rustfmt_out.status.success() { + println!("rustfmt stdout ======================="); + println!("{:?}", Bytes::from(rustfmt_out.stdout)); + println!("rustfmt stderr ======================="); + println!("{:?}", Bytes::from(rustfmt_out.stderr)); + + panic!("Fail to run rustfmt"); + } + + // Write code to disk. + println!("Writing code to disk ..."); + fs::write(dst, rustfmt_out.stdout)?; + Ok(()) +} + +fn generate_root(output: &mut String, begin_string: &str) { + output.push_str(&format!( + r#" #[allow(unused_imports)] + use quickfix::*; + + pub const FIX_BEGIN_STRING: &str = "{begin_string}"; + + #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] + pub struct FixParseError; + + impl std::fmt::Display for FixParseError {{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{ + writeln!(f, "FIX parse error") + }} + }} + + impl std::error::Error for FixParseError {{}} + + "# + )) +} + +fn generate_field_ids(output: &mut String, field_specs: &[FieldSpec]) { + output.push_str("pub mod field_id {\n"); + + for field_spec in field_specs { + output.push_str(&format!( + "pub const {}: i32 = {};\n", + field_spec.name.to_case(Case::ScreamingSnake), + field_spec.number + )); + } + + output.push_str("} // field_id\n\n"); +} + +fn generate_field_types(output: &mut String, field_specs: &[FieldSpec]) { + output.push_str("pub mod field_types {\n"); + + for field_spec in field_specs { + if !field_spec.values.is_empty() { + match &field_spec.r#type { + FieldType::Int => { + generate_field_type_int_values(output, field_spec); + } + _ => { + generate_field_type_char_values(output, field_spec); + } + } + } else { + generate_field_type_alias(output, field_spec); + } + } + + output.push_str("} // field_types\n\n"); +} + +fn generate_field_type_int_values(output: &mut String, field_spec: &FieldSpec) { + assert!(!field_spec.values.is_empty()); + assert!(matches!(field_spec.r#type, FieldType::Int)); + + let enum_name = field_spec.name.as_str(); + + // Generate enum possible values. + output.push_str("#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\n"); + output.push_str(&format!("pub enum {enum_name} {{\n")); + for value in &field_spec.values { + output.push_str(&format!( + "{} = {},\n", + value.description.to_case(Case::UpperCamel), + value.value + )); + } + output.push_str("}\n\n"); + + generate_field_type_values(output, field_spec); +} + +fn generate_field_type_char_values(output: &mut String, field_spec: &FieldSpec) { + assert!(!field_spec.values.is_empty()); + + let enum_name = field_spec.name.as_str(); + + // Generate enum possible values. + output.push_str("#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\n"); + output.push_str(&format!("pub enum {enum_name} {{\n")); + for value in &field_spec.values { + output.push_str(&format!( + "{},\n", + value.description.to_case(Case::UpperCamel) + )); + } + output.push_str("}\n\n"); + + generate_field_type_values(output, field_spec); +} + +fn generate_field_type_values(output: &mut String, field_spec: &FieldSpec) { + assert!(!field_spec.values.is_empty()); + + let type_name = field_spec.name.as_str(); + + // Generate method helpers. + output.push_str(&format!( + r#" impl {type_name} {{ + #[inline(always)] + pub const fn from_const_bytes(s: &[u8]) -> Result {{ + match s {{ + "# + )); + for value in &field_spec.values { + output.push_str(&format!( + " b\"{}\" => Ok(Self::{}),\n", + value.value, + value.description.to_case(Case::UpperCamel), + )); + } + output.push_str( + r#" _ => Err(crate::FixParseError), + } + } + } + + "#, + ); + + // Generate `FromStr`. + output.push_str(&format!( + r#" impl std::str::FromStr for {type_name} {{ + type Err = crate::FixParseError; + fn from_str(s: &str) -> Result {{ + Self::from_const_bytes(s.as_bytes()) + }} + }} + + "# + )); + + // Generate `AsFixValue`. + output.push_str(&format!( + r#" impl quickfix::AsFixValue for {type_name} {{ + fn as_fix_value(&self) -> String {{ + match self {{ + "# + )); + for value in &field_spec.values { + output.push_str(&format!( + " Self::{} => \"{}\",\n", + value.description.to_case(Case::UpperCamel), + value.value, + )); + } + output.push_str( + r#" }.to_string() + } + } + + "#, + ); +} + +fn generate_field_type_alias(output: &mut String, field_spec: &FieldSpec) { + assert!(field_spec.values.is_empty()); + + let type_name = field_spec.name.as_str(); + let rust_type = match &field_spec.r#type { + FieldType::Int => "i64", + FieldType::Length => "u32", + FieldType::SequenceNumber => "u32", + FieldType::NumberInGroup => "i32", + FieldType::Boolean => "bool", + + FieldType::Float + | FieldType::Price + | FieldType::Amount + | FieldType::Quantity + | FieldType::PriceOffset + | FieldType::Percentage => "f64", + + FieldType::Char + | FieldType::Data + | FieldType::Time + | FieldType::Date + | FieldType::MonthYear + | FieldType::DayOfMonth + | FieldType::String + | FieldType::Currency + | FieldType::MultipleValueString + | FieldType::Exchange + | FieldType::LocalMarketDate + | FieldType::UtcTimeStamp + | FieldType::UtcDate + | FieldType::UtcTimeOnly + | FieldType::Country + | FieldType::UtcDateOnly + | FieldType::MultipleCharValue + | FieldType::MultipleStringValue + | FieldType::TzTimeOnly + | FieldType::TzTimestamp + | FieldType::XmlData + | FieldType::Language + | FieldType::TagNumber + | FieldType::XidRef + | FieldType::Xid + | FieldType::LocalMarketTime => "String", + x => unimplemented!("Unsupported FieldType: {x:?}"), + }; + + output.push_str(&format!("pub type {type_name} = {rust_type};\n\n")); +} + +fn generate_headers(output: &mut String, components: &[SubComponent]) { + struct Accessor; + + impl FieldAccessorGenerator for Accessor { + fn getter_prefix_text(&self) -> &'static str { + "inner.with_header(|x| x." + } + fn setter_prefix_text(&self) -> &'static str { + "inner.with_header_mut(|x| x." + } + fn caller_suffix_text(&self) -> &'static str { + ")" + } + } + + generate_message_wrapper(output, "Header", components, &Accessor); +} + +fn generate_trailers(output: &mut String, components: &[SubComponent]) { + struct Accessor; + + impl FieldAccessorGenerator for Accessor { + fn getter_prefix_text(&self) -> &'static str { + "inner.with_trailer(|x| x." + } + fn setter_prefix_text(&self) -> &'static str { + "inner.with_trailer_mut(|x| x." + } + fn caller_suffix_text(&self) -> &'static str { + ")" + } + } + + generate_message_wrapper(output, "Trailer", components, &Accessor); +} + +fn generate_message_wrapper( + output: &mut String, + struct_name: &str, + components: &[SubComponent], + accessor: &impl FieldAccessorGenerator, +) { + output.push_str(&format!( + r#" #[derive(Debug)] + pub struct {struct_name}<'a> {{ inner: &'a quickfix::Message }} + + "# + )); + + generate_sub_components(output, &struct_name.to_case(Case::Snake), components); + + output.push_str(&format!("impl {struct_name}<'_> {{\n")); + generate_components_getters(output, struct_name, components, accessor); + output.push_str("}\n\n"); + + output.push_str(&format!( + r#" #[derive(Debug)] + pub struct {struct_name}Mut<'a> {{ inner: &'a mut quickfix::Message }} + + "# + )); + + output.push_str(&format!("impl {struct_name}Mut<'_> {{\n")); + generate_components_setters(output, struct_name, components, accessor); + output.push_str("}\n\n"); +} + +fn generate_messages(output: &mut String, messages: &[MessageSpec]) { + for message in messages { + generate_message(output, message); + } +} + +fn generate_message(output: &mut String, message: &MessageSpec) { + let struct_name = message.name.as_str(); + let msg_type = message.msg_type.as_str(); + + // Generate main struct content. + output.push_str(&format!( + r#" #[derive(Debug)] + pub struct {struct_name} {{ + inner: quickfix::Message, + }} + + impl {struct_name} {{ + pub const MSG_TYPE: crate::field_types::MsgType = + match crate::field_types::MsgType::from_const_bytes(b"{msg_type}") {{ + Ok(value) => value, + Err(_) => panic!("Invalid message type for {struct_name}"), + }}; + + #[inline(always)] + pub fn header(&mut self) -> Header {{ + Header {{ inner: &self.inner }} + }} + + #[inline(always)] + pub fn header_mut(&mut self) -> HeaderMut {{ + HeaderMut {{ inner: &mut self.inner }} + }} + + #[inline(always)] + pub fn trailer(&mut self) -> Trailer {{ + Trailer {{ inner: &self.inner }} + }} + + #[inline(always)] + pub fn trailer_mut(&mut self) -> TrailerMut {{ + TrailerMut {{ inner: &mut self.inner }} + }} + + /// Convert inner message as FIX text. + /// + /// This method is only here for debug / tests purposes. Do not use this + /// in real production code. + #[inline(never)] + pub fn as_fix_string(&self) -> String {{ + self.inner + .as_string() + .expect("Fail to format {struct_name} message as FIX string") + }} + }} + + impl From<{struct_name}> for quickfix::Message {{ + fn from(input: {struct_name}) -> Self {{ + input.inner + }} + }} + + impl From for {struct_name} {{ + fn from(input: quickfix::Message) -> Self {{ + assert_eq!( + input + .with_header(|h| h.get_field(field_id::MSG_TYPE)) + .expect("Missing message type"), + Self::MSG_TYPE.as_fix_value(), + ); + Self {{ inner: input }} + }} + }} + + "# + )); + + // Generate default constructor + let required_params = format_required_params(&message.components); + let new_setters = format_new_setters(&message.components); + + output.push_str(&format!( + r#" impl {struct_name} {{ + #[allow(clippy::too_many_arguments)] + pub fn try_new({required_params}) -> Result {{ + let mut inner = quickfix::Message::new(); + + // Set headers (most of them will be set by quickfix library). + inner.with_header_mut(|h| {{ + h.set_field(crate::field_id::BEGIN_STRING, crate::FIX_BEGIN_STRING) + }})?; + inner.with_header_mut(|h| {{ + h.set_field(crate::field_id::MSG_TYPE, Self::MSG_TYPE) + }})?; + + // Set required attributes. + {new_setters} + + Ok(Self {{ inner }}) + }} + }} + + "# + )); + + // Generate getter / setters and sub-components. + struct Accessor; + + impl FieldAccessorGenerator for Accessor { + fn getter_prefix_text(&self) -> &'static str { + "inner." + } + fn setter_prefix_text(&self) -> &'static str { + "inner." + } + fn caller_suffix_text(&self) -> &'static str { + "" + } + } + + generate_sub_components( + output, + &message.name.to_case(Case::Snake), + &message.components, + ); + + output.push_str(&format!("impl {struct_name} {{\n\n")); + generate_components_getters(output, struct_name, &message.components, &Accessor); + generate_components_setters(output, struct_name, &message.components, &Accessor); + output.push_str("}\n\n"); +} + +fn generate_group(output: &mut String, group: &MessageGroup) { + let struct_name = group.name.as_str(); + let group_id = format_field_id(&group.name); + let group_delim = format_field_id( + group + .components + .first() + .expect("Group cannot be empty") + .name(), + ); + let group_value_ids = group + .components + .iter() + .map(|x| format_field_id(x.name())) + .join(","); + + // Generate main struct. + let required_params = format_required_params(&group.components); + let new_setters = format_new_setters(&group.components); + + output.push_str(&format!( + r#" #[derive(Debug)] + pub struct {struct_name} {{ + pub(crate) inner: quickfix::Group, + }} + + impl {struct_name} {{ + pub const FIELD_ID: i32 = {group_id}; + pub const DELIMITER: i32 = {group_delim}; + + #[allow(clippy::too_many_arguments)] + pub fn try_new({required_params}) -> Result {{ + #[allow(unused_mut)] + let mut inner = quickfix::Group::try_with_orders( + Self::FIELD_ID, + Self::DELIMITER, + &[{group_value_ids}], + ).expect("Fail to build group {struct_name}"); + + {new_setters} + + Ok(Self {{ inner }}) + }} + }} + + "# + )); + + // Generate getter / setters and sub-components. + struct Accessor; + + impl FieldAccessorGenerator for Accessor { + fn getter_prefix_text(&self) -> &'static str { + "inner." + } + fn setter_prefix_text(&self) -> &'static str { + "inner." + } + fn caller_suffix_text(&self) -> &'static str { + "" + } + } + + generate_sub_components(output, &group.name.to_case(Case::Snake), &group.components); + + output.push_str(&format!("impl {struct_name} {{\n\n")); + generate_components_getters(output, struct_name, &group.components, &Accessor); + generate_components_setters(output, struct_name, &group.components, &Accessor); + output.push_str("}\n\n"); +} + +fn generate_sub_components(output: &mut String, module_name: &str, components: &[SubComponent]) { + // Check if message has some sub components + if components + .iter() + .any(|x| matches!(x, SubComponent::Group(_))) + { + // Create dedicate module for the component. + output.push_str(&format!( + r#" pub mod {module_name} {{ + use super::*; + + "# + )); + + for value in components { + match value { + SubComponent::Field(_) => {} // There is no sub-components to generate for a basic field + SubComponent::Group(x) => { + generate_group(output, x); + } + } + } + output.push_str("}\n\n"); + } +} + +fn generate_components_getters( + output: &mut String, + struct_name: &str, + components: &[SubComponent], + accessor: &impl FieldAccessorGenerator, +) { + for component in components { + match component { + SubComponent::Field(x) => { + generate_field_getter(output, &x.name, x.required, accessor); + } + SubComponent::Group(x) => { + generate_group_reader(output, struct_name, x); + } + } + } +} + +fn generate_components_setters( + output: &mut String, + struct_name: &str, + components: &[SubComponent], + accessor: &impl FieldAccessorGenerator, +) { + for component in components { + match component { + SubComponent::Field(x) => { + generate_field_setters(output, x, accessor); + } + SubComponent::Group(x) => { + generate_fn_add_group(output, struct_name, x); + } + } + } +} + +fn generate_field_getter( + output: &mut String, + field_name: &str, + field_required: bool, + accessor: &impl FieldAccessorGenerator, +) { + // Eval trait and make some string alias. + let call_get_prefix = accessor.getter_prefix_text(); + let call_suffix = accessor.caller_suffix_text(); + + let fun_name_suffix = field_name.to_case(Case::Snake); + let field_type = format!("crate::field_types::{field_name}"); + let field_id = format_field_id(field_name); + + // Generate code. + if field_required { + // Generate a getter that `panic()` if field is not set. + output.push_str(&format!( + r#" #[inline(always)] + pub fn get_{fun_name_suffix}(&self) -> {field_type} {{ + self.{call_get_prefix}get_field({field_id}){call_suffix} + .and_then(|x| x.parse().ok()) + .expect("{field_id} is required but it is missing") + }} + + "# + )); + } else { + // Generate an optional getter. + output.push_str(&format!( + r#" #[inline(always)] + pub fn get_{fun_name_suffix}(&self) -> Option<{field_type}> {{ + self.{call_get_prefix}get_field({field_id}){call_suffix} + .and_then(|x| x.parse().ok()) + }} + + "# + )); + } +} + +fn generate_field_setters( + output: &mut String, + field: &MessageField, + accessor: &impl FieldAccessorGenerator, +) { + // Eval trait and make some string alias. + let call_set_prefix = accessor.setter_prefix_text(); + let call_suffix = accessor.caller_suffix_text(); + + let fun_name_suffix = field.name.to_case(Case::Snake); + let field_type = format!("crate::field_types::{}", field.name); + let field_id = format_field_id(&field.name); + + // Generate code. + output.push_str(&format!( + r#" #[inline(always)] + pub fn set_{fun_name_suffix}(&mut self, value: {field_type}) -> Result<&Self, QuickFixError> {{ + self.{call_set_prefix}set_field({field_id}, value){call_suffix}?; + Ok(self) + }} + + "# + )); + + // If field is optional, we can generate a remover. + if !field.required { + output.push_str(&format!( + r#" #[inline(always)] + pub fn remove_{fun_name_suffix}(&mut self) -> Result<&Self, QuickFixError> {{ + self.{call_set_prefix}remove_field({field_id}){call_suffix}?; + Ok(self) + }} + + "# + )); + } +} + +fn generate_group_reader(output: &mut String, struct_name: &str, group: &MessageGroup) { + // Add some type alias. + let fun_name_suffix = group.name.to_case(Case::Snake); + let group_type = format!("self::{}::{}", struct_name.to_case(Case::Snake), group.name); + + // Generate code. + output.push_str(&format!( + r#" #[inline(always)] + pub fn clone_group_{fun_name_suffix}(&self, index: usize) -> Option<{group_type}> {{ + self.inner + .clone_group(index as i32, {group_type}::FIELD_ID) + .map(|raw_group| {group_type} {{ inner: raw_group }}) + }} + + "# + )); +} + +fn generate_fn_add_group(output: &mut String, struct_name: &str, group: &MessageGroup) { + // Add some type alias. + let fun_name = format!("add_{}", group.name.to_case(Case::Snake)); + let group_type = format!("self::{}::{}", struct_name.to_case(Case::Snake), group.name); + + // Generate code. + output.push_str(&format!( + r#" #[inline(always)] + pub fn {fun_name}(&mut self, value: {group_type}) -> Result<&Self, QuickFixError> {{ + self.inner.add_group(&value.inner)?; + Ok(self) + }} + + "# + )); +} + +fn format_field_id(input: &str) -> String { + format!("crate::field_id::{}", input.to_case(Case::ScreamingSnake)) +} + +fn format_required_params(components: &[SubComponent]) -> String { + components + .iter() + .filter(|x| x.is_required()) + .map(|x| { + let name = x.name(); + let param_name = name.to_case(Case::Snake); + format!("{param_name}: crate::field_types::{name}") + }) + .join(", ") +} + +fn format_new_setters(components: &[SubComponent]) -> String { + components + .iter() + .filter(|x| x.is_required()) + .map(|x| { + let name = x.name(); + let field_id = format_field_id(name); + let param_name = name.to_case(Case::Snake); + format!("inner.set_field({field_id}, {param_name})?;") + }) + .join("\n") +} diff --git a/quickfix-msg-gen/src/model.rs b/quickfix-msg-gen/src/model.rs new file mode 100644 index 0000000..f411a2c --- /dev/null +++ b/quickfix-msg-gen/src/model.rs @@ -0,0 +1,43 @@ +use quickfix_spec_parser::FieldSpec; + +pub struct FixCodeSpec { + pub field_specs: Vec, + pub headers: Vec, + pub trailers: Vec, + pub messages: Vec, +} + +pub struct MessageSpec { + pub name: String, + pub msg_type: String, + pub components: Vec, +} + +pub enum SubComponent { + Field(MessageField), + Group(MessageGroup), +} + +pub struct MessageField { + pub name: String, + pub required: bool, +} + +pub struct MessageGroup { + pub name: String, + pub components: Vec, +} + +impl SubComponent { + pub fn name(&self) -> &str { + match self { + SubComponent::Field(x) => &x.name, + SubComponent::Group(x) => &x.name, + } + } + + pub fn is_required(&self) -> bool { + // API is a little bit hard to define if we include required group as required parameters 😢. + matches!(self, Self::Field(x) if x.required) + } +} diff --git a/quickfix-msg-gen/tests/test_generator.rs b/quickfix-msg-gen/tests/test_generator.rs new file mode 100644 index 0000000..d732a7e --- /dev/null +++ b/quickfix-msg-gen/tests/test_generator.rs @@ -0,0 +1,41 @@ +use std::{env::temp_dir, fs, io}; + +use quickfix_msg_gen::generate; + +#[test] +fn test_no_crash() -> io::Result<()> { + let dump_path = temp_dir().join("test_quickfix_msg_gen"); + fs::create_dir_all(&dump_path)?; + + // Just test generator do not crash with standard FIX spec files. + // + // 🚨🚨 Output code is not compiled 🚨🚨 ! + // This will be done via sub-packages with a nicer output to debug what goes wrong. + generate( + "../quickfix-ffi/libquickfix/spec/FIX40.xml", + dump_path.join("out40.rs"), + "FIX.4.0", + )?; + generate( + "../quickfix-ffi/libquickfix/spec/FIX41.xml", + dump_path.join("out41.rs"), + "FIX.4.1", + )?; + generate( + "../quickfix-ffi/libquickfix/spec/FIX42.xml", + dump_path.join("out42.rs"), + "FIX.4.2", + )?; + generate( + "../quickfix-ffi/libquickfix/spec/FIX43.xml", + dump_path.join("out43.rs"), + "FIX.4.3", + )?; + generate( + "../quickfix-ffi/libquickfix/spec/FIX44.xml", + dump_path.join("out44.rs"), + "FIX.4.4", + )?; + + Ok(()) +} diff --git a/quickfix-msg40/Cargo.toml b/quickfix-msg40/Cargo.toml new file mode 100644 index 0000000..65f1661 --- /dev/null +++ b/quickfix-msg40/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "quickfix-msg40" +version = "0.1.0" +authors = ["Arthur LE MOIGNE"] +edition = "2021" +description = "FIX 4.0 messages generated from official XML spec file" +repository = "https://github.com/arthurlm/quickfix-rs" +license = "MIT" +keywords = ["quickfix", "fix-protocol", "finance", "auto-generated"] +categories = ["encoding"] +rust-version = "1.70.0" + +[dependencies] +quickfix = { path = "../quickfix", version = "0.1.0" } + +[build-dependencies] +quickfix-msg-gen = { path = "../quickfix-msg-gen", version = "0.1.0" } diff --git a/quickfix-msg40/build.rs b/quickfix-msg40/build.rs new file mode 100644 index 0000000..6041763 --- /dev/null +++ b/quickfix-msg40/build.rs @@ -0,0 +1,17 @@ +use std::{env, io}; + +use quickfix_msg_gen::*; + +const SPEC_FILENAME: &str = "src/FIX40.xml"; +const BEGIN_STRING: &str = "FIX.4.0"; + +fn main() -> io::Result<()> { + let out_dir = env::var("OUT_DIR").expect("Missing OUT_DIR"); + + generate(SPEC_FILENAME, format!("{out_dir}/code.rs"), BEGIN_STRING)?; + + // Uncomment bello lines to show generated code + // generate(SPEC_FILENAME, "src/out.rs", BEGIN_STRING)?; + + Ok(()) +} diff --git a/quickfix-msg40/src/FIX40.xml b/quickfix-msg40/src/FIX40.xml new file mode 100644 index 0000000..d1c33a4 --- /dev/null +++ b/quickfix-msg40/src/FIX40.xml @@ -0,0 +1,862 @@ + +
+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/quickfix-msg40/src/lib.rs b/quickfix-msg40/src/lib.rs new file mode 100644 index 0000000..d2934b0 --- /dev/null +++ b/quickfix-msg40/src/lib.rs @@ -0,0 +1,2 @@ +// include!("out.rs"); +include!(concat!(env!("OUT_DIR"), "/code.rs")); diff --git a/quickfix-msg40/tests/test_usage.rs b/quickfix-msg40/tests/test_usage.rs new file mode 100644 index 0000000..6e654d8 --- /dev/null +++ b/quickfix-msg40/tests/test_usage.rs @@ -0,0 +1,156 @@ +use quickfix::{FieldMap, Message, QuickFixError}; +use quickfix_msg40::{ + field_types::{MsgType, Side}, + *, +}; + +#[test] +fn test_build_order_status_request() -> Result<(), QuickFixError> { + // `OrderStatusRequest` is a basic message with no sub components. + // It is a good start to play with. + let mut obj = + OrderStatusRequest::try_new("foo".to_string(), "AAPL US Equity".to_string(), Side::Buy) + .unwrap(); + + // Check FIX message contains valid value. + assert_eq!( + obj.as_fix_string(), + "8=FIX.4.0\u{1}9=35\u{1}35=H\u{1}\ + 11=foo\u{1}54=1\u{1}55=AAPL US Equity\u{1}\ + 10=213\u{1}" + ); + + // Check headers / trailers getters. + assert_eq!(obj.header().get_msg_type(), MsgType::OrderStatusRequest); + assert_eq!(obj.trailer().get_check_sum(), "213"); + + // Check headers setters and remover + obj.header_mut().remove_on_behalf_of_comp_id()?; + assert_eq!(obj.header().get_on_behalf_of_comp_id(), None); + + obj.header_mut() + .set_on_behalf_of_comp_id("ZeCorp".to_string())?; + assert_eq!( + obj.header().get_on_behalf_of_comp_id().as_deref(), + Some("ZeCorp") + ); + + obj.header_mut().remove_on_behalf_of_comp_id()?; + assert_eq!(obj.header().get_on_behalf_of_comp_id().as_deref(), None); + + // Check trailers setters and remover + obj.trailer_mut().remove_signature()?; + assert_eq!(obj.trailer().get_signature(), None); + + obj.trailer_mut().set_signature("ME".to_string())?; + assert_eq!(obj.trailer().get_signature().as_deref(), Some("ME")); + + obj.trailer_mut().remove_signature()?; + assert_eq!(obj.trailer().get_signature(), None); + + // Check getters. + assert_eq!(obj.get_cl_ord_id(), "foo"); + assert_eq!(obj.get_order_id(), None); + assert_eq!(obj.get_side(), Side::Buy); + + // Check required setters. + obj.set_cl_ord_id("bar".to_string())?; + assert_eq!(obj.get_cl_ord_id(), "bar"); + + // Check setters and remover. + obj.remove_issuer()?; + assert_eq!(obj.get_issuer(), None); + + obj.set_issuer("BBG".to_string())?; + assert_eq!(obj.get_issuer().as_deref(), Some("BBG")); + + obj.remove_issuer()?; + assert_eq!(obj.get_issuer(), None); + + // Trigger recompute checksum and check it. + let _ = obj.as_fix_string(); + assert_eq!(obj.trailer().get_check_sum(), "198"); + + // Convert struct to and from message. + let msg: Message = obj.into(); + assert_eq!( + msg.with_header(|h| h.get_field(field_id::MSG_TYPE)) + .as_deref(), + Some("H") + ); + + let _obj: OrderStatusRequest = msg.into(); + + Ok(()) +} + +#[test] +fn test_build_list_status() -> Result<(), QuickFixError> { + // `ListStatus` is the simplest message with required groups that can be found. + let mut obj = ListStatus::try_new("My list".to_string(), 0, 0)?; + + // Check FIX message contains valid value. + assert_eq!( + obj.as_fix_string(), + "8=FIX.4.0\u{1}9=26\u{1}35=N\u{1}\ + 66=My list\u{1}82=0\u{1}83=0\u{1}\ + 10=237\u{1}" + ); + + // Add some groups and check again string content + // NB. We clearly see group content if final string + check sorter works correctly 😎. + obj.add_no_orders(list_status::NoOrders::try_new( + "Order:10000".to_string(), + 100, + 50, + 18.5, + )?)?; + obj.add_no_orders(list_status::NoOrders::try_new( + "Order:10001".to_string(), + 89, + 75, + 987.4, + )?)?; + obj.add_no_orders(list_status::NoOrders::try_new( + "Order:10018".to_string(), + 5, + 79, + 5.6, + )?)?; + + assert_eq!( + obj.as_fix_string(), + "8=FIX.4.0\u{1}9=133\u{1}35=N\u{1}\ + 66=My list\u{1}73=3\u{1}\ + 11=Order:10000\u{1}14=100\u{1}84=50\u{1}6=18.5\u{1}\ + 11=Order:10001\u{1}14=89\u{1}84=75\u{1}6=987.4\u{1}\ + 11=Order:10018\u{1}14=5\u{1}84=79\u{1}6=5.6\u{1}\ + 82=0\u{1}83=0\u{1}\ + 10=128\u{1}" + ); + + // Read out of bound groups. + assert!(obj.clone_group_no_orders(0).is_none()); + assert!(obj.clone_group_no_orders(4).is_none()); + + // Read some group back. + let group = obj.clone_group_no_orders(1).unwrap(); + assert_eq!(group.get_cl_ord_id(), "Order:10000"); + assert_eq!(group.get_cum_qty(), 100); + assert_eq!(group.get_cxl_qty(), 50); + assert_eq!(group.get_avg_px(), 18.5); + + let group = obj.clone_group_no_orders(2).unwrap(); + assert_eq!(group.get_cl_ord_id(), "Order:10001"); + assert_eq!(group.get_cum_qty(), 89); + assert_eq!(group.get_cxl_qty(), 75); + assert_eq!(group.get_avg_px(), 987.4); + + let group = obj.clone_group_no_orders(3).unwrap(); + assert_eq!(group.get_cl_ord_id(), "Order:10018"); + assert_eq!(group.get_cum_qty(), 5); + assert_eq!(group.get_cxl_qty(), 79); + assert_eq!(group.get_avg_px(), 5.6); + + Ok(()) +} diff --git a/quickfix-msg41/Cargo.toml b/quickfix-msg41/Cargo.toml new file mode 100644 index 0000000..29b3b92 --- /dev/null +++ b/quickfix-msg41/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "quickfix-msg41" +version = "0.1.0" +authors = ["Arthur LE MOIGNE"] +edition = "2021" +description = "FIX 4.1 messages generated from official XML spec file" +repository = "https://github.com/arthurlm/quickfix-rs" +license = "MIT" +keywords = ["quickfix", "fix-protocol", "finance", "auto-generated"] +categories = ["encoding"] +rust-version = "1.70.0" + +[dependencies] +quickfix = { path = "../quickfix", version = "0.1.0" } + +[build-dependencies] +quickfix-msg-gen = { path = "../quickfix-msg-gen", version = "0.1.0" } diff --git a/quickfix-msg41/build.rs b/quickfix-msg41/build.rs new file mode 100644 index 0000000..794147e --- /dev/null +++ b/quickfix-msg41/build.rs @@ -0,0 +1,17 @@ +use std::{env, io}; + +use quickfix_msg_gen::*; + +const SPEC_FILENAME: &str = "src/FIX41.xml"; +const BEGIN_STRING: &str = "FIX.4.1"; + +fn main() -> io::Result<()> { + let out_dir = env::var("OUT_DIR").expect("Missing OUT_DIR"); + + generate(SPEC_FILENAME, format!("{out_dir}/code.rs"), BEGIN_STRING)?; + + // Uncomment bello lines to show generated code + // generate(SPEC_FILENAME, "src/out.rs", BEGIN_STRING)?; + + Ok(()) +} diff --git a/quickfix-msg41/src/FIX41.xml b/quickfix-msg41/src/FIX41.xml new file mode 100644 index 0000000..2a8af9b --- /dev/null +++ b/quickfix-msg41/src/FIX41.xml @@ -0,0 +1,1282 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/quickfix-msg41/src/lib.rs b/quickfix-msg41/src/lib.rs new file mode 100644 index 0000000..d2934b0 --- /dev/null +++ b/quickfix-msg41/src/lib.rs @@ -0,0 +1,2 @@ +// include!("out.rs"); +include!(concat!(env!("OUT_DIR"), "/code.rs")); diff --git a/quickfix-msg42/Cargo.toml b/quickfix-msg42/Cargo.toml new file mode 100644 index 0000000..8724694 --- /dev/null +++ b/quickfix-msg42/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "quickfix-msg42" +version = "0.1.0" +authors = ["Arthur LE MOIGNE"] +edition = "2021" +description = "FIX 4.2 messages generated from official XML spec file" +repository = "https://github.com/arthurlm/quickfix-rs" +license = "MIT" +keywords = ["quickfix", "fix-protocol", "finance", "auto-generated"] +categories = ["encoding"] +rust-version = "1.70.0" + +[dependencies] +quickfix = { path = "../quickfix", version = "0.1.0" } + +[build-dependencies] +quickfix-msg-gen = { path = "../quickfix-msg-gen", version = "0.1.0" } diff --git a/quickfix-msg42/build.rs b/quickfix-msg42/build.rs new file mode 100644 index 0000000..e603e3a --- /dev/null +++ b/quickfix-msg42/build.rs @@ -0,0 +1,17 @@ +use std::{env, io}; + +use quickfix_msg_gen::*; + +const SPEC_FILENAME: &str = "src/FIX42.xml"; +const BEGIN_STRING: &str = "FIX.4.2"; + +fn main() -> io::Result<()> { + let out_dir = env::var("OUT_DIR").expect("Missing OUT_DIR"); + + generate(SPEC_FILENAME, format!("{out_dir}/code.rs"), BEGIN_STRING)?; + + // Uncomment bello lines to show generated code + // generate(SPEC_FILENAME, "src/out.rs", BEGIN_STRING)?; + + Ok(()) +} diff --git a/quickfix-msg42/src/FIX42.xml b/quickfix-msg42/src/FIX42.xml new file mode 100644 index 0000000..553616a --- /dev/null +++ b/quickfix-msg42/src/FIX42.xml @@ -0,0 +1,2743 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/quickfix-msg42/src/lib.rs b/quickfix-msg42/src/lib.rs new file mode 100644 index 0000000..d2934b0 --- /dev/null +++ b/quickfix-msg42/src/lib.rs @@ -0,0 +1,2 @@ +// include!("out.rs"); +include!(concat!(env!("OUT_DIR"), "/code.rs")); diff --git a/quickfix-msg43/Cargo.toml b/quickfix-msg43/Cargo.toml new file mode 100644 index 0000000..a9379ce --- /dev/null +++ b/quickfix-msg43/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "quickfix-msg43" +version = "0.1.0" +authors = ["Arthur LE MOIGNE"] +edition = "2021" +description = "FIX 4.3 messages generated from official XML spec file" +repository = "https://github.com/arthurlm/quickfix-rs" +license = "MIT" +keywords = ["quickfix", "fix-protocol", "finance", "auto-generated"] +categories = ["encoding"] +rust-version = "1.70.0" + +[dependencies] +quickfix = { path = "../quickfix", version = "0.1.0" } + +[build-dependencies] +quickfix-msg-gen = { path = "../quickfix-msg-gen", version = "0.1.0" } diff --git a/quickfix-msg43/build.rs b/quickfix-msg43/build.rs new file mode 100644 index 0000000..92dc490 --- /dev/null +++ b/quickfix-msg43/build.rs @@ -0,0 +1,17 @@ +use std::{env, io}; + +use quickfix_msg_gen::*; + +const SPEC_FILENAME: &str = "src/FIX43.xml"; +const BEGIN_STRING: &str = "FIX.4.3"; + +fn main() -> io::Result<()> { + let out_dir = env::var("OUT_DIR").expect("Missing OUT_DIR"); + + generate(SPEC_FILENAME, format!("{out_dir}/code.rs"), BEGIN_STRING)?; + + // Uncomment bello lines to show generated code + // generate(SPEC_FILENAME, "src/out.rs", BEGIN_STRING)?; + + Ok(()) +} diff --git a/quickfix-msg43/src/FIX43.xml b/quickfix-msg43/src/FIX43.xml new file mode 100644 index 0000000..6e96d88 --- /dev/null +++ b/quickfix-msg43/src/FIX43.xml @@ -0,0 +1,4230 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/quickfix-msg43/src/lib.rs b/quickfix-msg43/src/lib.rs new file mode 100644 index 0000000..d2934b0 --- /dev/null +++ b/quickfix-msg43/src/lib.rs @@ -0,0 +1,2 @@ +// include!("out.rs"); +include!(concat!(env!("OUT_DIR"), "/code.rs")); diff --git a/quickfix-msg44/Cargo.toml b/quickfix-msg44/Cargo.toml new file mode 100644 index 0000000..3aaf29f --- /dev/null +++ b/quickfix-msg44/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "quickfix-msg44" +version = "0.1.0" +authors = ["Arthur LE MOIGNE"] +edition = "2021" +description = "FIX 4.4 messages generated from official XML spec file" +repository = "https://github.com/arthurlm/quickfix-rs" +license = "MIT" +keywords = ["quickfix", "fix-protocol", "finance", "auto-generated"] +categories = ["encoding"] +rust-version = "1.70.0" + +[dependencies] +quickfix = { path = "../quickfix", version = "0.1.0" } + +[build-dependencies] +quickfix-msg-gen = { path = "../quickfix-msg-gen", version = "0.1.0" } diff --git a/quickfix-msg44/build.rs b/quickfix-msg44/build.rs new file mode 100644 index 0000000..376729f --- /dev/null +++ b/quickfix-msg44/build.rs @@ -0,0 +1,17 @@ +use std::{env, io}; + +use quickfix_msg_gen::*; + +const SPEC_FILENAME: &str = "src/FIX44.xml"; +const BEGIN_STRING: &str = "FIX.4.4"; + +fn main() -> io::Result<()> { + let out_dir = env::var("OUT_DIR").expect("Missing OUT_DIR"); + + generate(SPEC_FILENAME, format!("{out_dir}/code.rs"), BEGIN_STRING)?; + + // Uncomment bello lines to show generated code + // generate(SPEC_FILENAME, "src/out.rs", BEGIN_STRING)?; + + Ok(()) +} diff --git a/quickfix-msg44/src/FIX44.xml b/quickfix-msg44/src/FIX44.xml new file mode 100644 index 0000000..6971919 --- /dev/null +++ b/quickfix-msg44/src/FIX44.xml @@ -0,0 +1,6600 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/quickfix-msg44/src/lib.rs b/quickfix-msg44/src/lib.rs new file mode 100644 index 0000000..d2934b0 --- /dev/null +++ b/quickfix-msg44/src/lib.rs @@ -0,0 +1,2 @@ +// include!("out.rs"); +include!(concat!(env!("OUT_DIR"), "/code.rs")); diff --git a/quickfix-spec-parser/Cargo.toml b/quickfix-spec-parser/Cargo.toml index 0d0fbcc..3bf9d0f 100644 --- a/quickfix-spec-parser/Cargo.toml +++ b/quickfix-spec-parser/Cargo.toml @@ -1,8 +1,15 @@ [package] name = "quickfix-spec-parser" version = "0.1.0" +authors = ["Arthur LE MOIGNE"] edition = "2021" -publish = false +description = "FIX XML spec file parser / writer" +repository = "https://github.com/arthurlm/quickfix-rs" +license = "MIT" +keywords = ["quickfix", "fix-protocol", "finance"] +categories = ["development-tools::build-utils"] +rust-version = "1.70.0" +exclude = ["examples"] [dependencies] bytes = "1.5.0" diff --git a/quickfix/src/lib.rs b/quickfix/src/lib.rs index ae2a027..65c003b 100644 --- a/quickfix/src/lib.rs +++ b/quickfix/src/lib.rs @@ -12,6 +12,9 @@ - Provide basic and safe API wrapper above [quickfix](https://github.com/quickfix/quickfix) library. - Run on any hardware and operating system supported by Rust Tier 1 (Windows 7+, MacOS 10.12+ & Linux). - Message decoding / encoding including run-time validation. +- Supports FIX versions 4x (version 5x can be build locally from XML spec file). +- Spec driven run-time message validation. +- Spec driven code generation of type-safe FIX messages, fields, and repeating groups. - Session state storage options: SQL, File, In Memory. - Logging options: stdout, stderr, [log](https://crates.io/crates/log) or any other crate if you implement your own trait. @@ -22,6 +25,7 @@ Following package must be install to build the library: - `cmake` - a C++ compiler (with C++17 support) - `rustup` / `rustc` / `cargo` (obviously 😉) +- `rustfmt` for auto generated messages from spec. ## Example usage