archunit/slices/fluentapi/
diagram_source.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
use std::{
    fs,
    path::{Path, PathBuf},
};

use crate::common::{ArchUnitError, TechnicalError};

use super::SliceConfigurationError;

#[derive(Debug, Clone, PartialEq, Eq)]
enum DiagramSourceValue {
    Inline(String),
    File(PathBuf),
}

/// Immutable inline or file-backed PlantUML source, read only by a terminal check.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DiagramSource {
    value: DiagramSourceValue,
}

impl DiagramSource {
    /// Stores inline diagram text without parsing it.
    #[must_use]
    pub fn inline(text: impl Into<String>) -> Self {
        Self {
            value: DiagramSourceValue::Inline(text.into()),
        }
    }

    /// Stores a path that will be read only when the terminal is checked.
    #[must_use]
    pub fn file(path: impl AsRef<Path>) -> Self {
        Self {
            value: DiagramSourceValue::File(path.as_ref().to_path_buf()),
        }
    }

    /// Returns whether this source carries inline text.
    #[must_use]
    pub const fn is_inline(&self) -> bool {
        matches!(self.value, DiagramSourceValue::Inline(_))
    }

    /// Returns the file path when this is a file-backed source.
    #[must_use]
    pub fn path(&self) -> Option<&Path> {
        match &self.value {
            DiagramSourceValue::Inline(_) => None,
            DiagramSourceValue::File(path) => Some(path),
        }
    }

    pub(super) fn configuration_error(&self) -> Option<SliceConfigurationError> {
        match &self.value {
            DiagramSourceValue::Inline(text) if text.trim().is_empty() => {
                Some(SliceConfigurationError::EmptyDiagramText)
            }
            DiagramSourceValue::File(path) if path.as_os_str().is_empty() => {
                Some(SliceConfigurationError::EmptyDiagramPath)
            }
            DiagramSourceValue::Inline(_) | DiagramSourceValue::File(_) => None,
        }
    }

    pub(super) fn read(&self) -> Result<String, ArchUnitError> {
        match &self.value {
            DiagramSourceValue::Inline(text) => Ok(text.clone()),
            DiagramSourceValue::File(path) => fs::read_to_string(path).map_err(|source| {
                TechnicalError::with_source(
                    format!("could not read PlantUML diagram '{}'", path.display()),
                    source,
                )
                .into()
            }),
        }
    }
}

#[cfg(test)]
mod tests {
    use std::path::Path;

    use super::DiagramSource;

    #[test]
    fn stores_inline_and_file_sources_without_reading_or_parsing() {
        let inline = DiagramSource::inline("not parsed yet");
        let file = DiagramSource::file("missing/architecture.puml");

        assert!(inline.is_inline());
        assert!(!file.is_inline());
        assert_eq!(file.path(), Some(Path::new("missing/architecture.puml")));
        assert!(inline.configuration_error().is_none());
        assert!(file.configuration_error().is_none());
    }
}