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}