vast4_rs/creative.rs
1/// A container for one or more [`<Creative>`](Creative) element used to provide creative files for
2/// the ad.
3///
4/// ```text
5/// <xs:element name="Creatives">
6/// <xs:complexType>
7/// <xs:sequence>
8/// <xs:element name="Creative" minOccurs="1" maxOccurs="unbounded" type="vast:Creative" />
9/// </xs:sequence>
10/// </xs:complexType>
11/// </xs:element>
12/// ```
13#[derive(hard_xml::XmlWrite, hard_xml::XmlRead, Default, PartialEq, Clone, Debug)]
14#[xml(tag = "Creatives")]
15pub struct Creatives<'a> {
16 /// The container for one or more [`<Creative>`](Creative) elements.
17 #[xml(child = "Creative")]
18 pub creatives: Vec<Creative<'a>>,
19}
20
21/// Each `<Creative>` element contains nested elements that describe the type of ad being
22/// served using nested sub-elements.
23///
24/// ## Creative in [`InLine`](crate::InLine):
25/// ```text
26/// <xs:complexType name="Creative">
27/// <!-- Creative_Base_type -->
28/// <xs:attribute name="sequence" type="xs:integer" use="optional">
29/// <xs:attribute name="apiFramework" type="xs:string" use="optional">
30/// <xs:attribute name="id" type="xs:string" use="optional">
31/// <xs:attribute name="adId" type="xs:string" use="optional">
32///
33/// <xs:sequence>
34/// <xs:element name="CompanionAds" minOccurs="0" maxOccurs="1" type="vast:CompanionAds_Collection_type" />
35/// <xs:element name="CreativeExtensions" minOccurs="0" maxOccurs="1" type="vast:CreativeExtensions_type" />
36/// <xs:element name="Linear" minOccurs="0" maxOccurs="1" type="vast:Linear_Inline_type" />
37/// <xs:element name="NonLinearAds" minOccurs="0" maxOccurs="1">
38/// <xs:element name="UniversalAdId" minOccurs="1" maxOccurs="unbounded">
39/// </xs:sequence>
40/// </xs:complexType>
41/// ```
42///
43/// ## Creative in [`Wrapper`](crate::Wrapper):
44/// ```text
45/// <xs:complexType name="Creative">
46/// <!-- Creative_Base_type -->
47/// <xs:attribute name="sequence" type="xs:integer" use="optional">
48/// <xs:attribute name="apiFramework" type="xs:string" use="optional">
49/// <xs:attribute name="id" type="xs:string" use="optional">
50/// <xs:attribute name="adId" type="xs:string" use="optional">
51///
52/// <xs:sequence>
53/// <xs:element name="CompanionAds" minOccurs="0" maxOccurs="1" type="vast:CompanionAds_Collection_type" />
54/// <xs:element name="Linear" minOccurs="0" maxOccurs="1" type="vast:Linear_Wrapper_type" />
55/// <xs:element name="NonLinearAds" minOccurs="0" maxOccurs="1">
56/// </xs:sequence>
57/// </xs:complexType>
58/// ```
59#[derive(hard_xml::XmlWrite, hard_xml::XmlRead, Default, PartialEq, Clone, Debug)]
60#[xml(tag = "Creative")]
61pub struct Creative<'a> {
62 /// A number representing the numerical order in which each sequenced creative within
63 /// an ad should play.
64 #[xml(attr = "sequence", default)]
65 pub sequence: Option<i32>,
66 /// A string that identifies an API that is needed to execute the creative.
67 #[xml(attr = "apiFramework", default)]
68 pub api_framework: Option<std::borrow::Cow<'a, str>>,
69 /// A string used to identify the ad server that provides the creative.
70 #[xml(attr = "id", default)]
71 pub id: Option<std::borrow::Cow<'a, str>>,
72 /// Used to provide the ad server’s unique identifier for the creative. In VAST 4, the
73 /// [UniversalAdId] element was introduced to provide a unique identifier for the creative
74 /// that is maintained across systems. Please see section 3.7.1 for details on the
75 /// [UniversalAdId].
76 #[xml(attr = "adId", default)]
77 pub ad_id: Option<std::borrow::Cow<'a, str>>,
78
79 /// The container for zero or one [`<CompanionAds>`](crate::CompanionAds) element.
80 #[xml(child = "CompanionAds", default)]
81 pub companion_ads: Option<crate::CompanionAds<'a>>,
82 /// InLine: The container for zero or one [`<CreativeExtensions>`](CreativeExtensions) element.
83 /// Wrapper: No use this field.
84 #[xml(child = "CreativeExtensions", default)]
85 pub creative_extensions: Option<CreativeExtensions>,
86 /// The container for zero or one [`<Linear>`](crate::Linear) element.
87 #[xml(child = "Linear", default)]
88 pub linear: Option<crate::Linear<'a>>,
89 /// The container for zero or one [`<NonLinearAds>`](crate::NonLinearAds) element.
90 #[xml(child = "NonLinearAds", default)]
91 pub non_linear_ads: Option<crate::NonLinearAds<'a>>,
92 /// InLine: The container for one or more [`<UniversalAdId>`](UniversalAdId) elements.
93 /// Wrapper: No use this field.
94 #[xml(child = "UniversalAdId", default)]
95 pub universal_ad_id: Vec<UniversalAdId<'a>>,
96}
97
98/// A required element for the purpose of tracking ad creative, the `<UniversalAdId>` is used to
99/// provide a unique creative identifier that is maintained across systems.
100///
101/// ```text
102/// <xs:element name="UniversalAdId">
103/// <xs:complexType>
104/// <xs:simpleContent>
105/// <xs:extension base="xs:string">
106/// <xs:attribute name="idRegistry" type="xs:string" use="required" />
107/// </xs:extension>
108/// </xs:simpleContent>
109/// </xs:complexType>
110/// </xs:element>
111/// ```
112#[derive(hard_xml::XmlWrite, hard_xml::XmlRead, Default, PartialEq, Clone, Debug)]
113#[xml(tag = "UniversalAdId")]
114pub struct UniversalAdId<'a> {
115 /// A string used to identify the URL for the registry website where the unique
116 /// creative ID is cataloged. Default value is "unknown".
117 #[xml(attr = "idRegistry", default = "unknown")]
118 pub id_registry: std::borrow::Cow<'a, str>,
119
120 /// A string identifying the unique creative identifier. Default value is "unknown".
121 #[xml(text, default = "unknown")]
122 pub id: std::borrow::Cow<'a, str>,
123}
124
125/// This extension can be used to load an executable creative with or without using the
126/// [`<MediaFile>`](crate::MediaFile).
127///
128/// ```text
129/// <xs:complexType name="CreativeExtensions">
130/// <xs:sequence>
131/// <xs:element name="CreativeExtension" minOccurs="0" maxOccurs="unbounded">
132/// </xs:sequence>
133/// </xs:complexType>
134/// ```
135#[derive(hard_xml::XmlWrite, hard_xml::XmlRead, Default, PartialEq, Clone, Debug)]
136#[xml(tag = "CreativeExtensions")]
137pub struct CreativeExtensions {
138 /// The container for zero or more [`<CreativeExtension>`](CreativeExtension) elements.
139 #[xml(child = "CreativeExtension", default)]
140 pub creative_extensions: Vec<CreativeExtension>,
141}
142
143// TODO: custom xml object
144/// Any valid XML may be included in the Extensions node.
145///
146/// ```text
147/// <xs:element name="CreativeExtension" minOccurs="0" maxOccurs="unbounded">
148/// <xs:complexType>
149/// <xs:attribute name="type" type="xs:string" use="optional">
150/// <xs:anyAttribute namespace="##any" processContents="skip" />
151/// <xs:sequence>
152/// <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##any" processContents="skip" />
153/// </xs:sequence>
154/// </xs:complexType>
155/// </xs:element>
156/// ```
157#[derive(Default, PartialEq, Clone, Debug)]
158pub struct CreativeExtension {
159 /// The MIME type of any code that might be included in the extension.
160 pub mime_type: Option<String>,
161
162 /// Custom XML object.
163 pub xml: String,
164}
165
166impl CreativeExtension {
167 pub fn from_struct<T: hard_xml::XmlWrite>(
168 mime_type: &str,
169 value: &T,
170 ) -> hard_xml::XmlResult<Self> {
171 Ok(Self {
172 mime_type: Some(mime_type.to_owned()),
173 xml: hard_xml::XmlWrite::to_string(value)?,
174 })
175 }
176
177 pub fn as_struct<'a, 'b, T>(&'a self) -> hard_xml::XmlResult<T>
178 where
179 T: hard_xml::XmlRead<'b>,
180 'a: 'b,
181 {
182 <T as hard_xml::XmlRead<'b>>::from_str(&self.xml)
183 }
184}
185
186impl hard_xml::XmlWrite for CreativeExtension {
187 fn to_writer<W: std::io::Write>(
188 &self,
189 writer: &mut hard_xml::XmlWriter<W>,
190 ) -> hard_xml::XmlResult<()> {
191 writer.write_element_start("CreativeExtension")?;
192 if let Some(ref attr) = self.mime_type {
193 writer.write_attribute("type", attr)?;
194 }
195 writer.write_element_end_open()?;
196 write!(writer.inner, "{}", self.xml)?;
197 writer.write_element_end_close("CreativeExtension")?;
198 Ok(())
199 }
200}
201
202impl<'a> hard_xml::XmlRead<'a> for CreativeExtension {
203 fn from_reader(reader: &mut hard_xml::XmlReader<'a>) -> hard_xml::XmlResult<Self> {
204 use hard_xml::xmlparser::{ElementEnd, Token};
205
206 reader.read_till_element_start("CreativeExtension")?;
207
208 let mut mime_type = None;
209 while let Some(("type", value)) = reader.find_attribute()? {
210 mime_type = Some(match value {
211 std::borrow::Cow::Borrowed(s) => s.to_owned(),
212 std::borrow::Cow::Owned(s) => s,
213 });
214 }
215
216 if let Some(t) = reader.next() {
217 let t = t?;
218 if !matches!(
219 t,
220 Token::ElementEnd {
221 end: ElementEnd::Open,
222 ..
223 }
224 ) {
225 return Err(hard_xml::XmlError::UnexpectedToken {
226 token: format!("{t:?}"),
227 });
228 }
229 }
230
231 let mut depth = 0;
232 let mut xml = String::new();
233
234 while let Some(t) = reader.next() {
235 match t? {
236 Token::Declaration { span, .. }
237 | Token::ProcessingInstruction { span, .. }
238 | Token::Comment { span, .. }
239 | Token::DtdStart { span, .. }
240 | Token::EmptyDtd { span, .. }
241 | Token::EntityDeclaration { span, .. }
242 | Token::DtdEnd { span }
243 | Token::ElementStart { span, .. }
244 | Token::Cdata { span, .. } => xml.push_str(span.as_str()),
245 Token::Attribute { span, .. } => {
246 xml.push(' ');
247 xml.push_str(span.as_str())
248 }
249 Token::Text { text } => xml.push_str(text.as_str()),
250 Token::ElementEnd { end, span } => {
251 match end {
252 ElementEnd::Open => {
253 depth += 1;
254 }
255 ElementEnd::Close(_, name) => {
256 if depth == 0 && name.as_str() == "CreativeExtension" {
257 break;
258 } else {
259 depth -= 1;
260 }
261 }
262 ElementEnd::Empty => {}
263 };
264 xml.push_str(span.as_str())
265 }
266 }
267 }
268
269 Ok(Self { mime_type, xml })
270 }
271}
272
273crate::declare_test!(
274 test_creative_extension,
275 CreativeExtension,
276 r#"<CreativeExtension type="text/javascript">
277 <Ext>
278 <Some id="hoge">fuga</Some>
279 <Text><![CDATA[some cdata text]]></Text>
280 </Ext>
281 </CreativeExtension>"#,
282 CreativeExtension {
283 mime_type: Some("text/javascript".to_owned()),
284 xml: r#"
285 <Ext>
286 <Some id="hoge">fuga</Some>
287 <Text><![CDATA[some cdata text]]></Text>
288 </Ext>
289 "#
290 .into()
291 }
292);
293
294#[cfg(test)]
295#[test]
296fn test_creative_extension_attribute() {
297 let xml = r#"<CreativeExtension type="text/javascript" attr="value1">
298 <Ext>
299 <Some id="hoge">fuga</Some>
300 <Text><![CDATA[some cdata text]]></Text>
301 </Ext>
302 </CreativeExtension>"#;
303 let ext = crate::from_str::<CreativeExtension>(xml).unwrap();
304 assert_eq!(
305 ext,
306 CreativeExtension {
307 mime_type: Some("text/javascript".to_owned()),
308 xml: r#"
309 <Ext>
310 <Some id="hoge">fuga</Some>
311 <Text><![CDATA[some cdata text]]></Text>
312 </Ext>
313 "#
314 .into()
315 }
316 );
317
318 assert_eq!(
319 crate::to_string(&ext).unwrap(),
320 r#"<CreativeExtension type="text/javascript">
321 <Ext>
322 <Some id="hoge">fuga</Some>
323 <Text><![CDATA[some cdata text]]></Text>
324 </Ext>
325 </CreativeExtension>"#
326 );
327}