feat: ✨ quote publishing from rustlers
This commit is contained in:
parent
b3d2fde147
commit
9625e32bbd
813
.github/img/diagram.svg
vendored
Normal file
813
.github/img/diagram.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 140 KiB |
4
.github/img/rustler.svg
vendored
4
.github/img/rustler.svg
vendored
@ -5,7 +5,5 @@
|
|||||||
.a { fill: #ffffff; }
|
.a { fill: #ffffff; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<path class="a" d="M34,10v4H0V10H8V2.5A.5.5,0,0,1,8.5,2H26v8ZM25.677,20.75452a12.03954,12.03954,0,0,0,.312-2.236.50528.50528,0,0,0-.49176-.51831L25.48285,18H8.5a.5.5,0,0,0-.5.5v11a.5.5,0,0,0,.5.5H14a11.95968,11.95968,0,0,0,8.69525-3.74884A3.99442,3.99442,0,0,0,26,28h4a4,4,0,0,0-4-4h2a4,4,0,0,0,4-4H28A3.97023,3.97023,0,0,0,25.677,20.75452Z" />
|
||||||
<path class="a" d="M32,20H28a3.96994,3.96994,0,0,0-2.323.75452,12.03549,12.03549,0,0,0,.312-2.236.5052.5052,0,0,0-.49176-.51831L25.48285,18H8.5a.5.5,0,0,0-.5.5v11a.5.5,0,0,0,.5.5H14a11.95959,11.95959,0,0,0,8.69525-3.74884A3.99448,3.99448,0,0,0,26,28h4a4,4,0,0,0-4-4h2A4,4,0,0,0,32,20Z" />
|
|
||||||
<path class="a" d="M30,6.5V10H26V2.5a.5.5,0,0,0-.5-.5h-7a.5.5,0,0,0-.5.5V4H14V2.5a.5.5,0,0,0-.5-.5h-5a.5.5,0,0,0-.5.5V10H4V6.5A.5.5,0,0,0,3.5,6H.5a.5.5,0,0,0-.5.5V10a4,4,0,0,0,4,4H30a4,4,0,0,0,4-4V6.5a.5.5,0,0,0-.5-.5h-3A.5.5,0,0,0,30,6.5ZM22,10H12V6h2V7.5a.5.5,0,0,0,.5.5h3a.5.5,0,0,0,.5-.5V6h4Z" />
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 801 B After Width: | Height: | Size: 557 B |
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -10,7 +10,7 @@
|
|||||||
".cocorc": true,
|
".cocorc": true,
|
||||||
".task": true,
|
".task": true,
|
||||||
".cargo": true,
|
".cargo": true,
|
||||||
".github": true,
|
// ".github": true,
|
||||||
"rustfmt.toml": true,
|
"rustfmt.toml": true,
|
||||||
// "**/**/Cargo.toml": true,
|
// "**/**/Cargo.toml": true,
|
||||||
|
|
||||||
@ -32,6 +32,7 @@
|
|||||||
// "rustler.db": true,
|
// "rustler.db": true,
|
||||||
"rustler.db-shm": true,
|
"rustler.db-shm": true,
|
||||||
"rustler.db-wal": true,
|
"rustler.db-wal": true,
|
||||||
"rustler.db.bkp": true,
|
"rustler.db.bkp": true
|
||||||
}
|
}
|
||||||
|
// "editor.formatOnSave": true
|
||||||
}
|
}
|
||||||
11
README.md
11
README.md
@ -14,9 +14,9 @@
|
|||||||
|
|
||||||
## Why "rustler"
|
## Why "rustler"
|
||||||
|
|
||||||
A `rustler` is a person who steals live***stock***. Well, this library is a service that collects _stock_ market data from the internet. So, it's a "_rustler_" for stock market data.
|
A `rustler` is a person who steals live**_stock_**. Well, this library is a service that collects _stock_ market data from the internet. So, it's a "_rustler_" for stock market data.
|
||||||
|
|
||||||
Also, this library is built using the `Rust` programming language... so, ***rust***ler 😊
|
Also, this library is built using the `Rust` programming language... so, **_rust_**ler 😊
|
||||||
|
|
||||||
## What this library includes
|
## What this library includes
|
||||||
|
|
||||||
@ -41,6 +41,13 @@ Apart from the above, this library also defines:
|
|||||||
>
|
>
|
||||||
> Actual concrete implementations for each market cannot be published for many reasons.
|
> Actual concrete implementations for each market cannot be published for many reasons.
|
||||||
|
|
||||||
|
## Diagram
|
||||||
|
|
||||||
|
The following diagram shows the core components of the `rustler-core` library and how they interact
|
||||||
|
with each other.
|
||||||
|
|
||||||
|
<p align="center"><img src=".github/img/diagram.svg"></p>
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
Check the [examples](./examples) directory for an example of how to use this library.
|
Check the [examples](./examples) directory for an example of how to use this library.
|
||||||
|
|||||||
@ -1,12 +1,16 @@
|
|||||||
use {
|
use {
|
||||||
async_trait::async_trait,
|
async_trait::async_trait,
|
||||||
eyre::Result,
|
eyre::{OptionExt, Result},
|
||||||
lool::logger::info,
|
lool::{
|
||||||
|
logger::{debug, error, info},
|
||||||
|
s,
|
||||||
|
},
|
||||||
rustler_core::{
|
rustler_core::{
|
||||||
rustler,
|
rustler,
|
||||||
rustlers::{Rustler, RustlerAccessor, RustlerStatus, Ticker},
|
rustlers::{svc::quote, MarketHourType, Rustler, RustlerAccessor, RustlerStatus, Ticker},
|
||||||
},
|
},
|
||||||
std::collections::HashMap,
|
std::collections::HashMap,
|
||||||
|
tokio::select,
|
||||||
};
|
};
|
||||||
|
|
||||||
rustler!(
|
rustler!(
|
||||||
@ -26,6 +30,41 @@ impl FooRustler {
|
|||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn start_rustling(&mut self) -> Result<()> {
|
||||||
|
let sender = self.msg_sender().as_ref().ok_or_eyre("Sender not found")?.clone();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
debug!("Starting rustling");
|
||||||
|
select! {
|
||||||
|
_ = async move {
|
||||||
|
loop {
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||||
|
|
||||||
|
let result = sender
|
||||||
|
.send(quote(
|
||||||
|
s!("BTCUSDT"),
|
||||||
|
s!("BINANCE"),
|
||||||
|
50000.0,
|
||||||
|
0.0,
|
||||||
|
198798798798,
|
||||||
|
MarketHourType::Regular,
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(e) = result {
|
||||||
|
error!("Failed to send message: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} => {
|
||||||
|
error!("Rustling stopped");
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -37,7 +76,9 @@ impl Rustler for FooRustler {
|
|||||||
|
|
||||||
self.set_status(RustlerStatus::Connecting)?;
|
self.set_status(RustlerStatus::Connecting)?;
|
||||||
|
|
||||||
info!("Connecting to data source");
|
info!("(mock) Connecting to data source");
|
||||||
|
self.start_rustling().await?;
|
||||||
|
info!("(mock) Connected to data source");
|
||||||
|
|
||||||
self.set_status(RustlerStatus::Connected)?;
|
self.set_status(RustlerStatus::Connected)?;
|
||||||
|
|
||||||
@ -51,23 +92,20 @@ impl Rustler for FooRustler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.set_status(RustlerStatus::Disconnecting)?;
|
self.set_status(RustlerStatus::Disconnecting)?;
|
||||||
|
info!("(mock) Disconnecting from data source");
|
||||||
info!("Disconnecting from data source");
|
|
||||||
|
|
||||||
self.set_status(RustlerStatus::Disconnected)?;
|
self.set_status(RustlerStatus::Disconnected)?;
|
||||||
|
info!("(mock) Disconnected from data source");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_add(&mut self, tickers: &[Ticker]) -> Result<()> {
|
fn on_add(&mut self, tickers: &[Ticker]) -> Result<()> {
|
||||||
info!("Adding tickers: {:?}", tickers);
|
info!("(mock) Adding tickers: {:?}", tickers);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_delete(&mut self, tickers: &[Ticker]) -> Result<()> {
|
fn on_delete(&mut self, tickers: &[Ticker]) -> Result<()> {
|
||||||
info!("Deleting tickers: {:?}", tickers);
|
info!("(mock) Deleting tickers: {:?}", tickers);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,13 +3,16 @@ mod binance;
|
|||||||
use {
|
use {
|
||||||
eyre::{set_hook, DefaultHandler, Result},
|
eyre::{set_hook, DefaultHandler, Result},
|
||||||
lool::s,
|
lool::s,
|
||||||
rustler_core::rustlers::{bus, MarketHourType, Quote},
|
rustler_core::rustlers::{
|
||||||
|
bus::{self, PublisherTrait},
|
||||||
|
MarketHourType, Quote,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
set_hook(Box::new(DefaultHandler::default_with))?;
|
set_hook(Box::new(DefaultHandler::default_with))?;
|
||||||
let mut px = bus::publisher(&"redis://127.0.0.1/").await?;
|
let mut px = bus::redis::publisher(&"redis://127.0.0.1/").await?;
|
||||||
let variations = vec![-4.3, -1.1, 2.0, -0.5, 1.5, -1.3, 0.7, 0.3, -0.1, 3.4];
|
let variations = vec![-4.3, -1.1, 2.0, -0.5, 1.5, -1.3, 0.7, 0.3, -0.1, 3.4];
|
||||||
|
|
||||||
let vars = variations.clone();
|
let vars = variations.clone();
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
use {
|
use {
|
||||||
eyre::{set_hook, DefaultHandler, Result},
|
eyre::{set_hook, DefaultHandler, Result},
|
||||||
rustler_core::rustlers::{bus, Quote, Ticker},
|
rustler_core::rustlers::{
|
||||||
|
bus::{self, SubscriberTrait},
|
||||||
|
Quote, Ticker,
|
||||||
|
},
|
||||||
rxrust::observable::{ObservableExt, ObservableItem},
|
rxrust::observable::{ObservableExt, ObservableItem},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -8,7 +11,7 @@ use {
|
|||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
set_hook(Box::new(DefaultHandler::default_with))?;
|
set_hook(Box::new(DefaultHandler::default_with))?;
|
||||||
|
|
||||||
let mut sx = bus::subscriber::<Quote, _>(&"redis://127.0.0.1/").await?;
|
let mut sx = bus::redis::subscriber::<Quote, _>(&"redis://127.0.0.1/").await?;
|
||||||
|
|
||||||
let ticker = Ticker {
|
let ticker = Ticker {
|
||||||
market: "BINANCE".to_string(),
|
market: "BINANCE".to_string(),
|
||||||
|
|||||||
@ -5,7 +5,11 @@ use {
|
|||||||
dotenvy::dotenv,
|
dotenvy::dotenv,
|
||||||
eyre::{set_hook, DefaultHandler, Result},
|
eyre::{set_hook, DefaultHandler, Result},
|
||||||
lool::logger::{info, ConsoleLogger, Level},
|
lool::logger::{info, ConsoleLogger, Level},
|
||||||
rustler_core::{entities::db::get_connection, grpc, rustlerjar, rustlers::svc::RustlersSvc},
|
rustler_core::{
|
||||||
|
entities::db::get_connection,
|
||||||
|
grpc, rustlerjar,
|
||||||
|
rustlers::{bus, svc::RustlersSvc, Quote},
|
||||||
|
},
|
||||||
tokio::join,
|
tokio::join,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -15,12 +19,15 @@ async fn main() -> Result<()> {
|
|||||||
ConsoleLogger::default_setup(Level::Trace, "rustler")?;
|
ConsoleLogger::default_setup(Level::Trace, "rustler")?;
|
||||||
|
|
||||||
dotenv()?;
|
dotenv()?;
|
||||||
|
let publisher = bus::redis::publisher::<Quote, _>(&"redis://127.0.0.1/").await?;
|
||||||
|
|
||||||
let conn = get_connection().await?;
|
let conn = get_connection().await?;
|
||||||
let mut rustler = RustlersSvc::new(
|
let mut rustler = RustlersSvc::new(
|
||||||
conn.clone(),
|
conn.clone(),
|
||||||
rustlerjar! {
|
rustlerjar! {
|
||||||
"BINANCE" => FooRustler::create
|
"BINANCE" => FooRustler::create
|
||||||
},
|
},
|
||||||
|
publisher,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|||||||
45
lib/rustlers/bus.rs
Normal file
45
lib/rustlers/bus.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
use std::{convert::Infallible, fmt::Debug};
|
||||||
|
|
||||||
|
use eyre::Result;
|
||||||
|
use rxrust::ops::box_it::CloneableBoxOpThreads;
|
||||||
|
use tonic::async_trait;
|
||||||
|
|
||||||
|
pub mod redis;
|
||||||
|
|
||||||
|
/// 🐎 » represents a value that can be serialized to a bus value
|
||||||
|
pub trait ToBusVal {
|
||||||
|
fn to_bus_val(&self) -> Vec<(String, String)>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 🐎 » represents a value that can be serialized to a bus key
|
||||||
|
pub trait ToBusKey {
|
||||||
|
fn to_bus_key(&self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 🐎 » represents a value that can be serialized to and from a bus message
|
||||||
|
pub trait ToFromBusMessage {
|
||||||
|
fn as_message(&self) -> String;
|
||||||
|
fn from_message<T: AsRef<str>>(msg: T) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 🐎 » supertrait combining all bus object traits + debug + send + sync + 'static
|
||||||
|
pub trait BusMessage:
|
||||||
|
ToBusVal + ToBusKey + ToFromBusMessage + Debug + Clone + Send + Sync + PartialEq + 'static
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 🐎 » trait for bus **Publisher**s
|
||||||
|
#[async_trait]
|
||||||
|
pub trait PublisherTrait<RM: BusMessage> {
|
||||||
|
/// 🐎 » publish a message to the bus
|
||||||
|
async fn publish(&mut self, value: RM) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 🐎 » trait for bus **Publisher**s
|
||||||
|
#[async_trait]
|
||||||
|
pub trait SubscriberTrait<RM: BusMessage> {
|
||||||
|
/// 🐎 » **stream**
|
||||||
|
///
|
||||||
|
/// returns an `Observable` stream of messages from the redis bus
|
||||||
|
async fn stream(&mut self) -> Result<CloneableBoxOpThreads<RM, Infallible>>;
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
use {eyre::Result, redis::Client, std::fmt::Debug};
|
use {super::BusMessage, eyre::Result, redis::Client};
|
||||||
|
|
||||||
pub mod publish;
|
pub mod publish;
|
||||||
pub mod subscribe;
|
pub mod subscribe;
|
||||||
@ -58,28 +58,6 @@ impl RedisClient for String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 🐎 » represents a value that can be serialized to a redis value
|
|
||||||
pub trait ToRedisVal {
|
|
||||||
fn to_redis_val(&self) -> Vec<(String, String)>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 🐎 » represents a value that can be serialized to a redis key
|
|
||||||
pub trait ToRedisKey {
|
|
||||||
fn to_redis_key(&self) -> String;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 🐎 » represents a value that can be serialized to and from a redis message
|
|
||||||
pub trait ToFromRedisMessage {
|
|
||||||
fn as_message(&self) -> String;
|
|
||||||
fn from_message<T: AsRef<str>>(msg: T) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 🐎 » supertrait combining all redis object traits + debug + send + sync + 'static
|
|
||||||
pub trait RedisMessage:
|
|
||||||
ToRedisVal + ToRedisKey + ToFromRedisMessage + Debug + Clone + Send + Sync + PartialEq + 'static
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 🐎 » **publisher**: create bus publisher
|
/// 🐎 » **publisher**: create bus publisher
|
||||||
///
|
///
|
||||||
/// **Arguments**
|
/// **Arguments**
|
||||||
@ -87,10 +65,10 @@ pub trait RedisMessage:
|
|||||||
///
|
///
|
||||||
/// **Returns**
|
/// **Returns**
|
||||||
/// - a new `Publisher` instance
|
/// - a new `Publisher` instance
|
||||||
pub async fn publisher<RM: RedisMessage, RC: RedisClient>(
|
pub async fn publisher<RM: BusMessage, RC: RedisClient>(
|
||||||
redis: &RC,
|
redis: &RC,
|
||||||
) -> Result<publish::Publisher<RM>> {
|
) -> Result<publish::RedisPublisher<RM>> {
|
||||||
publish::Publisher::new(redis).await
|
publish::RedisPublisher::new(redis).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 🐎 » **subscriber**: create bus subscriber
|
/// 🐎 » **subscriber**: create bus subscriber
|
||||||
@ -100,10 +78,10 @@ pub async fn publisher<RM: RedisMessage, RC: RedisClient>(
|
|||||||
///
|
///
|
||||||
/// **Returns**
|
/// **Returns**
|
||||||
/// - a new `Subscriber` instance
|
/// - a new `Subscriber` instance
|
||||||
pub async fn subscriber<RM: RedisMessage, RC: RedisClient>(
|
pub async fn subscriber<RM: BusMessage, RC: RedisClient>(
|
||||||
redis: &RC,
|
redis: &RC,
|
||||||
) -> Result<subscribe::Subscriber<RM>> {
|
) -> Result<subscribe::RedisSubscriber<RM>> {
|
||||||
subscribe::Subscriber::new(redis).await
|
subscribe::RedisSubscriber::new(redis).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 🐎 » **pubsub*: create bus publisher and subscriber
|
/// 🐎 » **pubsub*: create bus publisher and subscriber
|
||||||
@ -113,11 +91,11 @@ pub async fn subscriber<RM: RedisMessage, RC: RedisClient>(
|
|||||||
///
|
///
|
||||||
/// **Returns**
|
/// **Returns**
|
||||||
/// - a tuple containing a `Publisher` and a `Subscriber` instance
|
/// - a tuple containing a `Publisher` and a `Subscriber` instance
|
||||||
pub async fn pubsub<RO: RedisMessage, T: RedisClient>(
|
pub async fn pubsub<RO: BusMessage, T: RedisClient>(
|
||||||
redis: &T,
|
redis: &T,
|
||||||
) -> Result<(publish::Publisher<RO>, subscribe::Subscriber<RO>)> {
|
) -> Result<(publish::RedisPublisher<RO>, subscribe::RedisSubscriber<RO>)> {
|
||||||
let publisher = publish::Publisher::new(redis).await?;
|
let publisher = publish::RedisPublisher::new(redis).await?;
|
||||||
let subscriber = subscribe::Subscriber::new(redis).await?;
|
let subscriber = subscribe::RedisSubscriber::new(redis).await?;
|
||||||
|
|
||||||
Ok((publisher, subscriber))
|
Ok((publisher, subscriber))
|
||||||
}
|
}
|
||||||
@ -1,20 +1,22 @@
|
|||||||
use {
|
use {
|
||||||
super::{key, PrefixedPubSub, RedisClient, RedisMessage, KEY_PREFIX},
|
super::{key, BusMessage, PrefixedPubSub, RedisClient, KEY_PREFIX},
|
||||||
|
crate::rustlers::bus::PublisherTrait,
|
||||||
eyre::Result,
|
eyre::Result,
|
||||||
redis::{aio::MultiplexedConnection, AsyncCommands},
|
redis::{aio::MultiplexedConnection, AsyncCommands},
|
||||||
|
tonic::async_trait,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// 🐎 » bus **Publisher**
|
/// 🐎 » bus **Publisher**
|
||||||
///
|
///
|
||||||
/// allows to push a message or resource to the bus
|
/// allows to push a message or resource to the bus
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Publisher<RM: RedisMessage> {
|
pub struct RedisPublisher<RM: BusMessage> {
|
||||||
conn: MultiplexedConnection,
|
conn: MultiplexedConnection,
|
||||||
key_prefix: String,
|
key_prefix: String,
|
||||||
resource_type: std::marker::PhantomData<RM>,
|
resource_type: std::marker::PhantomData<RM>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<RM: RedisMessage> PrefixedPubSub for Publisher<RM> {
|
impl<RM: BusMessage> PrefixedPubSub for RedisPublisher<RM> {
|
||||||
fn get_prefix(&self) -> String {
|
fn get_prefix(&self) -> String {
|
||||||
self.key_prefix.clone()
|
self.key_prefix.clone()
|
||||||
}
|
}
|
||||||
@ -25,7 +27,7 @@ impl<RM: RedisMessage> PrefixedPubSub for Publisher<RM> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<RM: RedisMessage> Publisher<RM> {
|
impl<RM: BusMessage> RedisPublisher<RM> {
|
||||||
/// 🐎 » create a new bus publisher
|
/// 🐎 » create a new bus publisher
|
||||||
pub async fn new<RC>(redis: &RC) -> Result<Self>
|
pub async fn new<RC>(redis: &RC) -> Result<Self>
|
||||||
where
|
where
|
||||||
@ -40,12 +42,15 @@ impl<RM: RedisMessage> Publisher<RM> {
|
|||||||
resource_type: std::marker::PhantomData,
|
resource_type: std::marker::PhantomData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<RM: BusMessage> PublisherTrait<RM> for RedisPublisher<RM> {
|
||||||
/// 🐎 » publish a message to the bus
|
/// 🐎 » publish a message to the bus
|
||||||
pub async fn publish(&mut self, value: RM) -> Result<()> {
|
async fn publish(&mut self, value: RM) -> Result<()> {
|
||||||
let obj_key = key(self.get_prefix(), value.to_redis_key());
|
let obj_key = key(self.get_prefix(), value.to_bus_key());
|
||||||
// set hash key
|
// set hash key
|
||||||
self.conn.hset_multiple(&obj_key, value.to_redis_val().as_slice()).await?;
|
self.conn.hset_multiple(&obj_key, value.to_bus_val().as_slice()).await?;
|
||||||
|
|
||||||
// publish to the appropriate channel
|
// publish to the appropriate channel
|
||||||
self.conn.publish(&obj_key, value.as_message()).await?;
|
self.conn.publish(&obj_key, value.as_message()).await?;
|
||||||
@ -1,5 +1,6 @@
|
|||||||
use {
|
use {
|
||||||
super::{key, PrefixedPubSub, RedisClient, RedisMessage, KEY_PREFIX},
|
super::{key, PrefixedPubSub, RedisClient, KEY_PREFIX},
|
||||||
|
crate::rustlers::bus::{BusMessage, SubscriberTrait},
|
||||||
eyre::Result,
|
eyre::Result,
|
||||||
futures::StreamExt,
|
futures::StreamExt,
|
||||||
lool::{fail, s},
|
lool::{fail, s},
|
||||||
@ -8,6 +9,7 @@ use {
|
|||||||
subject::SubjectThreads, subscription::Subscription,
|
subject::SubjectThreads, subscription::Subscription,
|
||||||
},
|
},
|
||||||
std::convert::Infallible,
|
std::convert::Infallible,
|
||||||
|
tonic::async_trait,
|
||||||
};
|
};
|
||||||
|
|
||||||
// IDEA: create another version using tokio broadcast channels
|
// IDEA: create another version using tokio broadcast channels
|
||||||
@ -16,7 +18,7 @@ use {
|
|||||||
/// 🐎 » bus **Subscriber**
|
/// 🐎 » bus **Subscriber**
|
||||||
///
|
///
|
||||||
/// allows to subscribe to a redis key pattern and receive messages from the redis bus
|
/// allows to subscribe to a redis key pattern and receive messages from the redis bus
|
||||||
pub struct Subscriber<RM: RedisMessage> {
|
pub struct RedisSubscriber<RM: BusMessage> {
|
||||||
// TODO: replace with storing tokio multiplexed connection like in publish.rs when redis@0.26.0
|
// TODO: replace with storing tokio multiplexed connection like in publish.rs when redis@0.26.0
|
||||||
// is released see https://github.com/redis-rs/redis-rs/issues/1137.
|
// is released see https://github.com/redis-rs/redis-rs/issues/1137.
|
||||||
// this way we can just clone the connection when needing instead of storing the
|
// this way we can just clone the connection when needing instead of storing the
|
||||||
@ -27,7 +29,7 @@ pub struct Subscriber<RM: RedisMessage> {
|
|||||||
pattern: String,
|
pattern: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<RM: RedisMessage> PrefixedPubSub for Subscriber<RM> {
|
impl<RM: BusMessage> PrefixedPubSub for RedisSubscriber<RM> {
|
||||||
fn get_prefix(&self) -> String {
|
fn get_prefix(&self) -> String {
|
||||||
self.key_prefix.clone()
|
self.key_prefix.clone()
|
||||||
}
|
}
|
||||||
@ -38,7 +40,7 @@ impl<RM: RedisMessage> PrefixedPubSub for Subscriber<RM> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<RM: RedisMessage> Subscriber<RM> {
|
impl<RM: BusMessage> RedisSubscriber<RM> {
|
||||||
/// 🐎 » create a new bus subscriber
|
/// 🐎 » create a new bus subscriber
|
||||||
pub async fn new<RC>(redis: &RC) -> Result<Self>
|
pub async fn new<RC>(redis: &RC) -> Result<Self>
|
||||||
where
|
where
|
||||||
@ -63,20 +65,6 @@ impl<RM: RedisMessage> Subscriber<RM> {
|
|||||||
key(self.get_prefix(), self.pattern.clone())
|
key(self.get_prefix(), self.pattern.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 🐎 » **stream**
|
|
||||||
///
|
|
||||||
/// returns an `Observable` stream of messages from the redis bus
|
|
||||||
pub async fn stream(&mut self) -> Result<CloneableBoxOpThreads<RM, Infallible>> {
|
|
||||||
if self.subject.is_none() {
|
|
||||||
self.start_streaming().await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.subject.as_ref() {
|
|
||||||
Some(subject) => Ok(subject.clone().box_it()),
|
|
||||||
None => fail!("Could not start streaming messages from redis bus"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// subscribe to the redis feed
|
/// subscribe to the redis feed
|
||||||
async fn start_streaming(&mut self) -> Result<()> {
|
async fn start_streaming(&mut self) -> Result<()> {
|
||||||
if self.subject.is_none() {
|
if self.subject.is_none() {
|
||||||
@ -118,3 +106,20 @@ impl<RM: RedisMessage> Subscriber<RM> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<RM: BusMessage> SubscriberTrait<RM> for RedisSubscriber<RM> {
|
||||||
|
/// 🐎 » **stream**
|
||||||
|
///
|
||||||
|
/// returns an `Observable` stream of messages from the redis bus
|
||||||
|
async fn stream(&mut self) -> Result<CloneableBoxOpThreads<RM, Infallible>> {
|
||||||
|
if self.subject.is_none() {
|
||||||
|
self.start_streaming().await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.subject.as_ref() {
|
||||||
|
Some(subject) => Ok(subject.clone().box_it()),
|
||||||
|
None => fail!("Could not start streaming messages from redis bus"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,7 +2,10 @@ pub extern crate chrono;
|
|||||||
pub extern crate eyre;
|
pub extern crate eyre;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
super::bus::{RedisMessage, ToFromRedisMessage, ToRedisKey, ToRedisVal},
|
super::{
|
||||||
|
bus::{BusMessage, ToBusKey, ToBusVal, ToFromBusMessage},
|
||||||
|
svc::RustlerMsg,
|
||||||
|
},
|
||||||
crate::entities::{market, ticker},
|
crate::entities::{market, ticker},
|
||||||
async_trait::async_trait,
|
async_trait::async_trait,
|
||||||
chrono::{DateTime, Local},
|
chrono::{DateTime, Local},
|
||||||
@ -12,6 +15,7 @@ use {
|
|||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt::{self, Display, Formatter},
|
fmt::{self, Display, Formatter},
|
||||||
},
|
},
|
||||||
|
tokio::sync::mpsc::Sender,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// 🐎 » a struct representing the status of a rustler at a given time
|
/// 🐎 » a struct representing the status of a rustler at a given time
|
||||||
@ -75,8 +79,8 @@ impl Display for Quote {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToRedisVal for Quote {
|
impl ToBusVal for Quote {
|
||||||
fn to_redis_val(&self) -> Vec<(String, String)> {
|
fn to_bus_val(&self) -> Vec<(String, String)> {
|
||||||
let market_hours_u8: u8 = self.market_hours.clone().into();
|
let market_hours_u8: u8 = self.market_hours.clone().into();
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
@ -90,13 +94,13 @@ impl ToRedisVal for Quote {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToRedisKey for Quote {
|
impl ToBusKey for Quote {
|
||||||
fn to_redis_key(&self) -> String {
|
fn to_bus_key(&self) -> String {
|
||||||
format!("quote:{}:{}", self.market, self.id)
|
format!("quote:{}:{}", self.market, self.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToFromRedisMessage for Quote {
|
impl ToFromBusMessage for Quote {
|
||||||
/// 🐎 » converts a `Quote` to a serialized message that can be sent over a redis channel
|
/// 🐎 » converts a `Quote` to a serialized message that can be sent over a redis channel
|
||||||
///
|
///
|
||||||
/// the message is in the format `id¦market¦price¦change_percent¦time¦market_hours`
|
/// the message is in the format `id¦market¦price¦change_percent¦time¦market_hours`
|
||||||
@ -158,7 +162,7 @@ impl PartialEq<Quote> for Quote {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RedisMessage for Quote {}
|
impl BusMessage for Quote {}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RustlerOpts {
|
pub struct RustlerOpts {
|
||||||
@ -175,13 +179,6 @@ impl Default for RustlerOpts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
pub struct ScrapperCallbacks {
|
|
||||||
pub on_connected: Option<fn() -> Result<()>>,
|
|
||||||
pub on_disconnected: Option<fn() -> Result<()>>,
|
|
||||||
pub on_message: Option<fn(message: Quote) -> Result<()>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 🐎 » a scruct representing a ticker
|
/// 🐎 » a scruct representing a ticker
|
||||||
///
|
///
|
||||||
/// in `rustler` a ticker is the union between a symbol (stock identifier) and its market
|
/// in `rustler` a ticker is the union between a symbol (stock identifier) and its market
|
||||||
@ -246,8 +243,9 @@ pub trait RustlerAccessor {
|
|||||||
fn tickers_mut(&mut self) -> &mut HashMap<String, Ticker>;
|
fn tickers_mut(&mut self) -> &mut HashMap<String, Ticker>;
|
||||||
fn set_tickers(&mut self, tickers: HashMap<String, Ticker>);
|
fn set_tickers(&mut self, tickers: HashMap<String, Ticker>);
|
||||||
|
|
||||||
fn callbacks(&self) -> &Option<ScrapperCallbacks>;
|
fn msg_sender(&self) -> &Option<Sender<RustlerMsg>>;
|
||||||
fn set_callbacks(&mut self, callbacks: Option<ScrapperCallbacks>);
|
fn msg_sender_mut(&mut self) -> &mut Option<Sender<RustlerMsg>>;
|
||||||
|
fn set_msg_sender(&mut self, sender: Option<Sender<RustlerMsg>>);
|
||||||
// #endregion
|
// #endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,20 +279,20 @@ pub trait Rustler: RustlerAccessor + Send + Sync {
|
|||||||
RustlerStatus::Disconnected => {
|
RustlerStatus::Disconnected => {
|
||||||
self.set_last_stop(Some(Local::now()));
|
self.set_last_stop(Some(Local::now()));
|
||||||
|
|
||||||
if let Some(callbacks) = self.callbacks() {
|
// if let Some(callbacks) = self.callbacks() {
|
||||||
if let Some(on_disconnected) = callbacks.on_disconnected {
|
// if let Some(on_disconnected) = callbacks.on_disconnected {
|
||||||
on_disconnected()?;
|
// on_disconnected()?;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
RustlerStatus::Connected => {
|
RustlerStatus::Connected => {
|
||||||
self.set_last_run(Some(Local::now()));
|
self.set_last_run(Some(Local::now()));
|
||||||
|
|
||||||
if let Some(callbacks) = self.callbacks() {
|
// if let Some(callbacks) = self.callbacks() {
|
||||||
if let Some(on_connected) = callbacks.on_connected {
|
// if let Some(on_connected) = callbacks.on_connected {
|
||||||
on_connected()?;
|
// on_connected()?;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
@ -343,17 +341,6 @@ pub trait Rustler: RustlerAccessor + Send + Sync {
|
|||||||
self.on_delete(new_tickers)?;
|
self.on_delete(new_tickers)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// registers a new quote by passing it to the on_message callback
|
|
||||||
fn register_quote(&self, quote: Quote) -> Result<()> {
|
|
||||||
if let Some(callbacks) = self.callbacks() {
|
|
||||||
if let Some(on_message) = callbacks.on_message {
|
|
||||||
on_message(quote)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// macro that expands to the accessor functions for a `Rustler` struct
|
/// macro that expands to the accessor functions for a `Rustler` struct
|
||||||
@ -458,11 +445,21 @@ macro_rules! rustler_accessors {
|
|||||||
fn set_tickers(&mut self, tickers: HashMap<String, $crate::rustlers::Ticker>) {
|
fn set_tickers(&mut self, tickers: HashMap<String, $crate::rustlers::Ticker>) {
|
||||||
self.tickers = tickers;
|
self.tickers = tickers;
|
||||||
}
|
}
|
||||||
fn callbacks(&self) -> &Option<$crate::rustlers::ScrapperCallbacks> {
|
fn msg_sender(
|
||||||
&self.callbacks
|
&self,
|
||||||
|
) -> &Option<tokio::sync::mpsc::Sender<$crate::rustlers::svc::RustlerMsg>> {
|
||||||
|
&self.msg_sender
|
||||||
}
|
}
|
||||||
fn set_callbacks(&mut self, callbacks: Option<$crate::rustlers::ScrapperCallbacks>) {
|
fn msg_sender_mut(
|
||||||
self.callbacks = callbacks;
|
&mut self,
|
||||||
|
) -> &mut Option<tokio::sync::mpsc::Sender<$crate::rustlers::svc::RustlerMsg>> {
|
||||||
|
&mut self.msg_sender
|
||||||
|
}
|
||||||
|
fn set_msg_sender(
|
||||||
|
&mut self,
|
||||||
|
sender: Option<tokio::sync::mpsc::Sender<$crate::rustlers::svc::RustlerMsg>>,
|
||||||
|
) {
|
||||||
|
self.msg_sender = sender;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -491,7 +488,7 @@ macro_rules! rustler {
|
|||||||
last_update: Option<$crate::rustlers::chrono::DateTime<$crate::rustlers::chrono::Local>>,
|
last_update: Option<$crate::rustlers::chrono::DateTime<$crate::rustlers::chrono::Local>>,
|
||||||
opts: $crate::rustlers::RustlerOpts,
|
opts: $crate::rustlers::RustlerOpts,
|
||||||
tickers: HashMap<String, $crate::rustlers::Ticker>,
|
tickers: HashMap<String, $crate::rustlers::Ticker>,
|
||||||
callbacks: Option<$crate::rustlers::ScrapperCallbacks>,
|
msg_sender: Option<tokio::sync::mpsc::Sender<$crate::rustlers::svc::RustlerMsg>>,
|
||||||
$($fields)*
|
$($fields)*
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,8 +2,12 @@ use {
|
|||||||
super::{
|
super::{
|
||||||
rustler::{Rustler, Ticker},
|
rustler::{Rustler, Ticker},
|
||||||
rustlerjar::RustlerJar,
|
rustlerjar::RustlerJar,
|
||||||
|
MarketHourType,
|
||||||
|
},
|
||||||
|
crate::{
|
||||||
|
entities::{market, sea_orm::DatabaseConnection, ticker},
|
||||||
|
rustlers::{bus::PublisherTrait, Quote},
|
||||||
},
|
},
|
||||||
crate::entities::{market, sea_orm::DatabaseConnection, ticker},
|
|
||||||
eyre::Result,
|
eyre::Result,
|
||||||
lool::{
|
lool::{
|
||||||
logger::{info, warn},
|
logger::{info, warn},
|
||||||
@ -13,36 +17,54 @@ use {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
std::sync::Arc,
|
std::sync::Arc,
|
||||||
tokio::sync::Mutex,
|
tokio::{
|
||||||
|
spawn,
|
||||||
|
sync::{mpsc::Sender, Mutex},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// interface MarketExecData {
|
/// **🐎 » Rustler Message**
|
||||||
// entity: MarketModel;
|
pub enum RustlerMsg {
|
||||||
// startJob?: Job;
|
QuoteMsg(Quote),
|
||||||
// stopJob?: Job;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// interface ScrapperData {
|
/// **🐎 » create a quote message**
|
||||||
// markets?: MarketExecData[];
|
#[inline]
|
||||||
// // subscription?: Subscription
|
pub fn quote(
|
||||||
// }]]
|
id: String,
|
||||||
|
market: String,
|
||||||
// struct MarketExecData {
|
price: f64,
|
||||||
// entity: MarketModel,
|
change_percent: f64,
|
||||||
// startJob?: Job,
|
time: i64,
|
||||||
// stopJob?: Job,
|
market_hours: MarketHourType,
|
||||||
// }
|
) -> RustlerMsg {
|
||||||
|
RustlerMsg::QuoteMsg(Quote {
|
||||||
|
id,
|
||||||
|
market,
|
||||||
|
price,
|
||||||
|
change_percent,
|
||||||
|
time,
|
||||||
|
market_hours,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// **🐎 » Rustlers Service**
|
/// **🐎 » Rustlers Service**
|
||||||
///
|
///
|
||||||
/// The `RustlersSvc` is a service that manages the rustlers and orchestrates their executions.
|
/// The `RustlersSvc` is a service that manages the rustlers and orchestrates their executions.
|
||||||
pub struct RustlersSvc {
|
pub struct RustlersSvc<P>
|
||||||
|
where
|
||||||
|
P: PublisherTrait<Quote> + Send + Sync + 'static + Clone,
|
||||||
|
{
|
||||||
market_svc: market::Service,
|
market_svc: market::Service,
|
||||||
sched: Scheduler,
|
sched: Scheduler,
|
||||||
rustlers: RustlerJar,
|
rustlers: RustlerJar,
|
||||||
|
publisher: P,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RustlersSvc {
|
impl<Publisher> RustlersSvc<Publisher>
|
||||||
|
where
|
||||||
|
Publisher: PublisherTrait<Quote> + Send + Sync + 'static + Clone,
|
||||||
|
{
|
||||||
/// **🐎 » create service**
|
/// **🐎 » create service**
|
||||||
///
|
///
|
||||||
/// creates a new instance of the `RustlersSvc`
|
/// creates a new instance of the `RustlersSvc`
|
||||||
@ -53,7 +75,7 @@ impl RustlersSvc {
|
|||||||
///
|
///
|
||||||
/// **Returns**
|
/// **Returns**
|
||||||
/// the created `RustlersSvc` instance
|
/// the created `RustlersSvc` instance
|
||||||
pub async fn new(conn: DatabaseConnection, rustlers: RustlerJar) -> Self {
|
pub async fn new(conn: DatabaseConnection, rustlers: RustlerJar, publisher: Publisher) -> Self {
|
||||||
let market_svc = market::Service::new(conn).await;
|
let market_svc = market::Service::new(conn).await;
|
||||||
let sched = Scheduler::new();
|
let sched = Scheduler::new();
|
||||||
|
|
||||||
@ -61,6 +83,7 @@ impl RustlersSvc {
|
|||||||
market_svc,
|
market_svc,
|
||||||
rustlers,
|
rustlers,
|
||||||
sched,
|
sched,
|
||||||
|
publisher,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,8 +95,26 @@ impl RustlersSvc {
|
|||||||
info!("Starting rustlers");
|
info!("Starting rustlers");
|
||||||
let markets = self.market_svc.get_all_with_tickers().await?;
|
let markets = self.market_svc.get_all_with_tickers().await?;
|
||||||
|
|
||||||
|
if markets.len() > 0 {
|
||||||
|
let (sender, mut receiver) = tokio::sync::mpsc::channel(100);
|
||||||
|
|
||||||
for (market, tickers) in markets {
|
for (market, tickers) in markets {
|
||||||
self.schedule_rustler_for((market, tickers)).await?;
|
self.schedule_rustler_for((market, tickers), sender.clone()).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut publisher = self.publisher.clone();
|
||||||
|
spawn(async move {
|
||||||
|
while let Some(msg) = receiver.recv().await {
|
||||||
|
match msg {
|
||||||
|
RustlerMsg::QuoteMsg(quote) => publisher.publish(quote).await?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Rustler message receiver stopped");
|
||||||
|
Ok::<(), eyre::Report>(())
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
warn!("No markets found with tickers");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -101,6 +142,7 @@ impl RustlersSvc {
|
|||||||
async fn schedule_rustler_for(
|
async fn schedule_rustler_for(
|
||||||
&mut self,
|
&mut self,
|
||||||
market: (market::Model, Vec<ticker::Model>),
|
market: (market::Model, Vec<ticker::Model>),
|
||||||
|
sender: Sender<RustlerMsg>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let (market, tickers) = market;
|
let (market, tickers) = market;
|
||||||
let tickers: Vec<Ticker> = tickers.into_iter().map(|t| Ticker::from(&t, &market)).collect();
|
let tickers: Vec<Ticker> = tickers.into_iter().map(|t| Ticker::from(&t, &market)).collect();
|
||||||
@ -109,6 +151,12 @@ impl RustlersSvc {
|
|||||||
let rustler = self.rustlers.get(&market);
|
let rustler = self.rustlers.get(&market);
|
||||||
|
|
||||||
if let Some(rustler) = rustler {
|
if let Some(rustler) = rustler {
|
||||||
|
{
|
||||||
|
let mut rustler = rustler.lock().await;
|
||||||
|
info!("Setting message sender for rustler '{}'", rustler.name());
|
||||||
|
rustler.set_msg_sender(Some(sender))
|
||||||
|
}
|
||||||
|
|
||||||
if let Some((start, stop)) = rules {
|
if let Some((start, stop)) = rules {
|
||||||
let start_name = format!("start-rustler-{}", market.short_name);
|
let start_name = format!("start-rustler-{}", market.short_name);
|
||||||
let end_name = format!("end-rustler-{}", market.short_name);
|
let end_name = format!("end-rustler-{}", market.short_name);
|
||||||
|
|||||||
@ -7,15 +7,26 @@
|
|||||||
"settings": {
|
"settings": {
|
||||||
"terminal.integrated.env.windows": {
|
"terminal.integrated.env.windows": {
|
||||||
// allows to run the command in the terminal for development purposes
|
// allows to run the command in the terminal for development purposes
|
||||||
"PATH": "${workspaceFolder}/target/debug;${env:PATH}",
|
"PATH": "${workspaceFolder}/target/debug;${env:PATH}"
|
||||||
},
|
},
|
||||||
"lucodear-icons.activeIconPack": "rust_ferris",
|
"lucodear-icons.activeIconPack": "rust_ferris",
|
||||||
"lucodear-icons.folders.associations": {
|
"lucodear-icons.folders.associations": {
|
||||||
".cargo": "rust",
|
".cargo": "rust"
|
||||||
},
|
},
|
||||||
"lucodear-icons.files.associations": {
|
"lucodear-icons.folders.customClones": [
|
||||||
|
{
|
||||||
|
"name": "redis",
|
||||||
|
"base": "database",
|
||||||
|
"color": "deep-orange-400",
|
||||||
|
"folderNames": ["redis"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "bus",
|
||||||
|
"base": "pipe",
|
||||||
|
"color": "blue-500",
|
||||||
|
"folderNames": ["bus"]
|
||||||
|
}
|
||||||
|
],
|
||||||
"rust-analyzer.checkOnSave": true
|
"rust-analyzer.checkOnSave": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user