use std::collections::HashMap;
use reqwest::{Client, Method, RequestBuilder};
use serde::de::DeserializeOwned;
use serde::Deserialize;
use crate::error::ForeciteError;
use crate::stream::ForeciteStream;
use crate::types::*;
const DEFAULT_BASE: &str = "https://api.forecite.dev";
const DEFAULT_WS: &str = "wss://ws.forecite.dev";
#[derive(Deserialize)]
struct Envelope<T> {
data: T,
}
pub struct ForeciteBuilder {
api_key: String,
base_url: String,
ws_url: String,
}
impl ForeciteBuilder {
pub fn base_url(mut self, url: impl Into<String>) -> Self {
self.base_url = url.into().trim_end_matches('/').to_string();
self
}
pub fn ws_url(mut self, url: impl Into<String>) -> Self {
self.ws_url = url.into().trim_end_matches('/').to_string();
self
}
pub fn build(self) -> Forecite {
Forecite {
api_key: self.api_key,
base_url: self.base_url,
ws_url: self.ws_url,
http: Client::new(),
}
}
}
#[derive(Clone)]
pub struct Forecite {
api_key: String,
base_url: String,
ws_url: String,
http: Client,
}
impl Forecite {
pub fn new(api_key: impl Into<String>) -> Self {
Self::builder(api_key).build()
}
pub fn builder(api_key: impl Into<String>) -> ForeciteBuilder {
ForeciteBuilder {
api_key: api_key.into(),
base_url: DEFAULT_BASE.to_string(),
ws_url: DEFAULT_WS.to_string(),
}
}
fn request(&self, method: Method, path: &str) -> RequestBuilder {
self.http
.request(method, format!("{}{}", self.base_url, path))
.bearer_auth(&self.api_key)
}
async fn send<T: DeserializeOwned>(&self, builder: RequestBuilder) -> Result<T, ForeciteError> {
let resp = builder.send().await?;
let status = resp.status();
let bytes = resp.bytes().await?;
if !status.is_success() {
let (code, message) = parse_error(&bytes);
return Err(ForeciteError::Api {
status: status.as_u16(),
code,
message,
});
}
Ok(serde_json::from_slice(&bytes)?)
}
async fn send_data<T: DeserializeOwned>(
&self,
builder: RequestBuilder,
) -> Result<T, ForeciteError> {
let env: Envelope<T> = self.send(builder).await?;
Ok(env.data)
}
pub async fn me(&self) -> Result<KeyInfo, ForeciteError> {
self.send_data(self.request(Method::GET, "/v1/me")).await
}
pub async fn usage(&self, days: u32) -> Result<UsageInfo, ForeciteError> {
self.send_data(self.request(Method::GET, "/v1/usage").query(&[("days", days)]))
.await
}
pub async fn score(
&self,
artifact: &Artifact,
options: Option<&ScoreOptions>,
) -> Result<Verdict, ForeciteError> {
let mut body = serde_json::json!({ "artifact": artifact });
if let Some(opts) = options {
body["options"] = serde_json::to_value(opts)?;
}
self.send_data(self.request(Method::POST, "/v1/score").json(&body))
.await
}
pub async fn feeds_list(&self, query: &FeedsQuery) -> Result<FeedList, ForeciteError> {
self.send(self.request(Method::GET, "/v1/feeds").query(query))
.await
}
pub async fn feeds_get(&self, id: &str) -> Result<FeedItem, ForeciteError> {
self.send_data(self.request(Method::GET, &format!("/v1/feeds/{}", encode(id))))
.await
}
pub async fn providers_list(&self) -> Result<Vec<Provider>, ForeciteError> {
self.send_data(self.request(Method::GET, "/v1/providers"))
.await
}
pub async fn providers_get(&self, id: &str) -> Result<Provider, ForeciteError> {
self.send_data(self.request(Method::GET, &format!("/v1/providers/{}", encode(id))))
.await
}
pub async fn sources_list(&self) -> Result<Vec<String>, ForeciteError> {
self.send_data(self.request(Method::GET, "/v1/sources")).await
}
pub async fn sources_get(&self, name: &str) -> Result<SourceInfo, ForeciteError> {
self.send_data(self.request(Method::GET, &format!("/v1/sources/{}", encode(name))))
.await
}
pub async fn symbols_list(&self, limit: Option<u32>) -> Result<Vec<SymbolInfo>, ForeciteError> {
let mut req = self.request(Method::GET, "/v1/symbols");
if let Some(l) = limit {
req = req.query(&[("limit", l)]);
}
self.send_data(req).await
}
pub async fn symbols_get(&self, ticker: &str) -> Result<SymbolInfo, ForeciteError> {
self.send_data(self.request(Method::GET, &format!("/v1/symbols/{}", encode(ticker))))
.await
}
pub async fn tags_list(&self) -> Result<HashMap<String, Vec<TagValue>>, ForeciteError> {
self.send_data(self.request(Method::GET, "/v1/tags")).await
}
pub async fn tags_get(&self, dimension: &str) -> Result<TagDimensionValues, ForeciteError> {
self.send_data(self.request(Method::GET, &format!("/v1/tags/{}", encode(dimension))))
.await
}
pub async fn webhooks_list(&self) -> Result<Vec<Webhook>, ForeciteError> {
self.send_data(self.request(Method::GET, "/v1/webhooks"))
.await
}
pub async fn webhooks_create(
&self,
url: &str,
description: Option<&str>,
) -> Result<Webhook, ForeciteError> {
let body = serde_json::json!({ "url": url, "description": description });
self.send_data(self.request(Method::POST, "/v1/webhooks").json(&body))
.await
}
pub async fn webhooks_get(&self, id: &str) -> Result<Webhook, ForeciteError> {
self.send_data(self.request(Method::GET, &format!("/v1/webhooks/{}", encode(id))))
.await
}
pub async fn webhooks_update(
&self,
id: &str,
update: &UpdateWebhook,
) -> Result<Webhook, ForeciteError> {
self.send_data(
self.request(Method::PATCH, &format!("/v1/webhooks/{}", encode(id)))
.json(update),
)
.await
}
pub async fn webhooks_delete(&self, id: &str) -> Result<(), ForeciteError> {
let _: Envelope<serde_json::Value> = self
.send(self.request(Method::DELETE, &format!("/v1/webhooks/{}", encode(id))))
.await?;
Ok(())
}
pub async fn stream(
&self,
filters: &StreamFilters,
snapshot: u32,
) -> Result<ForeciteStream, ForeciteError> {
ForeciteStream::connect(&self.ws_url, &self.api_key, filters, snapshot).await
}
}
fn encode(segment: &str) -> String {
urlencoding::encode(segment).into_owned()
}
fn parse_error(bytes: &[u8]) -> (Option<String>, Option<String>) {
#[derive(Deserialize)]
struct ErrEnvelope {
error: Option<ErrBody>,
}
#[derive(Deserialize)]
struct ErrBody {
code: Option<String>,
message: Option<String>,
}
serde_json::from_slice::<ErrEnvelope>(bytes)
.ok()
.and_then(|e| e.error)
.map(|b| (b.code, b.message))
.unwrap_or((None, None))
}