1use 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 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}