Skip to content

## πŸš€ Add pure Rust Dictionary implementation #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ members = [
"quickfix-msg42",
"quickfix-msg43",
"quickfix-msg44",
"quickfix-msg50",
"quickfix-msg50", "quickfix-native",
]
11 changes: 11 additions & 0 deletions quickfix-native/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "quickfix-native"
version = "0.1.0"
edition = "2024"

[dependencies]
thiserror="2.0.12"


[lints.clippy]
uninlined_format_args = "allow"
31 changes: 31 additions & 0 deletions quickfix-native/TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## Basics:

- [x] Dictionary class
- [x] SessionID class
- [ ] DataDictionary class


## Core message types:

- [ ] Message class
- [ ] Header class
- [ ] Trailer class
- [ ] Group class


## Session management:

- [ ] SessionSettings class
- [ ] Session class


## Storage backends:

- [ ] NullStoreFactory / MemoryStoreFactory (simplest)
- [ ] FileStoreFactory (more complex)


## Network layer:

- [ ] SocketAcceptor
- [ ] SocketInitiator
165 changes: 165 additions & 0 deletions quickfix-native/src/dictionary.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
use std::collections::HashMap;

use crate::errors::{NativeError, Result};

/// For storage and retrieval of key/value pairs.
#[derive(Default, Debug, Clone)]
pub struct Dictionary {
name: String,
data: HashMap<String, String>,
}

impl<'a> IntoIterator for &'a Dictionary {
type Item = (&'a String, &'a String);
type IntoIter = std::collections::hash_map::Iter<'a, String, String>;
fn into_iter(self) -> Self::IntoIter {
self.data.iter()
}
}

impl Dictionary {
pub fn new(name: String) -> Self {
Dictionary {
name,
data: HashMap::new(),
}
}

/// Get the name of the dictionary.
pub fn get_name(&self) -> &str {
&self.name
}

/// Return the number of key/value pairs.
pub fn size(&self) -> usize {
self.data.len()
}

/// Check if the dictionary contains a value for key.
pub fn has(&self, key: &str) -> bool {
self.data.contains_key(key)
}

/// Get a value as a string.
pub fn get_string(&self, key: &str, capitalize: bool) -> Result<String> {
let normalized_key = key.trim().to_uppercase();
let value = self
.data
.get(&normalized_key)
.ok_or_else(|| NativeError::ConfigError(format!("{} not defined", key)))?;

if capitalize {
Ok(value.to_uppercase())
} else {
Ok(value.clone())
}
}

/// Get a value as an i32.
pub fn get_int(&self, key: &str) -> Result<i32> {
let value = self.get_string(key, false)?;
value.parse::<i32>().map_err(|_| {
NativeError::FieldConvertError(format!("Illegal value {} for {} ", value, key))
})
}
/// Get a value as a f64
pub fn get_double(&self, key: &str) -> Result<f64> {
let value = self.get_string(key, false)?;
value.parse::<f64>().map_err(|_| {
NativeError::FieldConvertError(format!("Illegal value {} for {} ", value, key))
})
}
/// Get a value as a bool
pub fn get_bool(&self, key: &str) -> Result<bool> {
let value = self.get_string(key, false)?;
match Self::convert_bool(&value) {
Some(true) => Ok(true),
Some(false) => Err(NativeError::FieldConvertError(
"Returned value is set to 'NO'".to_string(),
)),
None => Err(NativeError::FieldConvertError(format!(
"Illegal value {} for {}",
value, key
))),
}
}

/// Get a value as a day of week
pub fn get_day(&self, key: &str) -> Result<i32> {
let value = self.get_string(key, false)?;
if value.len() < 2 {
return Err(NativeError::FieldConvertError(format!(
"Illegal value {} for {}",
value, key
)));
}

let first_two = value[..2].to_lowercase();
match first_two.as_str() {
"su" => Ok(1),
"mo" => Ok(2),
"tu" => Ok(3),
"we" => Ok(4),
"th" => Ok(5),
"fr" => Ok(6),
"sa" => Ok(7),
_ => Err(NativeError::FieldConvertError(format!(
"Illegal value {} for {}",
value, key
))),
}
}

/// Set a value from a string.
pub fn set_string(&mut self, key: &str, value: &str) {
self.data
.insert(key.trim().to_uppercase(), value.trim().to_uppercase());
}

/// Set a value from a string.
pub fn set_int(&mut self, key: &str, value: i32) {
self.set_string(key, &value.to_string());
}

///Set a value from a bool
pub fn set_bool(&mut self, key: &str, value: bool) {
let val = if value { "Y" } else { "N" };
self.set_string(key, val);
}
/// set a value from a f64
pub fn set_double(&mut self, key: &str, value: f64) {
self.set_string(key, &value.to_string());
}

/// Set a value from a day
pub fn set_day(&mut self, key: &str, value: i32) {
let val = match value {
1 => "SU",
2 => "MO",
3 => "TU",
4 => "WE",
5 => "TH",
6 => "FR",
7 => "SA",
_ => return,
};
self.set_string(key, val);
}

pub fn merge(&mut self, dict: &Dictionary) -> Result<()> {
for (k, v) in dict {
self.data.entry(k.clone()).or_insert_with(|| v.clone());
}

Ok(())
}

/// convert a value to its corresponding bool
pub fn convert_bool(value: &str) -> Option<bool> {
match value.to_uppercase().as_str() {
"Y" => Some(true),
"N" => Some(false),
_ => None,
}
}
}
28 changes: 28 additions & 0 deletions quickfix-native/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use thiserror::Error;

//TODO: should later be matched against QuickFixErros
#[derive(Error, Debug, Clone, PartialEq)]
pub enum NativeError {
#[error("Configuration error: {0}")]
ConfigError(String),

#[error("Field conversion error: {0}")]
FieldConvertError(String),

#[error("Field not found: {0}")]
FieldNotFound(String),

#[error("Invalid message: {0}")]
InvalidMessage(String),

#[error("Session not found: {0}")]
SessionNotFound(String),

#[error("Io error: {0}")]
IoError(String),

#[error("Network error: {0}")]
NetworkError(String),
}

pub type Result<T> = std::result::Result<T, NativeError>;
Loading