1use std::{error::Error, fmt, str::FromStr};
4
5use chrono::{NaiveDate, NaiveDateTime};
6use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum StringValueError {
12 Empty,
14 ControlCharacter,
16 Comma,
18}
19
20impl fmt::Display for StringValueError {
21 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
22 match self {
23 Self::Empty => formatter.write_str("value must not be empty or whitespace-only"),
24 Self::ControlCharacter => {
25 formatter.write_str("value must not contain control characters")
26 }
27 Self::Comma => formatter.write_str("ticker must not contain a comma"),
28 }
29 }
30}
31
32impl Error for StringValueError {}
33
34fn validate_string_value(value: &str, reject_comma: bool) -> Result<(), StringValueError> {
35 if value.trim().is_empty() {
36 return Err(StringValueError::Empty);
37 }
38 if value.chars().any(char::is_control) {
39 return Err(StringValueError::ControlCharacter);
40 }
41 if reject_comma && value.contains(',') {
42 return Err(StringValueError::Comma);
43 }
44 Ok(())
45}
46
47macro_rules! string_value {
48 ($name:ident, $description:literal, $reject_comma:literal) => {
49 #[doc = $description]
50 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
51 #[serde(transparent)]
52 pub struct $name(String);
53
54 impl $name {
55 pub fn new(value: impl Into<String>) -> Result<Self, StringValueError> {
57 let value = value.into();
58 validate_string_value(&value, $reject_comma)?;
59 Ok(Self(value))
60 }
61
62 pub fn as_str(&self) -> &str {
64 &self.0
65 }
66
67 pub fn into_inner(self) -> String {
69 self.0
70 }
71 }
72
73 impl fmt::Display for $name {
74 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
75 formatter.write_str(self.as_str())
76 }
77 }
78
79 impl FromStr for $name {
80 type Err = StringValueError;
81
82 fn from_str(value: &str) -> Result<Self, Self::Err> {
83 Self::new(value)
84 }
85 }
86
87 impl TryFrom<String> for $name {
88 type Error = StringValueError;
89
90 fn try_from(value: String) -> Result<Self, Self::Error> {
91 Self::new(value)
92 }
93 }
94
95 impl TryFrom<&str> for $name {
96 type Error = StringValueError;
97
98 fn try_from(value: &str) -> Result<Self, Self::Error> {
99 Self::new(value)
100 }
101 }
102
103 impl AsRef<str> for $name {
104 fn as_ref(&self) -> &str {
105 self.as_str()
106 }
107 }
108
109 impl<'de> Deserialize<'de> for $name {
110 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
111 where
112 D: Deserializer<'de>,
113 {
114 let value = String::deserialize(deserializer)?;
115 Self::new(value).map_err(de::Error::custom)
116 }
117 }
118 };
119}
120
121string_value!(Ticker, "A provider ticker symbol.", true);
122string_value!(
123 TipRanksExpertUid,
124 "A representation-preserving stable TipRanks analyst identifier.",
125 false
126);
127string_value!(
128 BulkPart,
129 "An open, representation-preserving provider bulk partition.",
130 false
131);
132string_value!(
133 SearchTerm,
134 "A representation-preserving provider search term.",
135 false
136);
137string_value!(Cik, "A Central Index Key.", false);
138string_value!(
139 CongressionalMemberId,
140 "A representation-preserving congressional member identifier.",
141 false
142);
143string_value!(
144 FormType,
145 "An open, representation-preserving SEC filing form type.",
146 false
147);
148string_value!(
149 TransactionTypeCode,
150 "An open, representation-preserving insider transaction type code.",
151 false
152);
153string_value!(Cusip, "A CUSIP identifier.", false);
154string_value!(Isin, "An ISIN identifier.", false);
155string_value!(Lei, "A Legal Entity Identifier.", false);
156string_value!(ExchangeCode, "An open provider exchange code.", false);
157string_value!(
158 MarketHoursTimestamp,
159 "An opaque, representation-preserving market-hours query timestamp.",
160 false
161);
162string_value!(CurrencyCode, "An open provider currency code.", false);
163string_value!(CountryCode, "An open provider country code.", false);
164string_value!(Sector, "An open provider company sector.", false);
165string_value!(Industry, "An open provider company industry.", false);
166string_value!(
167 BenchmarkYear,
168 "An open, representation-preserving executive-compensation benchmark query year.",
169 false
170);
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
174pub struct NonFiniteDecimal;
175
176impl fmt::Display for NonFiniteDecimal {
177 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
178 formatter.write_str("decimal value must be finite")
179 }
180}
181
182impl Error for NonFiniteDecimal {}
183
184#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
189pub struct FiniteDecimal(f64);
190
191impl FiniteDecimal {
192 pub fn new(value: f64) -> Result<Self, NonFiniteDecimal> {
194 if value.is_finite() {
195 Ok(Self(value))
196 } else {
197 Err(NonFiniteDecimal)
198 }
199 }
200
201 pub const fn get(self) -> f64 {
203 self.0
204 }
205}
206
207impl TryFrom<f64> for FiniteDecimal {
208 type Error = NonFiniteDecimal;
209
210 fn try_from(value: f64) -> Result<Self, Self::Error> {
211 Self::new(value)
212 }
213}
214
215impl From<FiniteDecimal> for f64 {
216 fn from(value: FiniteDecimal) -> Self {
217 value.get()
218 }
219}
220
221impl fmt::Display for FiniteDecimal {
222 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
223 self.0.fmt(formatter)
224 }
225}
226
227impl Serialize for FiniteDecimal {
228 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
229 where
230 S: Serializer,
231 {
232 serializer.serialize_f64(self.0)
233 }
234}
235
236impl<'de> Deserialize<'de> for FiniteDecimal {
237 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
238 where
239 D: Deserializer<'de>,
240 {
241 Self::new(f64::deserialize(deserializer)?).map_err(de::Error::custom)
242 }
243}
244
245#[derive(Debug, Clone, Copy, PartialEq, Eq)]
247pub struct EmptyTickerList;
248
249impl fmt::Display for EmptyTickerList {
250 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
251 formatter.write_str("ticker list must contain at least one ticker")
252 }
253}
254
255impl Error for EmptyTickerList {}
256
257#[derive(Debug, Clone, PartialEq, Eq)]
259pub struct TickerList(Vec<Ticker>);
260
261impl TickerList {
262 pub fn new(tickers: Vec<Ticker>) -> Result<Self, EmptyTickerList> {
263 if tickers.is_empty() {
264 Err(EmptyTickerList)
265 } else {
266 Ok(Self(tickers))
267 }
268 }
269
270 pub fn as_slice(&self) -> &[Ticker] {
271 &self.0
272 }
273
274 pub fn into_inner(self) -> Vec<Ticker> {
275 self.0
276 }
277
278 pub fn iter(&self) -> impl ExactSizeIterator<Item = &Ticker> {
279 self.0.iter()
280 }
281
282 pub fn len(&self) -> usize {
283 self.0.len()
284 }
285
286 pub fn is_empty(&self) -> bool {
287 self.0.is_empty()
288 }
289}
290
291impl TryFrom<Vec<Ticker>> for TickerList {
292 type Error = EmptyTickerList;
293
294 fn try_from(tickers: Vec<Ticker>) -> Result<Self, Self::Error> {
295 Self::new(tickers)
296 }
297}
298
299impl fmt::Display for TickerList {
300 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
301 let mut tickers = self.0.iter();
302 if let Some(first) = tickers.next() {
303 first.fmt(formatter)?;
304 }
305 for ticker in tickers {
306 formatter.write_str(",")?;
307 ticker.fmt(formatter)?;
308 }
309 Ok(())
310 }
311}
312
313#[derive(Debug, Clone, Copy, PartialEq, Eq)]
315pub struct InvalidTemporalValue {
316 expected: &'static str,
317}
318
319impl InvalidTemporalValue {
320 pub(crate) const fn new(expected: &'static str) -> Self {
321 Self { expected }
322 }
323
324 pub fn expected(&self) -> &'static str {
325 self.expected
326 }
327}
328
329impl fmt::Display for InvalidTemporalValue {
330 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
331 write!(formatter, "value must be a valid {}", self.expected)
332 }
333}
334
335impl Error for InvalidTemporalValue {}
336
337fn has_exact_ascii_shape(value: &str, len: usize, separators: &[(usize, u8)]) -> bool {
338 if value.len() != len || !value.is_ascii() {
339 return false;
340 }
341
342 value.bytes().enumerate().all(|(index, byte)| {
343 separators
344 .iter()
345 .find_map(|(position, expected)| (*position == index).then_some(*expected))
346 .map_or_else(|| byte.is_ascii_digit(), |expected| byte == expected)
347 })
348}
349
350#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
352pub struct Date(NaiveDate);
353
354impl Date {
355 pub fn parse(value: &str) -> Result<Self, InvalidTemporalValue> {
356 const EXPECTED: &str = "YYYY-MM-DD date";
357 if !has_exact_ascii_shape(value, 10, &[(4, b'-'), (7, b'-')]) {
358 return Err(InvalidTemporalValue { expected: EXPECTED });
359 }
360 NaiveDate::parse_from_str(value, "%Y-%m-%d")
361 .map(Self)
362 .map_err(|_| InvalidTemporalValue { expected: EXPECTED })
363 }
364
365 pub const fn into_inner(self) -> NaiveDate {
367 self.0
368 }
369}
370
371impl FromStr for Date {
372 type Err = InvalidTemporalValue;
373
374 fn from_str(value: &str) -> Result<Self, Self::Err> {
375 Self::parse(value)
376 }
377}
378
379impl fmt::Display for Date {
380 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
381 write!(formatter, "{}", self.0.format("%Y-%m-%d"))
382 }
383}
384
385impl Serialize for Date {
386 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
387 where
388 S: Serializer,
389 {
390 serializer.collect_str(self)
391 }
392}
393
394impl<'de> Deserialize<'de> for Date {
395 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
396 where
397 D: Deserializer<'de>,
398 {
399 let value = String::deserialize(deserializer)?;
400 Self::parse(&value).map_err(de::Error::custom)
401 }
402}
403
404#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
406pub struct ApiDateTime(NaiveDateTime);
407
408impl ApiDateTime {
409 pub fn parse(value: &str) -> Result<Self, InvalidTemporalValue> {
410 const EXPECTED: &str = "YYYY-MM-DD HH:MM:SS datetime";
411 let separators = &[(4, b'-'), (7, b'-'), (10, b' '), (13, b':'), (16, b':')];
412 if !has_exact_ascii_shape(value, 19, separators) {
413 return Err(InvalidTemporalValue { expected: EXPECTED });
414 }
415 NaiveDateTime::parse_from_str(value, "%Y-%m-%d %H:%M:%S")
416 .map(Self)
417 .map_err(|_| InvalidTemporalValue { expected: EXPECTED })
418 }
419
420 pub const fn into_inner(self) -> NaiveDateTime {
422 self.0
423 }
424}
425
426impl FromStr for ApiDateTime {
427 type Err = InvalidTemporalValue;
428
429 fn from_str(value: &str) -> Result<Self, Self::Err> {
430 Self::parse(value)
431 }
432}
433
434impl fmt::Display for ApiDateTime {
435 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
436 write!(formatter, "{}", self.0.format("%Y-%m-%d %H:%M:%S"))
437 }
438}
439
440impl Serialize for ApiDateTime {
441 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
442 where
443 S: Serializer,
444 {
445 serializer.collect_str(self)
446 }
447}
448
449impl<'de> Deserialize<'de> for ApiDateTime {
450 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
451 where
452 D: Deserializer<'de>,
453 {
454 let value = String::deserialize(deserializer)?;
455 Self::parse(&value).map_err(de::Error::custom)
456 }
457}
458
459#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
461#[serde(transparent)]
462pub struct UnixSeconds(pub i64);
463
464#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
466#[serde(transparent)]
467pub struct UnixMilliseconds(pub i64);
468
469impl fmt::Display for UnixSeconds {
470 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
471 self.0.fmt(formatter)
472 }
473}
474
475impl fmt::Display for UnixMilliseconds {
476 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
477 self.0.fmt(formatter)
478 }
479}
480
481#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
486#[serde(transparent)]
487pub struct CalendarYear(pub u32);
488
489impl CalendarYear {
490 pub const fn get(self) -> u32 {
492 self.0
493 }
494}
495
496impl From<u32> for CalendarYear {
497 fn from(value: u32) -> Self {
498 Self(value)
499 }
500}
501
502impl From<CalendarYear> for u32 {
503 fn from(value: CalendarYear) -> Self {
504 value.get()
505 }
506}
507
508impl fmt::Display for CalendarYear {
509 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
510 self.0.fmt(formatter)
511 }
512}
513
514#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
519pub struct CalendarQuarter(u8);
520
521impl CalendarQuarter {
522 pub const fn new(value: u8) -> Result<Self, InvalidCalendarQuarter> {
524 if value >= 1 && value <= 4 {
525 Ok(Self(value))
526 } else {
527 Err(InvalidCalendarQuarter)
528 }
529 }
530
531 pub const fn get(self) -> u8 {
533 self.0
534 }
535}
536
537impl TryFrom<u8> for CalendarQuarter {
538 type Error = InvalidCalendarQuarter;
539
540 fn try_from(value: u8) -> Result<Self, Self::Error> {
541 Self::new(value)
542 }
543}
544
545impl From<CalendarQuarter> for u8 {
546 fn from(value: CalendarQuarter) -> Self {
547 value.get()
548 }
549}
550
551impl fmt::Display for CalendarQuarter {
552 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
553 self.0.fmt(formatter)
554 }
555}
556
557impl Serialize for CalendarQuarter {
558 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
559 where
560 S: Serializer,
561 {
562 serializer.serialize_u8(self.get())
563 }
564}
565
566impl<'de> Deserialize<'de> for CalendarQuarter {
567 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
568 where
569 D: Deserializer<'de>,
570 {
571 Self::new(u8::deserialize(deserializer)?).map_err(de::Error::custom)
572 }
573}
574
575#[derive(Debug, Clone, Copy, PartialEq, Eq)]
577pub struct InvalidCalendarQuarter;
578
579impl fmt::Display for InvalidCalendarQuarter {
580 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
581 formatter.write_str("calendar quarter must be an integer from 1 through 4")
582 }
583}
584
585impl Error for InvalidCalendarQuarter {}
586
587#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
592#[serde(transparent)]
593pub struct Page(pub u32);
594
595#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
600#[serde(transparent)]
601pub struct Limit(pub u32);
602
603impl fmt::Display for Page {
604 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
605 self.0.fmt(formatter)
606 }
607}
608
609impl fmt::Display for Limit {
610 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
611 self.0.fmt(formatter)
612 }
613}
614
615#[derive(Debug, Clone, Copy, PartialEq, Eq)]
617pub struct InvalidDateRange;
618
619impl fmt::Display for InvalidDateRange {
620 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
621 formatter.write_str("date range start must not be later than its end")
622 }
623}
624
625impl Error for InvalidDateRange {}
626
627#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
629pub struct DateRange {
630 from: Date,
631 to: Date,
632}
633
634impl DateRange {
635 pub fn new(from: Date, to: Date) -> Result<Self, InvalidDateRange> {
636 if from > to {
637 Err(InvalidDateRange)
638 } else {
639 Ok(Self { from, to })
640 }
641 }
642
643 pub fn from(&self) -> Date {
644 self.from
645 }
646
647 pub fn to(&self) -> Date {
648 self.to
649 }
650
651 pub fn span_days(&self) -> u64 {
656 self.to.0.signed_duration_since(self.from.0).num_days() as u64
657 }
658}
659
660pub type Price = f64;
662pub type Change = f64;
664pub type Percentage = f64;
666pub type MarketValue = f64;
674pub type Ratio = f64;
679pub type PerShareAmount = f64;
685pub type Quantity = f64;
693pub type Volume = f64;
702pub type MarketCapitalization = f64;
710pub type TokenSupply = f64;
716pub type SplitTerm = f64;
722pub type Count = u64;
728pub type StatementAmount = f64;