Skip to content

Commit

Permalink
✨ Generate message from FIX 4x XML spec
Browse files Browse the repository at this point in the history
  • Loading branch information
arthurlm committed Jan 17, 2024
1 parent 5579750 commit 323c498
Show file tree
Hide file tree
Showing 34 changed files with 17,092 additions and 9 deletions.
Empty file added .gitattributes
Empty file.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
submodules: true
- run: rustup toolchain install 1.70.0
- run: rustup override set 1.70.0
- run: rustup add component rustfmt
- uses: Swatinem/rust-cache@v2
- run: rustc --version
- name: Build debug
Expand All @@ -40,6 +41,7 @@ jobs:
submodules: true
- run: rustup toolchain install 1.70.0
- run: rustup override set 1.70.0
- run: rustup add component rustfmt
- uses: Swatinem/rust-cache@v2
- name: Install dependencies
run: |
Expand All @@ -57,6 +59,7 @@ jobs:
submodules: true
- run: rustup toolchain install 1.70.0
- run: rustup override set 1.70.0
- run: rustup add component rustfmt
- uses: Swatinem/rust-cache@v2
- name: Install dependencies
run: |
Expand Down
81 changes: 81 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -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",
]
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -90,10 +93,15 @@ 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:

- `cmake`
- a C++ compiler (with C++17 support)
- `rustup` / `rustc` / `cargo` (obviously 😉)
- `rustfmt` for auto generated messages from spec.
15 changes: 9 additions & 6 deletions doc/ABOUT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down
2 changes: 1 addition & 1 deletion quickfix-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
18 changes: 18 additions & 0 deletions quickfix-msg-gen/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
59 changes: 59 additions & 0 deletions quickfix-msg-gen/src/converter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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<SubComponent> {
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(),
required: x.required,
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<MessageSpec> {
messages
.iter()
.map(|message| MessageSpec {
name: message.name.clone(),
msg_type: message.msg_type.clone(),
components: convert_field_value_list(spec, &message.values),
})
.collect()
}
Loading

0 comments on commit 323c498

Please sign in to comment.