Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] Add Rust Client #95

Merged
merged 2 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ exclude = [".github/"]

[workspace]
members = [
"bothan-api/client/rust-client",
"bothan-api/server",
"bothan-api/server-cli",
"bothan-binance",
Expand All @@ -32,6 +33,9 @@ futures = "0.3.30"
humantime-serde = "1.1.1"
itertools = "0.13.0"
mockito = "1.4.0"
prost = "0.13.1"
protoc-gen-prost = "0.4.0"
protoc-gen-tonic = "0.4.1"
rand = "0.8.5"
reqwest = { version = "0.12.3", features = ["json"] }
rust_decimal = "1.10.2"
Expand All @@ -42,11 +46,14 @@ thiserror = "1.0.57"
tokio = { version = "1.36.0", features = ["full"] }
tokio-tungstenite = { version = "0.24.0", features = ["native-tls"] }
tokio-util = "0.7.10"
tonic = "0.12.1"
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
url = "2.5.0"

bothan-core = { path = "bothan-core" }

bothan-client = { path = "bothan-api/client/rust-client" }
bothan-api = { path = "bothan-api/server" }

bothan-binance = { path = "bothan-binance" }
Expand Down
21 changes: 21 additions & 0 deletions bothan-api/client/rust-client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "bothan-client"
version = "0.0.1-alpha.4"
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
exclude.workspace = true

[dependencies]
prost = { workspace = true }
protoc-gen-prost = { workspace = true }
protoc-gen-tonic = { workspace = true }
tokio = { workspace = true }
tonic = { workspace = true }
reqwest = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
url = { workspace = true }

pbjson = "0.7.0"
7 changes: 7 additions & 0 deletions bothan-api/client/rust-client/src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![allow(unused_imports)]
#![allow(dead_code)]
pub use grpc::GrpcClient;
pub use rest::RestClient;

mod grpc;
mod rest;
70 changes: 70 additions & 0 deletions bothan-api/client/rust-client/src/client/grpc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use std::str::FromStr;
use std::sync::Arc;

use tokio::sync::Mutex;
use tonic::transport::{Channel, Endpoint};
use tonic::{Request, Status};

use crate::proto::bothan::v1::{
BothanServiceClient, GetInfoRequest, GetInfoResponse, GetPricesRequest, GetPricesResponse,
PushMonitoringRecordsRequest, UpdateRegistryRequest,
};

pub struct GrpcClient {
client: Arc<Mutex<BothanServiceClient<Channel>>>,
}

impl GrpcClient {
pub fn new(client: BothanServiceClient<Channel>) -> Self {
Self {
client: Arc::new(Mutex::new(client)),
}
}

pub async fn connect(addr: &str) -> Result<Self, tonic::transport::Error> {
let endpoint = Endpoint::from_str(addr)?;
let client = Arc::new(Mutex::new(BothanServiceClient::connect(endpoint).await?));
Ok(GrpcClient { client })
}

async fn get_info(&self) -> Result<GetInfoResponse, Status> {
let get_info_request = GetInfoRequest {};
let request = Request::new(get_info_request);
let response = self.client.lock().await.get_info(request).await?;
Ok(response.into_inner())
}

async fn update_registry(&self, ipfs_hash: &str, version: &str) -> Result<(), Status> {
let update_registry_request = UpdateRegistryRequest {
ipfs_hash: ipfs_hash.into(),
version: version.into(),
};
let request = Request::new(update_registry_request);
let _ = self.client.lock().await.update_registry(request).await?;
Ok(())
}

async fn push_monitoring_records(&self, uuid: &str, tx_hash: &str) -> Result<(), Status> {
let push_monitoring_records_request = PushMonitoringRecordsRequest {
uuid: uuid.into(),
tx_hash: tx_hash.into(),
};
let request = Request::new(push_monitoring_records_request);
let _ = self
.client
.lock()
.await
.push_monitoring_records(request)
.await?;
Ok(())
}

async fn get_prices(&self, signal_ids: &[&str]) -> Result<GetPricesResponse, Status> {
let get_prices_request = GetPricesRequest {
signal_ids: signal_ids.iter().map(|s| s.to_string()).collect(),
};
let request = Request::new(get_prices_request);
let response = self.client.lock().await.get_prices(request).await?;
Ok(response.into_inner())
}
}
69 changes: 69 additions & 0 deletions bothan-api/client/rust-client/src/client/rest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use reqwest::{Client, Error};
use url::{ParseError, Url};

use crate::proto::bothan::v1::{GetInfoResponse, GetPricesResponse};

pub struct RestClient {
url: Url,
client: Client,
}

impl RestClient {
pub fn new(url: String) -> Result<Self, ParseError> {
Ok(RestClient {
url: Url::parse(&url)?,
client: Client::new(),
})
}

async fn get_info(&self) -> Result<GetInfoResponse, Error> {
let mut url = self.url.clone();
url.set_path("/info");
let response = self.client.get(url).send().await?.error_for_status()?;
let get_info_response = response.json().await?;
Ok(get_info_response)
}

async fn update_registry(&self, ipfs_hash: &str, version: &str) -> Result<(), Error> {
let mut url = self.url.clone();
url.set_path("/registry");
let payload = serde_json::json!({ "ipfs_hash": ipfs_hash, "version": version });
let _ = self
.client
.post(url)
.json(&payload)
.send()
.await?
.error_for_status()?;
Ok(())
}

async fn push_monitoring_records(&self, uuid: &str, tx_hash: &str) -> Result<(), Error> {
let mut url = self.url.clone();
url.set_path("/monitoring_records");
let payload = serde_json::json!({ "uuid": uuid, "tx_hash": tx_hash });
let _ = self
.client
.post(url)
.json(&payload)
.send()
.await?
.error_for_status()?;
Ok(())
}

async fn get_prices(&self, signal_ids: &[&str]) -> Result<GetPricesResponse, Error> {
let mut url = self.url.clone();
url.set_path("/prices");
let payload = serde_json::json!({ "signal_ids": signal_ids });
let response = self
.client
.post(url)
.json(&payload)
.send()
.await?
.error_for_status()?;
let get_prices_response = response.json().await?;
Ok(get_prices_response)
}
}
2 changes: 2 additions & 0 deletions bothan-api/client/rust-client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod client;
pub mod proto;
6 changes: 6 additions & 0 deletions bothan-api/client/rust-client/src/proto.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pub mod bothan {
pub mod v1 {
pub use bothan_service_client::BothanServiceClient;
include!("proto/bothan.v1.rs");
}
}
132 changes: 132 additions & 0 deletions bothan-api/client/rust-client/src/proto/bothan.v1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// @generated
// This file is @generated by prost-build.
/// GetInfoRequest defines the request message for the GetInfo RPC method.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct GetInfoRequest {
}
/// GetInfoResponse defines the response message for the GetInfo RPC method.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GetInfoResponse {
/// The Bothan version
#[prost(string, tag="1")]
pub bothan_version: ::prost::alloc::string::String,
/// The IPFS hash pointing to the registry data.
#[prost(string, tag="2")]
pub registry_ipfs_hash: ::prost::alloc::string::String,
/// The version requirements for the registry.
#[prost(string, tag="3")]
pub registry_version_requirement: ::prost::alloc::string::String,
/// The active sources the Bothan instance is using
#[prost(string, repeated, tag="4")]
pub active_sources: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
/// Whether or not the Bothan instance has monitoring enabled
#[prost(bool, tag="5")]
pub monitoring_enabled: bool,
}
/// UpdateRegistryRequest defines the request message for the UpdateRegistry RPC method.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct UpdateRegistryRequest {
/// The IPFS hash pointing to the registry data.
#[prost(string, tag="1")]
pub ipfs_hash: ::prost::alloc::string::String,
/// The version of the registry.
#[prost(string, tag="2")]
pub version: ::prost::alloc::string::String,
}
/// UpdateRegistryResponse defines the response message for the UpdateRegistry RPC method.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct UpdateRegistryResponse {
}
/// PushMonitoringRecordsRequest defines the request message for the PushMonitoringRecords RPC method.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PushMonitoringRecordsRequest {
/// The uuid of a list of monitoring records to be pushed to the monitoring service.
#[prost(string, tag="1")]
pub uuid: ::prost::alloc::string::String,
/// The tx hash of the transaction associated with the monitoring records.
#[prost(string, tag="2")]
pub tx_hash: ::prost::alloc::string::String,
}
/// PushMonitoringRecordsResponse defines the response message for the PushMonitoringRecords RPC method.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct PushMonitoringRecordsResponse {
}
/// GetPricesRequest defines the request message for the GetPrices RPC method.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GetPricesRequest {
/// A list of signal IDs for which the prices are being requested.
#[prost(string, repeated, tag="1")]
pub signal_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
}
/// GetPricesResponse defines the response message for the GetPrices RPC method.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GetPricesResponse {
/// A unique identifier for the response.
#[prost(string, tag="1")]
pub uuid: ::prost::alloc::string::String,
/// A list of prices for the requested signal IDs.
#[prost(message, repeated, tag="2")]
pub prices: ::prost::alloc::vec::Vec<Price>,
}
/// Price defines the price information for a signal ID.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Price {
/// The signal ID.
#[prost(string, tag="1")]
pub signal_id: ::prost::alloc::string::String,
/// The price value associated with this signal ID.
#[prost(uint64, tag="2")]
pub price: u64,
/// The status of the signal ID.
#[prost(enumeration="Status", tag="3")]
pub status: i32,
}
/// Status defines the status for a signal ID.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum Status {
/// Default status, should not be used.
Unspecified = 0,
/// Indicates that the signal ID is not supported.
Unsupported = 1,
/// Indicates that the signal ID is currently unavailable.
Unavailable = 2,
/// Indicates that the signal ID is available.
Available = 3,
}
impl Status {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
Status::Unspecified => "STATUS_UNSPECIFIED",
Status::Unsupported => "STATUS_UNSUPPORTED",
Status::Unavailable => "STATUS_UNAVAILABLE",
Status::Available => "STATUS_AVAILABLE",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"STATUS_UNSPECIFIED" => Some(Self::Unspecified),
"STATUS_UNSUPPORTED" => Some(Self::Unsupported),
"STATUS_UNAVAILABLE" => Some(Self::Unavailable),
"STATUS_AVAILABLE" => Some(Self::Available),
_ => None,
}
}
}
include!("bothan.v1.tonic.rs");
include!("bothan.v1.serde.rs");
// @@protoc_insertion_point(module)
Loading
Loading