subplotlib/
file.rs

1//! Subplot data files
2//!
3//! Subplot can embed data files into test suites.  This module provides
4//! the representation of the files in a way designed to be cheap to clone
5//! so that they can be passed around "by value".
6
7use std::path::{Path, PathBuf};
8use std::sync::Arc;
9
10use base64::prelude::{Engine as _, BASE64_STANDARD};
11
12/// An embedded data file.
13///
14/// Embedded data files have names and content.  The subplot template will generate
15/// a `lazy_static` containing all the data files embedded into the suite.  Then
16/// generated test functions will extract data files
17///
18/// If you are using them in your test steps you should take them by value:
19///
20/// ```rust
21/// # use subplotlib::prelude::*;
22/// # #[derive(Debug, Default)]
23/// # struct Context {}
24/// # impl ContextElement for Context {}
25/// #[step]
26/// fn step_using_a_file(context: &mut Context, somefile: SubplotDataFile) {
27///     // context.stash_file(somefile);
28/// }
29///
30/// # fn main() {}
31/// ```
32///
33/// For the generated test to correctly recognise how to pass a file in, you
34/// **must** mark the argument as a file in your binding:
35///
36/// ```yaml
37/// - when: using {somefile} as a input
38///   function: step_using_a_file
39///   types:
40///     somefile: file
41/// ```
42#[derive(Debug)]
43pub struct SubplotDataFile {
44    name: Arc<Path>,
45    data: Arc<[u8]>,
46}
47
48impl Clone for SubplotDataFile {
49    fn clone(&self) -> Self {
50        Self {
51            name: Arc::clone(&self.name),
52            data: Arc::clone(&self.data),
53        }
54    }
55}
56
57impl Default for SubplotDataFile {
58    fn default() -> Self {
59        Self {
60            name: PathBuf::from("").into(),
61            data: Vec::new().into(),
62        }
63    }
64}
65
66impl SubplotDataFile {
67    /// Construct a new data file object
68    ///
69    /// Typically this will only be called from the generated test suite.
70    /// The passed in name and data must be base64 encoded strings and each will
71    /// be interpreted independently.  The name will be treated as a [`PathBuf`]
72    /// and the data will be stored as a slice of bytes.
73    ///
74    /// Neither will be interpreted as utf8.
75    ///
76    /// ```
77    /// # use subplotlib::prelude::*;
78    ///
79    /// let data_file = SubplotDataFile::new("aGVsbG8=", "d29ybGQ=");
80    /// ```
81    ///
82    /// # Panics
83    ///
84    /// This will panic if the passed in strings are not correctly base64 encoded.
85    pub fn new(name: &str, data: &str) -> Self {
86        let name = BASE64_STANDARD
87            .decode(name)
88            .expect("Subplot generated bad base64?");
89        let name = String::from_utf8_lossy(&name);
90        let name: PathBuf = name.as_ref().into();
91        let name = name.into();
92        let data = BASE64_STANDARD
93            .decode(data)
94            .expect("Subplot generated bad base64?")
95            .into();
96        Self { name, data }
97    }
98
99    /// Retrieve the filename
100    ///
101    /// File names are returned as a borrow of a [`Path`] since they could be
102    /// arbitrarily constructed, though typically they'll have come from markdown
103    /// source and so likely they will be utf-8 compatible.
104    ///
105    /// ```
106    /// # use subplotlib::prelude::*;
107    /// let data_file = SubplotDataFile::new("aGVsbG8=", "d29ybGQ=");
108    /// assert_eq!(data_file.name().display().to_string(), "hello");
109    /// ```
110    pub fn name(&self) -> &Path {
111        &self.name
112    }
113
114    /// Retrieve the data
115    ///
116    /// The data of a file is always returned as a slice of bytes.  This is because
117    /// files could be arbitrary data, though again, they typically will have been
118    /// sourced from a Subplot document and so be utf-8 compatible.
119    ///
120    /// ```
121    /// # use subplotlib::prelude::*;
122    ///
123    /// let data_file = SubplotDataFile::new("aGVsbG8=", "d29ybGQ=");
124    /// assert_eq!(data_file.data(), b"world");
125    /// ```
126    pub fn data(&self) -> &[u8] {
127        &self.data
128    }
129}