Data visualizer for Advanced Programming course project
In order to display information related to the simulation you must:
- Include the following dependencies in the cargo.toml file:
serde = "1.0.130"
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] }
futures = "0.3.25"
-
Every struct that has to be sent in the body has to implement the Serialize trait (
#[derive(serde::Serialize)]
) -
To send the requests a connection with the server is needed. Use reqwest to create a client instance to perform requests as follow
let client = reqwest::Client::new();
- To execute a query
let _res = client.post(url).json(&body).send().await;
where the url is the endopoint of the API needed, while body is the content that has to be sent (maybe empty, if the request does not expect any data).
-
Mark the functions as async, cause of the await
-
In order to make the program wait for the APIs to return use the following crates
use futures::executor::block_on;
use tokio::runtime::Runtime;
let rt = Runtime::new().unwrap();
rt.block_on(<async function>);
This will force the execution to wait for the APIs to return before continuing the procedure.
This API returns the number of milliseconds to wait before performing the next event. This can be usefull in order to inspect what's happening to the trader without being overwhelmed by requests.
The endopint of this API is
GET /delay
let res = client.get("http://localhost:8000/delay").send().await.unwrap();
let value:u64 = res.json::<u64>().await.unwrap();
This API returns (inside a stream) the trader that has been chosen from the visualizer.
The endpoint of this API is
GET /traderToUse
async fn initialize() -> u8 {
let mut connection = EventSource::get("http://localhost:8000/traderToUse");
let mut traderIndex: u8 = 4;
loop {
let next = connection.next().await;
match next {
Some(content) => match content {
Ok(ReqEvent::Message(message)) => {
traderIndex = message.data.parse::<u8>().unwrap();
break;
}
Err(err) => panic!("{err}"),
_ => continue
},
None => continue,
}
}
traderIndex
}
This function waits for a message containing the trader index, returning it. A common usage of this may be the following: inside the main function call initialize
let run_time = Runtime::new().unwrap();
let res = run_time.block_on(async { initialize().await });
match res {
0 => {
...
},
...
}
The API returns:
- for Sabin's trader;
- for Alfredo's trader;
- for Taras's trader;
This API is used to post every action performed by the trader, in order to display them inside the table. The actions that can be sent to this API are the ones defined in the EventKind enum.
The endpoint of this API is
POST /log
If running on local the complete url is
http://localhost:8000/log
The body to pass is an instance of the stuct LogEvent
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum CustomEventKind {
Bought, Sold, LockedBuy, LockedSell, Wait
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CustomEvent {
pub kind: CustomEventKind,
pub good_kind: GoodKind,
pub quantity: f32,
pub price: f32,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct LogEvent {
pub time: u32,
pub event: CustomEvent,
pub market: String,
pub result: bool,
pub error: Option<String>
}
(the structs/enums defined in the market-common could not be used because they do not implement the Serialize trait) The attribute time can be omitted (it is computed server-side); same for the error (if not present it will be considered None). The error should be populated with the error returned by the market in case of rejection.
Usage Example1
pub fn craft_log_event(time: u32, kind: CustomEventKind, good_kind: GoodKind, quantity: f32, price: f32, market: String, result: bool, error: Option<String>) -> LogEvent {
let custom_ev = CustomEvent {
kind,
good_kind,
quantity,
price,
};
LogEvent {
market,
result,
error,
time,
event: custom_ev,
}
}
...
let goodkind: GoodKind = GoodKind::YEN;
let quantity: f32 = 12.3;
let price: f32 = 42.0;
let market_name: String = "BFB".to_string();
let e_string: String = "NonPositiveQuantity".to_string();
let _res = client.post("http://localhost:8000/log").json(&craft_log_event(CustomEventKind::LockedBuy, goodkind, quantity, price, market_name, false, Some(e_string))).send().await;
This API is used to send the current status of a market (quantity of goods, exchange_buy/sell_rate), in order to produce graphs. The endpoint of this API is
POST /currentGoodLabels/<market>
where market is one of the market used in the simulationo ("BFB", "RCNZ", "ZSE"
).
The body to pass is a Vec<GoodLabel>
, easily obtainable calling the method get_goods() of the Market trait.
This API should be called at the beginning of the simulation and after each Event performed WITH SUCCESS by the trader (every time a day passes).
Usage Example1
let labels: Vec<GoodLabel> = market.borrow().get_goods();
let _res = client.post("http://localhost:8000/currentGoodLabels/".to_string() + market_name).json(&labels).send().await;
This API is used to send the current status of the trader (quantity of each good). The endpoint of this API is
POST /traderGoods
The body to pass is a Vec<TraderGood>
, where
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
struct TraderGood{
kind: GoodKind,
quantity: f32
}
This API should be called at the beginning of the simulation and after each Event performed WITH SUCCESS by the trader (every time a day passes).
Usage Example1
let mut tradergoods = vec![];
tradergoods.push(TraderGood{kind: GoodKind::EUR, quantity: self.cash});
for goodkind in &self.goods{
tradergoods.push(TraderGood{
kind: goodkind.borrow().get_kind().clone(),
quantity: goodkind.borrow().get_qty()
});
}
let _res = client.post("http://localhost:8000/traderGoods").json(&tradergoods).send().await;