use std::any::Any;
use crate::errors::QlResult;
use crate::patterns::observable::{AsObservable, Observable, Observer, ResetThenNotify};
use crate::shared::{Shared, SharedMut};
pub trait Arguments: Any {
fn validate(&self) -> QlResult<()>;
}
pub trait Results: Any {
fn reset(&mut self);
fn as_instrument_results(&self) -> Option<&crate::instrument::InstrumentResults> {
None
}
}
pub trait PricingEngine: AsObservable {
fn arguments_mut(&mut self) -> &mut dyn Arguments;
fn results(&self) -> &dyn Results;
fn reset(&mut self);
fn calculate(&mut self) -> QlResult<()>;
}
pub struct GenericEngine<A, R> {
arguments: A,
results: R,
observable: Shared<Observable>,
forwarder: SharedMut<ResetThenNotify>,
}
impl<A: Arguments, R: Results> GenericEngine<A, R> {
pub fn new(arguments: A, results: R) -> Self {
let (observable, forwarder) = ResetThenNotify::forwarder();
GenericEngine {
arguments,
results,
observable,
forwarder,
}
}
pub fn arguments(&self) -> &A {
&self.arguments
}
pub fn arguments_mut(&mut self) -> &mut A {
&mut self.arguments
}
pub fn results(&self) -> &R {
&self.results
}
pub fn results_mut(&mut self) -> &mut R {
&mut self.results
}
pub fn reset(&mut self) {
self.results.reset();
}
pub fn register_with(&self, source: &Observable) -> bool {
source.register_observer(&(SharedMut::clone(&self.forwarder) as SharedMut<dyn Observer>))
}
}
impl<A, R> AsObservable for GenericEngine<A, R> {
fn observable(&self) -> &Observable {
&self.observable
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::require;
use crate::test_support::{Flag, as_observer};
use crate::types::Real;
#[derive(Default)]
struct SumArguments {
x: Option<Real>,
y: Option<Real>,
}
impl Arguments for SumArguments {
fn validate(&self) -> QlResult<()> {
require!(self.x.is_some() && self.y.is_some(), "both terms required");
Ok(())
}
}
#[derive(Default)]
struct SumResults {
value: Option<Real>,
}
impl Results for SumResults {
fn reset(&mut self) {
self.value = None;
}
}
struct SumEngine {
base: GenericEngine<SumArguments, SumResults>,
}
impl SumEngine {
fn new() -> Self {
SumEngine {
base: GenericEngine::new(SumArguments::default(), SumResults::default()),
}
}
}
impl AsObservable for SumEngine {
fn observable(&self) -> &Observable {
self.base.observable()
}
}
impl PricingEngine for SumEngine {
fn arguments_mut(&mut self) -> &mut dyn Arguments {
self.base.arguments_mut()
}
fn results(&self) -> &dyn Results {
self.base.results()
}
fn reset(&mut self) {
self.base.reset();
}
fn calculate(&mut self) -> QlResult<()> {
self.base.arguments().validate()?;
let x = self.base.arguments().x.expect("validated");
let y = self.base.arguments().y.expect("validated");
self.base.results_mut().value = Some(x + y);
Ok(())
}
}
#[test]
fn engine_protocol_round_trip_through_trait_objects() {
let mut engine = SumEngine::new();
let engine: &mut dyn PricingEngine = &mut engine;
engine.reset();
let arguments = engine.arguments_mut();
let sum = (arguments as &mut dyn Any)
.downcast_mut::<SumArguments>()
.expect("engine carries SumArguments");
sum.x = Some(2.0);
sum.y = Some(3.0);
arguments.validate().unwrap();
engine.calculate().unwrap();
let results = (engine.results() as &dyn Any)
.downcast_ref::<SumResults>()
.expect("engine carries SumResults");
assert_eq!(results.value, Some(5.0));
}
#[test]
fn validate_rejects_incomplete_arguments() {
let mut engine = SumEngine::new();
engine.base.arguments_mut().x = Some(1.0);
let err = engine.base.arguments().validate().unwrap_err();
assert_eq!(err.message(), "both terms required");
assert!(engine.calculate().is_err());
}
#[test]
fn reset_clears_previous_results() {
let mut engine = SumEngine::new();
engine.base.arguments_mut().x = Some(1.0);
engine.base.arguments_mut().y = Some(1.0);
engine.calculate().unwrap();
assert_eq!(engine.base.results().value, Some(2.0));
engine.reset();
assert_eq!(engine.base.results().value, None);
}
#[test]
fn input_notifications_are_forwarded_to_engine_observers() {
let engine = SumEngine::new();
let input = Observable::new();
assert!(engine.base.register_with(&input));
let flag = Flag::new();
engine.observable().register_observer(&as_observer(&flag));
input.notify_observers();
assert!(
Flag::is_up(&flag),
"input change must reach engine observers"
);
}
}