use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Listing {
pub ticker: String,
pub exchange_code: String,
pub exchange_country: String,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Company {
pub company_id: i64,
#[serde(
default,
with = "flex::opt_f64",
skip_serializing_if = "Option::is_none"
)]
pub confidence: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub country: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub exchange: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub industry: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sector: Option<String>,
pub name: String,
pub ticker: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub isin: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub openfigi: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub primary_listing: Option<Listing>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub isins: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub other_listings: Vec<Listing>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Article {
pub link: String,
pub title: String,
#[serde(with = "flex::datetime")]
pub publish_date: DateTime<Utc>,
pub source: String,
pub language: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub images: Vec<String>,
#[serde(
default,
with = "flex::opt_datetime",
skip_serializing_if = "Option::is_none"
)]
pub created_at: Option<DateTime<Utc>>,
#[serde(
default,
with = "flex::opt_datetime",
skip_serializing_if = "Option::is_none"
)]
pub revised_date: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub is_update: Option<bool>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub categories: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sentiment: Option<String>,
#[serde(
default,
with = "flex::opt_f64",
skip_serializing_if = "Option::is_none"
)]
pub confidence: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub companies: Vec<Company>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub countries: Vec<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RawArticle {
pub link: String,
pub title: String,
#[serde(with = "flex::datetime")]
pub publish_date: DateTime<Utc>,
pub source: String,
pub language: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub images: Vec<String>,
#[serde(
default,
with = "flex::opt_datetime",
skip_serializing_if = "Option::is_none"
)]
pub created_at: Option<DateTime<Utc>>,
#[serde(
default,
with = "flex::opt_datetime",
skip_serializing_if = "Option::is_none"
)]
pub revised_date: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub is_update: Option<bool>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub categories: Vec<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ArticleResponse {
pub status: String,
pub page: u32,
pub page_size: u32,
pub articles: Vec<Article>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Source {
pub domain: String,
pub is_content_available: bool,
pub is_default_source: bool,
}
pub(crate) mod flex {
use chrono::{DateTime, NaiveDate, NaiveDateTime, SecondsFormat, Utc};
use serde::de::{Deserializer, Error as _};
use serde::{Deserialize, Serializer};
#[derive(Deserialize)]
#[serde(untagged)]
enum NumOrStr {
Num(f64),
Str(String),
}
fn num_or_str_to_f64<E: serde::de::Error>(v: NumOrStr) -> Result<f64, E> {
match v {
NumOrStr::Num(n) => Ok(n),
NumOrStr::Str(s) => s
.trim()
.parse::<f64>()
.map_err(|_| E::custom(format!("cannot parse {s:?} as float"))),
}
}
pub(crate) mod opt_f64 {
use super::*;
pub(crate) fn deserialize<'de, D: Deserializer<'de>>(
de: D,
) -> Result<Option<f64>, D::Error> {
Option::<NumOrStr>::deserialize(de)?
.map(num_or_str_to_f64)
.transpose()
}
pub(crate) fn serialize<S: Serializer>(v: &Option<f64>, ser: S) -> Result<S::Ok, S::Error> {
match v {
Some(f) => ser.serialize_f64(*f),
None => ser.serialize_none(),
}
}
}
pub(crate) fn parse_datetime(s: &str) -> Option<DateTime<Utc>> {
let s = s.trim();
if let Ok(t) = DateTime::parse_from_rfc3339(s) {
return Some(t.with_timezone(&Utc));
}
for format in ["%Y-%m-%dT%H:%M:%S%.f", "%Y-%m-%d %H:%M:%S%.f"] {
if let Ok(t) = NaiveDateTime::parse_from_str(s, format) {
return Some(t.and_utc());
}
}
if let Ok(d) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
return Some(d.and_hms_opt(0, 0, 0)?.and_utc());
}
None
}
pub(crate) mod datetime {
use super::*;
pub(crate) fn deserialize<'de, D: Deserializer<'de>>(
de: D,
) -> Result<DateTime<Utc>, D::Error> {
let s = String::deserialize(de)?;
parse_datetime(&s)
.ok_or_else(|| D::Error::custom(format!("cannot parse {s:?} as timestamp")))
}
pub(crate) fn serialize<S: Serializer>(
t: &DateTime<Utc>,
ser: S,
) -> Result<S::Ok, S::Error> {
ser.serialize_str(&t.to_rfc3339_opts(SecondsFormat::AutoSi, true))
}
}
pub(crate) mod opt_datetime {
use super::*;
pub(crate) fn deserialize<'de, D: Deserializer<'de>>(
de: D,
) -> Result<Option<DateTime<Utc>>, D::Error> {
Option::<String>::deserialize(de)?
.map(|s| {
parse_datetime(&s)
.ok_or_else(|| D::Error::custom(format!("cannot parse {s:?} as timestamp")))
})
.transpose()
}
pub(crate) fn serialize<S: Serializer>(
t: &Option<DateTime<Utc>>,
ser: S,
) -> Result<S::Ok, S::Error> {
match t {
Some(t) => datetime::serialize(t, ser),
None => ser.serialize_none(),
}
}
}
}