reqif_rs/
req_if.rs

1/// reqif-rs: Help library to write reqif files implemented in Rust.
2/// Copyright (C) <2024>  INVAP S.E.
3///
4/// This file is part of reqif-rs.
5///
6/// reqif-rs is free software: you can redistribute it and/or modify
7/// it under the terms of the GNU Affero General Public License as published by
8/// the Free Software Foundation, either version 3 of the License, or
9/// (at your option) any later version.
10///
11/// This program is distributed in the hope that it will be useful,
12/// but WITHOUT ANY WARRANTY; without even the implied warranty of
13/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14/// GNU Affero General Public License for more details.
15///
16/// You should have received a copy of the GNU Affero General Public License
17/// along with this program.  If not, see <https://www.gnu.org/licenses/>.
18use anyhow::{bail, Result};
19use chrono::{DateTime, Local, SecondsFormat};
20use std::fs::File;
21use std::io::Write;
22use yaserde_derive::YaSerialize;
23
24fn get_default_last_change_date() -> String {
25    Local::now().to_rfc3339_opts(SecondsFormat::Millis, false)
26}
27
28#[derive(Debug, PartialEq, YaSerialize)]
29pub struct ReqIfHeader {
30    #[yaserde(rename = "IDENTIFIER", attribute)]
31    pub identifier: String,
32    #[yaserde(rename = "CREATION-TIME")]
33    pub creation_time: String,
34    #[yaserde(rename = "REPOSITORY-ID")]
35    pub repository_id: String,
36    #[yaserde(rename = "REQ-IF-TOOL-ID")]
37    pub req_if_tool_id: String,
38    #[yaserde(rename = "REQ-IF-VERSION")]
39    pub req_if_version: String,
40    #[yaserde(rename = "SOURCE-TOOL-ID")]
41    pub source_tool_id: String,
42    #[yaserde(rename = "TITLE")]
43    pub title: String,
44}
45
46#[derive(Debug, PartialEq, YaSerialize)]
47pub struct TheHeader {
48    #[yaserde(rename = "REQ-IF-HEADER")]
49    pub req_if_header: ReqIfHeader,
50}
51
52#[derive(Debug, PartialEq, YaSerialize)]
53pub struct DataType {
54    #[yaserde(attribute, rename = "IDENTIFIER")]
55    identifier: String,
56    #[yaserde(attribute, rename = "LAST-CHANGE")]
57    last_change: String,
58    #[yaserde(attribute, rename = "LONG-NAME")]
59    long_name: String,
60}
61
62#[derive(Debug, PartialEq, YaSerialize)]
63pub struct DataTypes {
64    #[yaserde(rename = "DATATYPE-DEFINITION-STRING")]
65    xhtml_definition: DataType,
66}
67
68impl DataTypes {
69    pub fn new() -> Self {
70        DataTypes {
71            xhtml_definition: DataType {
72                identifier: "DATATYPE-DEFINITION-XHTML-IDENTIFIER".to_string(),
73                last_change: get_default_last_change_date(),
74                long_name: "XHTMLString".to_string(),
75            },
76        }
77    }
78}
79
80#[derive(Debug, PartialEq, YaSerialize)]
81pub struct SpecificationTypeModuleAttributes {
82    #[yaserde(rename = "ATTRIBUTE-DEFINITION-XHTML")]
83    reqif_name_attribute: AttributeDefinitionXHtml,
84}
85
86#[derive(Debug, PartialEq, YaSerialize)]
87pub struct SpecificationTypeModule {
88    #[yaserde(attribute, rename = "IDENTIFIER")]
89    identifier: String,
90    #[yaserde(attribute, rename = "LAST-CHANGE")]
91    last_change: String,
92    #[yaserde(attribute, rename = "LONG-NAME")]
93    long_name: String,
94    #[yaserde(rename = "SPEC-ATTRIBUTES")]
95    attributes: SpecificationTypeModuleAttributes,
96}
97
98#[derive(Debug, PartialEq, YaSerialize)]
99pub struct SpecTypes {
100    #[yaserde(rename = "SPEC-OBJECT-TYPE")]
101    spec_object_type_requirement: SpecObjectTypeRequirement,
102    #[yaserde(rename = "SPECIFICATION-TYPE")]
103    specification_type_module: SpecificationTypeModule,
104}
105
106impl SpecTypes {
107    fn new(data_types: &DataTypes) -> Self {
108        SpecTypes {
109            specification_type_module: SpecificationTypeModule {
110                identifier: "MODULE-SPECIFICATION-TYPE-ID".to_string(),
111                last_change: get_default_last_change_date(),
112                long_name: "Module Type".to_string(),
113                attributes: SpecificationTypeModuleAttributes {
114                    reqif_name_attribute: AttributeDefinitionXHtml {
115                        identifier: "ATTRIBUTE-DEFINITION-XHTML-REQIF.NAME-ID".to_string(),
116                        last_change: get_default_last_change_date(),
117                        long_name: "ReqIF.Name".to_string(),
118                        type_ref: TypeDefinitionXHtmlRef {
119                            reference: data_types.xhtml_definition.identifier.clone(),
120                        },
121                    },
122                },
123            },
124            spec_object_type_requirement: SpecObjectTypeRequirement::new(data_types),
125        }
126    }
127}
128
129#[derive(Debug, PartialEq, YaSerialize)]
130pub struct TypeDefinitionXHtmlRef {
131    #[yaserde(rename = "DATATYPE-DEFINITION-XHTML-REF")]
132    reference: String,
133}
134
135#[derive(Debug, PartialEq, YaSerialize)]
136pub struct AttributeDefinitionXHtml {
137    #[yaserde(attribute, rename = "IDENTIFIER")]
138    identifier: String,
139    #[yaserde(attribute, rename = "LAST-CHANGE")]
140    last_change: String,
141    #[yaserde(attribute, rename = "LONG-NAME")]
142    long_name: String,
143    #[yaserde(rename = "TYPE")]
144    type_ref: TypeDefinitionXHtmlRef,
145}
146
147#[derive(Debug, PartialEq, YaSerialize)]
148pub struct RequirementSpecObjectTypeAttributes {
149    #[yaserde(rename = "ATTRIBUTE-DEFINITION-XHTML")]
150    text_attribute: AttributeDefinitionXHtml,
151    #[yaserde(rename = "ATTRIBUTE-DEFINITION-XHTML")]
152    id_attribute: AttributeDefinitionXHtml,
153}
154
155#[derive(Debug, PartialEq, YaSerialize)]
156pub struct SpecObjectTypeRequirement {
157    #[yaserde(attribute, rename = "IDENTIFIER")]
158    identifier: String,
159    #[yaserde(attribute, rename = "LAST-CHANGE")]
160    last_change: String,
161    #[yaserde(attribute, rename = "LONG-NAME")]
162    long_name: String,
163    #[yaserde(rename = "SPEC-ATTRIBUTES")]
164    attributes: RequirementSpecObjectTypeAttributes,
165}
166
167impl SpecObjectTypeRequirement {
168    fn new(data_types: &DataTypes) -> Self {
169        SpecObjectTypeRequirement {
170            identifier: "SPEC-OBJEC-TYPE-REQ-TYPE-IDENTIFIER".to_string(),
171            long_name: "Requirement Type".to_string(),
172            last_change: get_default_last_change_date(),
173            attributes: RequirementSpecObjectTypeAttributes {
174                text_attribute: AttributeDefinitionXHtml {
175                    identifier: "ATTRIBUTE-DEFINITION-XHTML-REQIF.Text-ID".to_string(),
176                    last_change: get_default_last_change_date(),
177                    long_name: "ReqIF.Text".to_string(),
178                    type_ref: TypeDefinitionXHtmlRef {
179                        reference: data_types.xhtml_definition.identifier.clone(),
180                    },
181                },
182                id_attribute: AttributeDefinitionXHtml {
183                    identifier: "ATTRIBUTE-DEFINITION-XHTML-PUID-ID".to_string(),
184                    last_change: get_default_last_change_date(),
185                    long_name: "IE PUID".to_string(),
186                    type_ref: TypeDefinitionXHtmlRef {
187                        reference: data_types.xhtml_definition.identifier.clone(),
188                    },
189                },
190            },
191        }
192    }
193}
194
195#[derive(Debug, PartialEq, YaSerialize)]
196pub struct SpecObjectType {
197    #[yaserde(rename = "SPEC-OBJECT-TYPE-REF")]
198    reference: String,
199}
200
201#[derive(Debug, PartialEq, YaSerialize)]
202pub struct AttributeValueXHtmlDefinition {
203    #[yaserde(rename = "ATTRIBUTE-DEFINITION-XHTML-REF")]
204    reference: String,
205}
206
207#[derive(Debug, PartialEq, YaSerialize)]
208pub struct XHtmlValue {
209    #[yaserde(rename = "xhtml:div")]
210    value: String,
211}
212#[derive(Debug, PartialEq, YaSerialize)]
213pub struct AttributeValueXHtml {
214    #[yaserde(rename = "THE-VALUE")]
215    the_value: XHtmlValue,
216    #[yaserde(rename = "DEFINITION")]
217    definition: AttributeValueXHtmlDefinition,
218}
219
220#[derive(Debug, PartialEq, YaSerialize)]
221pub struct SpecObjectRequirementValues {
222    #[yaserde(rename = "ATTRIBUTE-VALUE-XHTML")]
223    req_id: AttributeValueXHtml,
224    #[yaserde(rename = "ATTRIBUTE-VALUE-XHTML")]
225    req_text: AttributeValueXHtml,
226}
227
228#[derive(Debug, PartialEq, YaSerialize)]
229pub struct SpecObjectRequirement {
230    #[yaserde(attribute, rename = "IDENTIFIER")]
231    identifier: String,
232    #[yaserde(attribute, rename = "LAST-CHANGE")]
233    last_change: String,
234    #[yaserde(attribute, rename = "LONG-NAME")]
235    long_name: String,
236    #[yaserde(rename = "TYPE")]
237    spec_object_type: SpecObjectType,
238    #[yaserde(rename = "VALUES")]
239    values: SpecObjectRequirementValues,
240}
241
242impl SpecObjectRequirement {
243    pub fn new(
244        identifier: String,
245        last_change: String,
246        long_name: String,
247        text: String,
248        spec_types: &SpecTypes,
249    ) -> Self {
250        SpecObjectRequirement {
251            identifier: identifier.clone(),
252            last_change,
253            long_name,
254            spec_object_type: SpecObjectType {
255                reference: spec_types.spec_object_type_requirement.identifier.clone(),
256            },
257            values: SpecObjectRequirementValues {
258                req_id: AttributeValueXHtml {
259                    definition: AttributeValueXHtmlDefinition {
260                        reference: spec_types
261                            .spec_object_type_requirement
262                            .attributes
263                            .id_attribute
264                            .identifier
265                            .clone(),
266                    },
267                    the_value: XHtmlValue { value: identifier },
268                },
269                req_text: AttributeValueXHtml {
270                    definition: AttributeValueXHtmlDefinition {
271                        reference: spec_types
272                            .spec_object_type_requirement
273                            .attributes
274                            .text_attribute
275                            .identifier
276                            .clone(),
277                    },
278                    the_value: XHtmlValue { value: text },
279                },
280            },
281        }
282    }
283}
284
285#[derive(Debug, PartialEq, YaSerialize)]
286pub struct SpecObjects {
287    #[yaserde(rename = "SPEC-OBJECT")]
288    requirements: Vec<SpecObjectRequirement>,
289}
290
291#[derive(Debug, PartialEq, YaSerialize)]
292pub struct SpecificationRef {
293    #[yaserde(rename = "SPECIFICATION-TYPE-REF")]
294    pub spec_ref: String,
295}
296
297#[derive(Debug, PartialEq, YaSerialize)]
298pub struct Object {
299    #[yaserde(rename = "SPEC-OBJECT-REF")]
300    pub object_ref: String,
301}
302impl Object {
303    pub fn new(object_ref: String) -> Object {
304        Object { object_ref }
305    }
306}
307
308#[derive(Debug, PartialEq, YaSerialize)]
309pub struct SpecHierarchy {
310    #[yaserde(attribute, rename = "IDENTIFIER")]
311    pub identifier: String,
312    #[yaserde(attribute, rename = "LAST-CHANGE")]
313    pub last_change: String,
314    #[yaserde(rename = "OBJECT")]
315    pub object: Object,
316    #[yaserde(rename = "CHILDREN")]
317    pub children: Option<Children>,
318}
319
320impl SpecHierarchy {
321    pub fn new(identifier: String, last_change: String, object: Object) -> Self {
322        SpecHierarchy {
323            identifier,
324            last_change,
325            object,
326            children: None,
327        }
328    }
329}
330
331#[derive(Debug, PartialEq, YaSerialize)]
332pub struct Children {
333    #[yaserde(rename = "SPEC-HIERARCHY")]
334    spec_hierarchy: Vec<SpecHierarchy>,
335}
336
337impl Children {
338    /// Adds the `spec_hierarchy` as the last children in the given `depth`.
339    /// a `depth` of 0 means add the spec as direct children.
340    /// # Panics:
341    /// Panic will occur in case that any intermediate level is missing.
342    pub fn add_spec_hierarchy(
343        &mut self,
344        spec_hierarchy: SpecHierarchy,
345        mut depth: i32,
346    ) -> Result<()> {
347        if depth == 0 {
348            self.spec_hierarchy.push(spec_hierarchy);
349        } else {
350            let spec = match self.spec_hierarchy.last_mut() {
351                Some(s) => s,
352                None => bail!("Missing spech hierarchy at level: {}", depth),
353            };
354
355            depth -= 1;
356            if spec.children.is_none() {
357                spec.children = Some(Children::new());
358            }
359
360            spec.children
361                .as_mut()
362                .unwrap()
363                .add_spec_hierarchy(spec_hierarchy, depth)?;
364        }
365        Ok(())
366    }
367
368    pub fn new() -> Self {
369        Children {
370            spec_hierarchy: Vec::new(),
371        }
372    }
373
374    pub fn get_spec_hierarchy(&self) -> &Vec<SpecHierarchy> {
375        &self.spec_hierarchy
376    }
377}
378
379#[derive(Debug, PartialEq, YaSerialize)]
380pub struct Specification {
381    #[yaserde(attribute, rename = "IDENTIFIER")]
382    pub identifier: String,
383    #[yaserde(attribute, rename = "LAST-CHANGE")]
384    pub last_change: String,
385    #[yaserde(attribute, rename = "LONG-NAME")]
386    pub long_name: String,
387    #[yaserde(rename = "TYPE")]
388    pub type_ref: SpecificationRef,
389    #[yaserde(rename = "CHILDREN")]
390    pub children: Children,
391}
392
393#[derive(Debug, PartialEq, YaSerialize)]
394pub struct Specifications {
395    #[yaserde(rename = "SPECIFICATION")]
396    specifications: Vec<Specification>,
397}
398
399#[derive(Debug, PartialEq, YaSerialize)]
400pub struct ReqIfContent {
401    #[yaserde(rename = "DATATYPES")]
402    pub data_types: DataTypes,
403    #[yaserde(rename = "SPEC-TYPES")]
404    pub spec_types: SpecTypes,
405    #[yaserde(rename = "SPEC-OBJECTS")]
406    pub spec_object: SpecObjects,
407    #[yaserde(rename = "SPECIFICATIONS")]
408    pub specifications: Specifications,
409}
410
411#[derive(Debug, PartialEq, YaSerialize)]
412pub struct CoreContent {
413    #[yaserde(rename = "REQ-IF-CONTENT")]
414    pub req_if_content: ReqIfContent,
415}
416
417impl CoreContent {
418    pub fn new() -> Self {
419        let data_types = DataTypes::new();
420        let spec_types = SpecTypes::new(&data_types);
421        CoreContent {
422            req_if_content: ReqIfContent {
423                spec_object: SpecObjects {
424                    requirements: vec![],
425                },
426                specifications: Specifications {
427                    specifications: vec![],
428                },
429                spec_types,
430                data_types,
431            },
432        }
433    }
434}
435
436#[derive(Debug, PartialEq, YaSerialize)]
437#[yaserde(rename = "REQ-IF")]
438pub struct ReqIf {
439    #[yaserde(attribute)]
440    pub xmlns: String,
441    #[yaserde(rename = "xmlns:xhtml", attribute)]
442    pub xmlns_xhtml: String,
443    #[yaserde(rename = "THE-HEADER")]
444    pub the_header: TheHeader,
445    #[yaserde(rename = "CORE-CONTENT")]
446    pub core_content: CoreContent,
447}
448
449impl ReqIf {
450    pub fn new(
451        identifier: String,
452        creation_time: DateTime<Local>,
453        repository_id: String,
454        req_if_tool_id: String,
455        source_tool_id: String,
456        title: String,
457    ) -> Self {
458        let req_if_header = ReqIfHeader {
459            identifier,
460            creation_time: creation_time.to_rfc3339_opts(SecondsFormat::Millis, false),
461            repository_id,
462            req_if_tool_id,
463            req_if_version: "1.0".to_string(),
464            source_tool_id,
465            title,
466        };
467
468        let the_header = TheHeader { req_if_header };
469        let xmlns = "http://www.omg.org/spec/ReqIF/20110401/reqif.xsd".to_string();
470        let xmlns_xhtml = "http://www.w3.org/1999/xhtml".to_string();
471
472        ReqIf {
473            the_header,
474            xmlns,
475            xmlns_xhtml,
476            core_content: CoreContent::new(),
477        }
478    }
479
480    pub fn add_requirement(&mut self, requirement: SpecObjectRequirement) {
481        self.core_content
482            .req_if_content
483            .spec_object
484            .requirements
485            .push(requirement);
486    }
487
488    pub fn build_module_specification(
489        &mut self,
490        identifier: String,
491        last_change: String,
492        long_name: String,
493    ) -> Specification {
494        Specification {
495            identifier,
496            last_change,
497            long_name,
498            type_ref: SpecificationRef {
499                spec_ref: self.get_module_specification_type().clone(),
500            },
501            children: Children {
502                spec_hierarchy: vec![],
503            },
504        }
505    }
506
507    pub fn add_specification(&mut self, specification: Specification) {
508        self.core_content
509            .req_if_content
510            .specifications
511            .specifications
512            .push(specification);
513    }
514
515    pub fn get_module_specification_type(&self) -> &String {
516        &self
517            .core_content
518            .req_if_content
519            .spec_types
520            .specification_type_module
521            .identifier
522    }
523
524    pub fn write_to(&self, filename: &str) -> anyhow::Result<()> {
525        let yaserde_cfg = yaserde::ser::Config {
526            perform_indent: true,
527            ..Default::default()
528        };
529
530        let s = match yaserde::ser::to_string_with_config(self, &yaserde_cfg) {
531            Ok(s) => s,
532            Err(s) => bail!(s),
533        };
534
535        let mut file = File::create(filename)?;
536        let _ = file.write_all(s.as_bytes());
537        Ok(())
538    }
539}
540
541#[cfg(test)]
542mod test {
543    use super::{get_default_last_change_date, Children, Object, SpecHierarchy};
544
545    #[test]
546    fn test_add_spec_hierarchy() {
547        let mut children = Children::new();
548        for _ in 0..4 {
549            children
550                .add_spec_hierarchy(
551                    SpecHierarchy::new(
552                        "2.1.2".to_string(),
553                        get_default_last_change_date(),
554                        Object::new("REQ001".to_string()),
555                    ),
556                    0,
557                )
558                .expect("Error");
559        }
560        assert_eq!(children.spec_hierarchy.len(), 4);
561    }
562
563    #[test]
564    fn test_add_spec_panic() {
565        let mut children = Children::new();
566        children
567            .add_spec_hierarchy(
568                SpecHierarchy::new(
569                    "2.1.2".to_string(),
570                    get_default_last_change_date(),
571                    Object::new("REQ001".to_string()),
572                ),
573                0,
574            )
575            .expect("error");
576        let res = children.add_spec_hierarchy(
577            SpecHierarchy::new(
578                "2.1.2".to_string(),
579                get_default_last_change_date(),
580                Object::new("REQ001".to_string()),
581            ),
582            2,
583        );
584        assert!(res.is_err());
585    }
586
587    #[test]
588    fn test_add_spec_hierarchy_2() {
589        let mut children = Children::new();
590        children
591            .add_spec_hierarchy(
592                SpecHierarchy::new(
593                    "2.1.2".to_string(),
594                    get_default_last_change_date(),
595                    Object::new("REQ001".to_string()),
596                ),
597                0,
598            )
599            .expect("error");
600        children
601            .add_spec_hierarchy(
602                SpecHierarchy::new(
603                    "2.1.2".to_string(),
604                    get_default_last_change_date(),
605                    Object::new("REQ001".to_string()),
606                ),
607                1,
608            )
609            .expect("error");
610
611        children
612            .add_spec_hierarchy(
613                SpecHierarchy::new(
614                    "2.1.2".to_string(),
615                    get_default_last_change_date(),
616                    Object::new("REQ001".to_string()),
617                ),
618                1,
619            )
620            .expect("error");
621
622        assert_eq!(children.spec_hierarchy.len(), 1);
623        let spec = children.get_spec_hierarchy().last().unwrap();
624        let len = spec.children.as_ref().unwrap().spec_hierarchy.len();
625        assert_eq!(len, 2)
626    }
627}