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}