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#[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 #[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}