use std::convert::Infallible;
use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Service {
ApiFlds,
BqlSvc,
ExrSvc,
Instruments,
MktBar,
MktData,
MktDepth,
MktList,
MktVwap,
RefData,
TaSvc,
Custom(String),
}
impl Service {
pub fn as_str(&self) -> &str {
match self {
Self::ApiFlds => "//blp/apiflds",
Self::BqlSvc => "//blp/bqlsvc",
Self::ExrSvc => "//blp/exrsvc",
Self::Instruments => "//blp/instruments",
Self::MktBar => "//blp/mktbar",
Self::MktData => "//blp/mktdata",
Self::MktDepth => "//blp/mktdepthdata",
Self::MktList => "//blp/mktlist",
Self::MktVwap => "//blp/mktvwap",
Self::RefData => "//blp/refdata",
Self::TaSvc => "//blp/tasvc",
Self::Custom(s) => s.as_str(),
}
}
}
impl fmt::Display for Service {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl FromStr for Service {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let svc = match s {
"//blp/apiflds" => Self::ApiFlds,
"//blp/bqlsvc" => Self::BqlSvc,
"//blp/exrsvc" => Self::ExrSvc,
"//blp/instruments" => Self::Instruments,
"//blp/mktbar" => Self::MktBar,
"//blp/mktdata" => Self::MktData,
"//blp/mktdepthdata" => Self::MktDepth,
"//blp/mktlist" => Self::MktList,
"//blp/mktvwap" => Self::MktVwap,
"//blp/refdata" => Self::RefData,
"//blp/tasvc" => Self::TaSvc,
other => Self::Custom(other.to_string()),
};
Ok(svc)
}
}
impl Serialize for Service {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for Service {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(match Self::from_str(&s) {
Ok(service) => service,
Err(never) => match never {},
})
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Operation {
Beqs,
BqlSendQuery,
CurveList,
ExcelGetGrid,
FieldInfo,
FieldSearch,
GovtList,
HistoricalData,
InstrumentList,
IntradayBar,
IntradayTick,
PortfolioData,
RawRequest,
ReferenceData,
StudyRequest,
Custom(String),
}
impl Operation {
pub fn as_str(&self) -> &str {
match self {
Self::Beqs => "BeqsRequest",
Self::BqlSendQuery => "sendQuery",
Self::CurveList => "curveListRequest",
Self::ExcelGetGrid => "ExcelGetGridRequest",
Self::FieldInfo => "FieldInfoRequest",
Self::FieldSearch => "FieldSearchRequest",
Self::GovtList => "govtListRequest",
Self::HistoricalData => "HistoricalDataRequest",
Self::InstrumentList => "instrumentListRequest",
Self::IntradayBar => "IntradayBarRequest",
Self::IntradayTick => "IntradayTickRequest",
Self::PortfolioData => "PortfolioDataRequest",
Self::RawRequest => "",
Self::ReferenceData => "ReferenceDataRequest",
Self::StudyRequest => "studyRequest",
Self::Custom(s) => s.as_str(),
}
}
pub fn default_extractor(&self) -> ExtractorType {
match self {
Self::Beqs => ExtractorType::Generic,
Self::BqlSendQuery => ExtractorType::Bql,
Self::CurveList => ExtractorType::Generic,
Self::ExcelGetGrid => ExtractorType::Bsrch,
Self::FieldInfo => ExtractorType::FieldInfo,
Self::FieldSearch => ExtractorType::Generic,
Self::GovtList => ExtractorType::Generic,
Self::HistoricalData => ExtractorType::HistData,
Self::InstrumentList => ExtractorType::Generic,
Self::IntradayBar => ExtractorType::IntradayBar,
Self::IntradayTick => ExtractorType::IntradayTick,
Self::PortfolioData => ExtractorType::Generic,
Self::RawRequest => ExtractorType::Generic,
Self::ReferenceData => ExtractorType::RefData,
Self::StudyRequest => ExtractorType::Generic,
Self::Custom(_) => ExtractorType::Generic,
}
}
pub fn default_service(&self) -> Option<Service> {
match self {
Self::Beqs => Some(Service::RefData),
Self::BqlSendQuery => Some(Service::BqlSvc),
Self::CurveList => Some(Service::Instruments),
Self::ExcelGetGrid => Some(Service::ExrSvc),
Self::FieldInfo => Some(Service::ApiFlds),
Self::FieldSearch => Some(Service::ApiFlds),
Self::GovtList => Some(Service::Instruments),
Self::HistoricalData => Some(Service::RefData),
Self::InstrumentList => Some(Service::Instruments),
Self::IntradayBar => Some(Service::RefData),
Self::IntradayTick => Some(Service::RefData),
Self::PortfolioData => Some(Service::RefData),
Self::RawRequest => None,
Self::ReferenceData => Some(Service::RefData),
Self::StudyRequest => Some(Service::TaSvc),
Self::Custom(_) => None,
}
}
}
impl fmt::Display for Operation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl FromStr for Operation {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let op = match s {
"BeqsRequest" => Self::Beqs,
"sendQuery" => Self::BqlSendQuery,
"curveListRequest" => Self::CurveList,
"ExcelGetGridRequest" => Self::ExcelGetGrid,
"FieldInfoRequest" => Self::FieldInfo,
"FieldSearchRequest" => Self::FieldSearch,
"govtListRequest" => Self::GovtList,
"HistoricalDataRequest" => Self::HistoricalData,
"instrumentListRequest" => Self::InstrumentList,
"IntradayBarRequest" => Self::IntradayBar,
"IntradayTickRequest" => Self::IntradayTick,
"PortfolioDataRequest" => Self::PortfolioData,
"" => Self::RawRequest,
"ReferenceDataRequest" => Self::ReferenceData,
"studyRequest" => Self::StudyRequest,
other => Self::Custom(other.to_string()),
};
Ok(op)
}
}
impl Serialize for Operation {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for Operation {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(match Self::from_str(&s) {
Ok(operation) => operation,
Err(never) => match never {},
})
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ExtractorType {
Bql,
Bsrch,
BulkData,
FieldInfo,
Generic,
HistData,
IntradayBar,
IntradayTick,
#[default]
RefData,
}
impl ExtractorType {
pub fn parse(s: &str) -> Option<Self> {
match s {
"bql" => Some(Self::Bql),
"bsrch" => Some(Self::Bsrch),
"bulk" => Some(Self::BulkData),
"fieldinfo" => Some(Self::FieldInfo),
"generic" => Some(Self::Generic),
"histdata" => Some(Self::HistData),
"intraday_bar" => Some(Self::IntradayBar),
"intraday_tick" => Some(Self::IntradayTick),
"refdata" => Some(Self::RefData),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Format {
#[default]
Long,
LongTyped,
LongWithMetadata,
SemiLong,
}
impl Format {
pub fn as_str(&self) -> &str {
match self {
Self::Long => "long",
Self::LongTyped => "long_typed",
Self::LongWithMetadata => "long_metadata",
Self::SemiLong => "semi_long",
}
}
}
impl fmt::Display for Format {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl FromStr for Format {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"long" => Ok(Self::Long),
"long_typed" => Ok(Self::LongTyped),
"long_metadata" => Ok(Self::LongWithMetadata),
"semi_long" => Ok(Self::SemiLong),
other => Err(format!("Unknown format: {}", other)),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum OutputMode {
#[default]
Arrow,
Json,
}
impl OutputMode {
pub fn as_str(&self) -> &str {
match self {
Self::Arrow => "arrow",
Self::Json => "json",
}
}
}
impl fmt::Display for OutputMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl FromStr for OutputMode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"arrow" => Ok(Self::Arrow),
"json" => Ok(Self::Json),
other => Err(format!("Unknown output mode: {}", other)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn service_roundtrip() {
assert_eq!(
Service::from_str("//blp/refdata").unwrap(),
Service::RefData
);
assert_eq!(Service::RefData.as_str(), "//blp/refdata");
assert_eq!(Service::RefData.to_string(), "//blp/refdata");
}
#[test]
fn service_custom() {
let custom = Service::from_str("//blp/custom").unwrap();
assert_eq!(custom, Service::Custom("//blp/custom".to_string()));
assert_eq!(custom.as_str(), "//blp/custom");
}
#[test]
fn service_all_known() {
for (uri, expected) in [
("//blp/apiflds", Service::ApiFlds),
("//blp/bqlsvc", Service::BqlSvc),
("//blp/exrsvc", Service::ExrSvc),
("//blp/instruments", Service::Instruments),
("//blp/mktbar", Service::MktBar),
("//blp/mktdata", Service::MktData),
("//blp/mktdepthdata", Service::MktDepth),
("//blp/mktlist", Service::MktList),
("//blp/mktvwap", Service::MktVwap),
("//blp/refdata", Service::RefData),
("//blp/tasvc", Service::TaSvc),
] {
assert_eq!(Service::from_str(uri).unwrap(), expected);
assert_eq!(expected.as_str(), uri);
}
}
#[test]
fn service_serde() {
let svc = Service::RefData;
let json = serde_json::to_string(&svc).unwrap();
assert_eq!(json, r#""//blp/refdata""#);
let back: Service = serde_json::from_str(&json).unwrap();
assert_eq!(back, Service::RefData);
}
#[test]
fn operation_roundtrip() {
assert_eq!(
Operation::from_str("ReferenceDataRequest").unwrap(),
Operation::ReferenceData
);
assert_eq!(Operation::ReferenceData.as_str(), "ReferenceDataRequest");
assert_eq!(Operation::ReferenceData.to_string(), "ReferenceDataRequest");
}
#[test]
fn operation_custom() {
let custom = Operation::from_str("CustomRequest").unwrap();
assert_eq!(custom, Operation::Custom("CustomRequest".to_string()));
assert_eq!(custom.as_str(), "CustomRequest");
}
#[test]
fn operation_all_known() {
for (op, expected) in [
("BeqsRequest", Operation::Beqs),
("sendQuery", Operation::BqlSendQuery),
("curveListRequest", Operation::CurveList),
("ExcelGetGridRequest", Operation::ExcelGetGrid),
("FieldInfoRequest", Operation::FieldInfo),
("FieldSearchRequest", Operation::FieldSearch),
("govtListRequest", Operation::GovtList),
("HistoricalDataRequest", Operation::HistoricalData),
("instrumentListRequest", Operation::InstrumentList),
("IntradayBarRequest", Operation::IntradayBar),
("IntradayTickRequest", Operation::IntradayTick),
("PortfolioDataRequest", Operation::PortfolioData),
("", Operation::RawRequest),
("ReferenceDataRequest", Operation::ReferenceData),
("studyRequest", Operation::StudyRequest),
] {
assert_eq!(Operation::from_str(op).unwrap(), expected);
assert_eq!(expected.as_str(), op);
}
}
#[test]
fn operation_serde() {
let op = Operation::HistoricalData;
let json = serde_json::to_string(&op).unwrap();
assert_eq!(json, r#""HistoricalDataRequest""#);
let back: Operation = serde_json::from_str(&json).unwrap();
assert_eq!(back, Operation::HistoricalData);
}
#[test]
fn operation_default_extractor() {
assert_eq!(Operation::Beqs.default_extractor(), ExtractorType::Generic);
assert_eq!(
Operation::BqlSendQuery.default_extractor(),
ExtractorType::Bql
);
assert_eq!(
Operation::CurveList.default_extractor(),
ExtractorType::Generic
);
assert_eq!(
Operation::ExcelGetGrid.default_extractor(),
ExtractorType::Bsrch
);
assert_eq!(
Operation::FieldInfo.default_extractor(),
ExtractorType::FieldInfo
);
assert_eq!(
Operation::FieldSearch.default_extractor(),
ExtractorType::Generic
);
assert_eq!(
Operation::GovtList.default_extractor(),
ExtractorType::Generic
);
assert_eq!(
Operation::HistoricalData.default_extractor(),
ExtractorType::HistData
);
assert_eq!(
Operation::InstrumentList.default_extractor(),
ExtractorType::Generic
);
assert_eq!(
Operation::IntradayBar.default_extractor(),
ExtractorType::IntradayBar
);
assert_eq!(
Operation::IntradayTick.default_extractor(),
ExtractorType::IntradayTick
);
assert_eq!(
Operation::PortfolioData.default_extractor(),
ExtractorType::Generic
);
assert_eq!(
Operation::RawRequest.default_extractor(),
ExtractorType::Generic
);
assert_eq!(
Operation::ReferenceData.default_extractor(),
ExtractorType::RefData
);
assert_eq!(
Operation::StudyRequest.default_extractor(),
ExtractorType::Generic
);
assert_eq!(
Operation::Custom("foo".into()).default_extractor(),
ExtractorType::Generic
);
}
#[test]
fn operation_default_service() {
assert_eq!(Operation::Beqs.default_service(), Some(Service::RefData));
assert_eq!(
Operation::BqlSendQuery.default_service(),
Some(Service::BqlSvc)
);
assert_eq!(
Operation::CurveList.default_service(),
Some(Service::Instruments)
);
assert_eq!(
Operation::ExcelGetGrid.default_service(),
Some(Service::ExrSvc)
);
assert_eq!(
Operation::FieldInfo.default_service(),
Some(Service::ApiFlds)
);
assert_eq!(
Operation::FieldSearch.default_service(),
Some(Service::ApiFlds)
);
assert_eq!(
Operation::GovtList.default_service(),
Some(Service::Instruments)
);
assert_eq!(
Operation::HistoricalData.default_service(),
Some(Service::RefData)
);
assert_eq!(
Operation::InstrumentList.default_service(),
Some(Service::Instruments)
);
assert_eq!(
Operation::IntradayBar.default_service(),
Some(Service::RefData)
);
assert_eq!(
Operation::IntradayTick.default_service(),
Some(Service::RefData)
);
assert_eq!(
Operation::PortfolioData.default_service(),
Some(Service::RefData)
);
assert_eq!(Operation::RawRequest.default_service(), None);
assert_eq!(
Operation::ReferenceData.default_service(),
Some(Service::RefData)
);
assert_eq!(
Operation::StudyRequest.default_service(),
Some(Service::TaSvc)
);
assert_eq!(Operation::Custom("foo".into()).default_service(), None);
}
#[test]
fn operation_lowercase_operations() {
assert_eq!(
Operation::from_str("sendQuery").unwrap(),
Operation::BqlSendQuery
);
assert_eq!(Operation::BqlSendQuery.as_str(), "sendQuery");
assert_eq!(
Operation::from_str("curveListRequest").unwrap(),
Operation::CurveList
);
assert_eq!(Operation::CurveList.as_str(), "curveListRequest");
assert_eq!(
Operation::from_str("govtListRequest").unwrap(),
Operation::GovtList
);
assert_eq!(Operation::GovtList.as_str(), "govtListRequest");
assert_eq!(
Operation::from_str("instrumentListRequest").unwrap(),
Operation::InstrumentList
);
assert_eq!(Operation::InstrumentList.as_str(), "instrumentListRequest");
assert_eq!(
Operation::from_str("studyRequest").unwrap(),
Operation::StudyRequest
);
assert_eq!(Operation::StudyRequest.as_str(), "studyRequest");
}
#[test]
fn format_roundtrip() {
assert_eq!(Format::from_str("long").unwrap(), Format::Long);
assert_eq!(Format::Long.as_str(), "long");
assert_eq!(Format::Long.to_string(), "long");
}
#[test]
fn format_all_variants() {
for (s, expected) in [
("long", Format::Long),
("long_typed", Format::LongTyped),
("long_metadata", Format::LongWithMetadata),
("semi_long", Format::SemiLong),
] {
assert_eq!(Format::from_str(s).unwrap(), expected);
assert_eq!(expected.as_str(), s);
}
}
#[test]
fn format_invalid() {
assert!(Format::from_str("invalid").is_err());
}
#[test]
fn format_serde() {
let fmt = Format::LongTyped;
let json = serde_json::to_string(&fmt).unwrap();
assert_eq!(json, r#""long_typed""#);
let back: Format = serde_json::from_str(&json).unwrap();
assert_eq!(back, Format::LongTyped);
}
#[test]
fn output_mode_roundtrip() {
assert_eq!(OutputMode::from_str("arrow").unwrap(), OutputMode::Arrow);
assert_eq!(OutputMode::Arrow.as_str(), "arrow");
assert_eq!(OutputMode::Arrow.to_string(), "arrow");
}
#[test]
fn output_mode_all_variants() {
for (s, expected) in [("arrow", OutputMode::Arrow), ("json", OutputMode::Json)] {
assert_eq!(OutputMode::from_str(s).unwrap(), expected);
assert_eq!(expected.as_str(), s);
}
}
#[test]
fn output_mode_invalid() {
assert!(OutputMode::from_str("invalid").is_err());
}
#[test]
fn output_mode_serde() {
let mode = OutputMode::Json;
let json = serde_json::to_string(&mode).unwrap();
assert_eq!(json, r#""json""#);
let back: OutputMode = serde_json::from_str(&json).unwrap();
assert_eq!(back, OutputMode::Json);
}
}
#[cfg(test)]
mod defs_contract {
use std::str::FromStr;
#[test]
fn overflow_policy_accepts_every_defs_spelling() {
for spelling in ["block", "drop_newest", "dropnewest"] {
assert!(
crate::engine::OverflowPolicy::from_str(spelling).is_ok(),
"defs/bloomberg.toml declares {spelling} but the parser rejects it"
);
}
}
#[test]
fn overflow_policy_defs_covers_every_variant() {
fn canonical(value: crate::engine::OverflowPolicy) -> &'static str {
match value {
crate::engine::OverflowPolicy::Block => "block",
crate::engine::OverflowPolicy::DropNewest => "drop_newest",
}
}
for spelling in ["block", "drop_newest"] {
assert_eq!(
canonical(crate::engine::OverflowPolicy::from_str(spelling).unwrap()),
spelling,
"defs maps this spelling to a different variant than the parser does"
);
}
}
#[test]
fn overflow_policy_display_matches_defs() {
for canonical in ["block", "drop_newest"] {
assert_eq!(
crate::engine::OverflowPolicy::from_str(canonical)
.unwrap()
.to_string(),
canonical,
"the canonical defs value must be the value the engine emits"
);
}
}
#[test]
fn overflow_policy_default_matches_defs() {
assert_eq!(
crate::engine::OverflowPolicy::from_str("drop_newest").unwrap(),
crate::engine::OverflowPolicy::default(),
"defs marks a different member as default than the parser does"
);
}
#[test]
fn validation_mode_accepts_every_defs_spelling() {
for spelling in ["disabled", "off", "none", "lenient", "strict"] {
assert!(
crate::engine::ValidationMode::from_str(spelling).is_ok(),
"defs/bloomberg.toml declares {spelling} but the parser rejects it"
);
}
}
#[test]
fn validation_mode_defs_covers_every_variant() {
fn canonical(value: crate::engine::ValidationMode) -> &'static str {
match value {
crate::engine::ValidationMode::Disabled => "disabled",
crate::engine::ValidationMode::Lenient => "lenient",
crate::engine::ValidationMode::Strict => "strict",
}
}
for spelling in ["disabled", "lenient", "strict"] {
assert_eq!(
canonical(crate::engine::ValidationMode::from_str(spelling).unwrap()),
spelling,
"defs maps this spelling to a different variant than the parser does"
);
}
}
#[test]
fn validation_mode_display_matches_defs() {
for canonical in ["disabled", "lenient", "strict"] {
assert_eq!(
crate::engine::ValidationMode::from_str(canonical)
.unwrap()
.to_string(),
canonical,
"the canonical defs value must be the value the engine emits"
);
}
}
#[test]
fn validation_mode_default_matches_defs() {
assert_eq!(
crate::engine::ValidationMode::from_str("disabled").unwrap(),
crate::engine::ValidationMode::default(),
"defs marks a different member as default than the parser does"
);
}
#[test]
fn sdk_log_level_accepts_every_defs_spelling() {
for spelling in [
"debug", "error", "fatal", "info", "off", "trace", "warn", "warning",
] {
assert!(
crate::sdk_logging::SdkLogLevel::from_str(spelling).is_ok(),
"defs/bloomberg.toml declares {spelling} but the parser rejects it"
);
}
}
#[test]
fn sdk_log_level_defs_covers_every_variant() {
fn canonical(value: crate::sdk_logging::SdkLogLevel) -> &'static str {
match value {
crate::sdk_logging::SdkLogLevel::Debug => "debug",
crate::sdk_logging::SdkLogLevel::Error => "error",
crate::sdk_logging::SdkLogLevel::Fatal => "fatal",
crate::sdk_logging::SdkLogLevel::Info => "info",
crate::sdk_logging::SdkLogLevel::Off => "off",
crate::sdk_logging::SdkLogLevel::Trace => "trace",
crate::sdk_logging::SdkLogLevel::Warn => "warn",
}
}
for spelling in ["debug", "error", "fatal", "info", "off", "trace", "warn"] {
assert_eq!(
canonical(crate::sdk_logging::SdkLogLevel::from_str(spelling).unwrap()),
spelling,
"defs maps this spelling to a different variant than the parser does"
);
}
}
}