use std::marker::PhantomData;
use std::rc::Rc;
use std::time::{Duration, Instant};
use crate::correlation::CorrelationId;
use crate::errors::{BlpError, Result};
use crate::event::Event;
use crate::identity::Identity;
use crate::message::Message;
use crate::request::Request;
use crate::service::Service;
use crate::session_operations::SessionOperations;
use crate::subscription::SubscriptionList;
pub use crate::options::SessionOptions;
pub struct Session {
ptr: *mut crate::ffi::blpapi_Session_t,
_not_send_sync: PhantomData<Rc<()>>,
}
impl Session {
const STARTUP_POLL_TIMEOUT_MS: u32 = 250;
fn operations(&self) -> SessionOperations<'_> {
unsafe { SessionOperations::new(&self.ptr) }
}
pub fn new(options: &SessionOptions) -> Result<Self> {
let ptr = unsafe {
crate::ffi::blpapi_Session_create(
options.as_raw(),
None, std::ptr::null_mut(), std::ptr::null_mut(), )
};
if ptr.is_null() {
return Err(BlpError::SessionStart {
source: None,
label: None,
});
}
Ok(Self {
ptr,
_not_send_sync: PhantomData,
})
}
pub fn start(&self) -> Result<()> {
let rc = unsafe { crate::ffi::blpapi_Session_start(self.ptr) };
if rc != 0 {
return Err(BlpError::SessionStart {
source: None,
label: None,
});
}
Ok(())
}
pub fn wait_until_started(&self, timeout_ms: u32) -> Result<()> {
let deadline = Instant::now() + Duration::from_millis(u64::from(timeout_ms));
loop {
let now = Instant::now();
if now >= deadline {
return Err(BlpError::Timeout);
}
let remaining = deadline.saturating_duration_since(now);
let poll_timeout = remaining
.min(Duration::from_millis(u64::from(
Self::STARTUP_POLL_TIMEOUT_MS,
)))
.as_millis() as u32;
let poll_timeout = poll_timeout.max(1);
let event = self.next_event(Some(poll_timeout))?;
let mut saw_session_started = false;
let mut messages = event.messages();
while let Some(msg) = messages.next() {
match msg.message_type().as_str() {
"SessionStarted" => saw_session_started = true,
"SessionStartupFailure" => {
return Err(startup_error_from_message("session startup failure", &msg));
}
"SessionTerminated" => {
return Err(startup_error_from_message(
"session terminated during startup",
&msg,
));
}
"AuthorizationFailure" => {
return Err(startup_error_from_message(
"session identity authorization failed",
&msg,
));
}
"AuthorizationRevoked" => {
return Err(startup_error_from_message(
"session identity authorization revoked",
&msg,
));
}
_ => {}
}
}
if saw_session_started {
return Ok(());
}
}
}
pub fn start_and_wait(&self, timeout_ms: u32) -> Result<()> {
self.start()?;
self.wait_until_started(timeout_ms)
}
pub fn stop(&self) {
unsafe {
crate::ffi::blpapi_Session_stop(self.ptr);
}
}
pub fn next_event(&self, timeout_ms: Option<u32>) -> Result<Event> {
let mut event_ptr: *mut crate::ffi::blpapi_Event_t = std::ptr::null_mut();
let rc = unsafe {
crate::ffi::blpapi_Session_nextEvent(self.ptr, &mut event_ptr, timeout_ms.unwrap_or(0))
};
if rc != 0 {
return Err(BlpError::Internal {
detail: format!("blpapi_Session_nextEvent failed: rc={rc}"),
});
}
if event_ptr.is_null() {
return Err(BlpError::Internal {
detail: "nextEvent returned null event".into(),
});
}
Ok(unsafe { Event::from_raw(event_ptr) })
}
pub fn try_next_event(&self) -> Option<Event> {
let mut event_ptr: *mut crate::ffi::blpapi_Event_t = std::ptr::null_mut();
let rc = unsafe { crate::ffi::blpapi_Session_tryNextEvent(self.ptr, &mut event_ptr) };
if rc == 0 && !event_ptr.is_null() {
Some(unsafe { Event::from_raw(event_ptr) })
} else {
None
}
}
pub fn open_service(&self, name: &str) -> Result<()> {
self.operations().open_service(name)
}
pub fn open_service_async(&self, name: &str, cid: &CorrelationId) -> Result<CorrelationId> {
self.operations().open_service_async(name, cid)
}
pub fn get_service(&self, name: &str) -> Result<Service<'_>> {
self.operations().get_service(name)
}
pub fn send_request(
&self,
req: &Request,
identity: Option<&Identity>,
cid: Option<&CorrelationId>,
) -> Result<CorrelationId> {
self.send_request_with_label(req, identity, cid, None)
}
pub fn send_request_with_label(
&self,
req: &Request,
identity: Option<&Identity>,
cid: Option<&CorrelationId>,
label: Option<&str>,
) -> Result<CorrelationId> {
self.operations().send_request(req, identity, cid, label)
}
pub fn subscribe(&self, subs: &SubscriptionList, label: Option<&str>) -> Result<()> {
self.operations().subscribe(subs, None, label, "label")
}
pub fn resubscribe(&self, subs: &SubscriptionList, label: Option<&str>) -> Result<()> {
self.operations().resubscribe(subs, label, "label")
}
pub fn unsubscribe(&self, subs: &SubscriptionList) -> Result<()> {
self.operations().unsubscribe(subs)
}
pub fn cancel(&self, cid: &CorrelationId) -> Result<()> {
self.operations().cancel(cid)
}
pub fn create_identity(&self) -> Result<Identity> {
self.operations().create_identity()
}
pub fn generate_token(&self, cid: Option<&CorrelationId>) -> Result<CorrelationId> {
self.operations().generate_token(cid)
}
pub fn send_authorization_request(
&self,
request: &Request,
identity: &mut Identity,
cid: Option<&CorrelationId>,
) -> Result<CorrelationId> {
self.operations()
.send_authorization_request(request, identity, cid)
}
pub fn subscribe_with_identity(
&self,
subs: &SubscriptionList,
identity: &Identity,
label: Option<&str>,
) -> Result<()> {
self.operations()
.subscribe(subs, Some(identity), label, "label")
}
}
fn startup_error_from_message(default_label: &str, msg: &Message<'_>) -> BlpError {
let label = extract_reason_description(msg).unwrap_or_else(|| default_label.to_string());
BlpError::SessionStart {
source: None,
label: Some(label),
}
}
fn extract_reason_description(msg: &Message<'_>) -> Option<String> {
let reason = msg.elements().get_by_str("reason")?;
if let Some(description) = reason
.get_by_str("description")
.and_then(|value| value.get_str(0))
{
return Some(description.to_string());
}
if let Some(category) = reason
.get_by_str("category")
.and_then(|value| value.get_str(0))
{
return Some(category.to_string());
}
if let Some(message) = reason
.get_by_str("message")
.and_then(|value| value.get_str(0))
{
return Some(message.to_string());
}
None
}
impl Drop for Session {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe {
crate::ffi::blpapi_Session_stop(self.ptr);
crate::ffi::blpapi_Session_destroy(self.ptr);
}
self.ptr = std::ptr::null_mut();
}
}
}