vast_protocol/
v4.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
4#[serde(rename_all = "camelCase")]
5pub struct VAST {
6    #[serde(rename = "Ad")]
7    pub ad: Ad,
8    pub version: String,
9    #[serde(rename = "xmlns:xs", default = "default_namespace")]
10    pub default_namespace: String,
11    #[serde(rename = "xmlns", default = "vast_namespace")]
12    pub vast_namespace: String,
13}
14
15impl VAST {
16    pub fn new(version: impl Into<String>, ad: Ad) -> VAST {
17        VAST {
18            ad,
19            version: version.into(),
20            default_namespace: default_namespace(),
21            vast_namespace: vast_namespace(),
22        }
23    }
24}
25
26fn default_namespace() -> String {
27    "http://www.w3.org/2001/XMLSchema".into()
28}
29
30fn vast_namespace() -> String {
31    "http://www.iab.com/VAST".into()
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35#[serde(rename_all = "camelCase")]
36pub struct Ad {
37    #[serde(rename = "InLine")]
38    pub in_line: Option<InLine>,
39    #[serde(rename = "Wrapper")]
40    pub wrapper: Option<Wrapper>,
41    pub id: String,
42    pub sequence: Option<i64>,
43    pub conditional_ad: Option<bool>,
44}
45
46/// Not yet implemented.
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub struct Wrapper;
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
52#[serde(rename_all = "PascalCase")]
53pub struct InLine {
54    pub ad_system: AdSystem,
55    pub error: Option<Error>,
56    pub impression: Impression,
57    pub description: Option<String>,
58    pub advertiser: Option<String>,
59    pub pricing: Option<Pricing>,
60    pub ad_title: AdTitle,
61    pub creatives: Creatives,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(rename_all = "camelCase")]
66pub struct Error(pub String);
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69#[serde(rename_all = "camelCase")]
70pub struct AdTitle(pub String);
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
73#[serde(rename_all = "camelCase")]
74pub struct AdSystem {
75    pub version: String,
76    #[serde(rename = "$value")]
77    pub content: Option<String>,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81#[serde(rename_all = "camelCase")]
82pub struct Impression {
83    pub id: String,
84    #[serde(rename = "$value")]
85    pub content: Option<String>,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
89#[serde(rename_all = "camelCase")]
90pub struct Pricing {
91    pub model: String,
92    pub currency: String,
93    #[serde(rename = "$value")]
94    pub content: Option<String>,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
98#[serde(rename_all = "camelCase")]
99pub struct Creatives {
100    #[serde(rename(serialize = "Creative", deserialize = "$value"))]
101    pub content: Vec<Creative>,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
105#[serde(rename_all = "camelCase")]
106pub struct Creative {
107    pub id: String,
108    pub sequence: i64,
109    pub ad_id: String,
110    pub api_framework: Option<String>,
111    #[serde(rename = "UniversalAdId")]
112    pub universal_ad_ids: Vec<UniversalAdId>,
113    #[serde(rename = "Linear")]
114    pub linear: Option<Linear>,
115    #[serde(rename = "NonLinearAds")]
116    pub non_linear_ads: Option<NonLinear>,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
120#[serde(rename_all = "camelCase")]
121pub struct UniversalAdId {
122    pub id_registry: String,
123    pub id_value: Option<String>,
124    #[serde(rename = "$value")]
125    pub content: Option<String>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
129#[serde(rename_all = "PascalCase")]
130pub struct Linear {
131    pub tracking_events: TrackingEvents,
132    pub duration: Duration,
133    pub media_files: MediaFiles,
134    pub video_clicks: Option<VideoClicks>,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
138#[serde(rename_all = "PascalCase")]
139pub struct NonLinear {}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
142#[serde(rename_all = "camelCase")]
143pub struct Duration(pub String);
144
145impl From<std::time::Duration> for Duration {
146    fn from(d: std::time::Duration) -> Self {
147        let ms = d.as_millis() % 1000;
148        let s = d.as_secs() % 60;
149        let m = (d.as_secs() / 60) % 60;
150        let h = (d.as_secs() / 60) / 60;
151
152        if d.as_secs() >= 100 * 60 * 60 {
153            return Self("99:59:59".into());
154        }
155
156        if ms > 0 {
157            Self(format!("{h:02}:{m:02}:{s:02}.{ms:03}"))
158        } else {
159            Self(format!("{h:02}:{m:02}:{s:02}"))
160        }
161    }
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
165#[serde(rename_all = "camelCase")]
166pub struct TrackingEvents {
167    #[serde(rename(serialize = "Tracking", deserialize = "$value"))]
168    pub content: Vec<Tracking>,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
172#[serde(rename_all = "camelCase")]
173pub struct Tracking {
174    pub event: String,
175    pub offset: Option<String>,
176    #[serde(rename = "$value")]
177    pub content: Option<String>,
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
181#[serde(rename_all = "camelCase")]
182pub struct MediaFiles {
183    // This doesn't use `$value` tag name to workaround variant vector issue
184    // https://github.com/tafia/quick-xml/pull/387
185    #[serde(rename = "MediaFile")]
186    pub content: Vec<MediaFile>,
187    #[serde(rename = "Mezzanine")]
188    pub mezzanine: Option<Mezzanine>,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize)]
192#[serde(rename_all = "camelCase")]
193pub struct MediaFile {
194    pub id: Option<String>,
195    pub delivery: String,
196    pub r#type: String,
197    pub bitrate: Option<u64>,
198    pub width: u64,
199    pub height: u64,
200    pub min_bitrate: Option<u64>,
201    pub max_bitrate: Option<u64>,
202    pub scalable: Option<String>,
203    pub maintain_aspect_ratio: Option<String>,
204    pub codec: Option<String>,
205    pub api_framework: Option<String>,
206    #[serde(rename = "$value")]
207    pub content: Option<String>,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
211#[serde(rename_all = "camelCase")]
212pub struct Mezzanine {}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
215#[serde(rename_all = "PascalCase")]
216pub struct VideoClicks {
217    pub click_through: Option<ClickThrough>,
218    pub click_tracking: Option<ClickTracking>,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
222#[serde(rename_all = "camelCase")]
223pub struct ClickThrough {
224    pub id: String,
225    #[serde(rename = "$value")]
226    pub content: Option<String>,
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize)]
230#[serde(rename_all = "camelCase")]
231pub struct ClickTracking {
232    pub id: Option<String>,
233    #[serde(rename = "$value")]
234    pub content: Option<String>,
235}
236
237pub mod util {
238    #[cfg(feature = "chrono")]
239    pub trait FromVastTimestamp {
240        fn parse_from_vast_timestamp(
241            s: &str,
242        ) -> chrono::ParseResult<chrono::DateTime<chrono::FixedOffset>> {
243            chrono::DateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.3f%#z")
244        }
245    }
246
247    #[cfg(feature = "chrono")]
248    impl FromVastTimestamp for chrono::DateTime<chrono::FixedOffset> {}
249}