use std::time::Duration;
use crate::error::VentureError;
use crate::types::*;
const DEFAULT_BASE_URL: &str = "https://ventureinkorea.com";
const DEFAULT_TIMEOUT_SECS: u64 = 30;
pub struct VentureInKoreaBuilder {
base_url: String,
timeout: Duration,
}
impl VentureInKoreaBuilder {
fn new() -> Self {
Self {
base_url: DEFAULT_BASE_URL.to_string(),
timeout: Duration::from_secs(DEFAULT_TIMEOUT_SECS),
}
}
pub fn base_url(mut self, url: &str) -> Self {
self.base_url = url.trim_end_matches('/').to_string();
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn build(self) -> Result<VentureInKorea, VentureError> {
let http = reqwest::Client::builder().timeout(self.timeout).build()?;
Ok(VentureInKorea {
base_url: self.base_url,
http,
})
}
}
pub struct VentureInKorea {
pub(crate) base_url: String,
pub(crate) http: reqwest::Client,
}
impl VentureInKorea {
pub fn new() -> Self {
Self {
base_url: DEFAULT_BASE_URL.to_string(),
http: reqwest::Client::builder()
.timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS))
.build()
.expect("failed to create HTTP client"),
}
}
pub fn builder() -> VentureInKoreaBuilder {
VentureInKoreaBuilder::new()
}
async fn get<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T, VentureError> {
let url = format!("{}{}", self.base_url, path);
let resp = self.http.get(&url).send().await?;
let status = resp.status();
if status == reqwest::StatusCode::NOT_FOUND {
return Err(VentureError::NotFound {
resource: path.to_string(),
});
}
if status == reqwest::StatusCode::TOO_MANY_REQUESTS {
let retry = resp
.headers()
.get("retry-after")
.and_then(|v| v.to_str().ok())
.and_then(|v| v.parse().ok())
.unwrap_or(60);
return Err(VentureError::RateLimit {
retry_after: retry,
});
}
if !status.is_success() {
let body = resp.text().await.unwrap_or_default();
return Err(VentureError::Api {
status: status.as_u16(),
message: body,
});
}
Ok(resp.json().await?)
}
pub async fn list_terms(
&self,
params: Option<&[(&str, &str)]>,
) -> Result<PaginatedResponse<GlossaryTerm>, VentureError> {
let qs = build_query(params);
self.get(&format!("/api/v1/terms/{}", qs)).await
}
pub async fn get_term(&self, slug: &str) -> Result<GlossaryTerm, VentureError> {
self.get(&format!("/api/v1/terms/{}/", slug)).await
}
pub async fn list_posts(
&self,
params: Option<&[(&str, &str)]>,
) -> Result<PaginatedResponse<BlogPost>, VentureError> {
let qs = build_query(params);
self.get(&format!("/api/v1/posts/{}", qs)).await
}
pub async fn get_post(&self, slug: &str) -> Result<BlogPost, VentureError> {
self.get(&format!("/api/v1/posts/{}/", slug)).await
}
pub async fn list_companies(
&self,
params: Option<&[(&str, &str)]>,
) -> Result<PaginatedResponse<Company>, VentureError> {
let qs = build_query(params);
self.get(&format!("/api/v1/companies/{}", qs)).await
}
pub async fn get_company(&self, pk: u64) -> Result<Company, VentureError> {
self.get(&format!("/api/v1/companies/{}/", pk)).await
}
pub async fn list_categories(&self) -> Result<PaginatedResponse<PostCategory>, VentureError> {
self.get("/api/v1/categories/").await
}
pub async fn list_faqs(
&self,
params: Option<&[(&str, &str)]>,
) -> Result<PaginatedResponse<Faq>, VentureError> {
let qs = build_query(params);
self.get(&format!("/api/v1/faq/{}", qs)).await
}
pub async fn get_stats(&self) -> Result<PlatformStats, VentureError> {
self.get("/api/v1/stats/").await
}
pub async fn search(&self, query: &str) -> Result<SearchResponse, VentureError> {
self.get(&format!("/api/v1/search/?q={}", urlencoding(query)))
.await
}
pub async fn autocomplete(&self, query: &str) -> Result<AutocompleteResponse, VentureError> {
self.get(&format!(
"/api/v1/autocomplete/?q={}",
urlencoding(query)
))
.await
}
}
impl Default for VentureInKorea {
fn default() -> Self {
Self::new()
}
}
fn build_query(params: Option<&[(&str, &str)]>) -> String {
match params {
None => String::new(),
Some(pairs) => {
let qs: Vec<String> = pairs
.iter()
.filter(|(_, v)| !v.is_empty())
.map(|(k, v)| format!("{}={}", k, urlencoding(v)))
.collect();
if qs.is_empty() {
String::new()
} else {
format!("?{}", qs.join("&"))
}
}
}
}
fn urlencoding(s: &str) -> String {
s.replace(' ', "%20")
.replace('&', "%26")
.replace('=', "%3D")
}