dusk_cdf/encoder/
context.rs

1use std::collections::HashMap;
2use std::ops::Deref;
3use std::{fs, io};
4
5use msgpacker::Message;
6
7use crate::{Config, Preamble};
8
9/// Encoding provider that will convert paths into file contents
10pub trait EncoderContextProvider {
11    /// Fetch the contents of a given path
12    fn contents<P>(&mut self, path: P) -> io::Result<String>
13    where
14        P: AsRef<str>;
15}
16
17/// Default encoding provider with a filesystem backend
18#[derive(Debug, Default, Clone, PartialEq, Eq)]
19pub struct EncoderContextFileProvider;
20
21impl EncoderContextProvider for EncoderContextFileProvider {
22    fn contents<P>(&mut self, path: P) -> io::Result<String>
23    where
24        P: AsRef<str>,
25    {
26        fs::read_to_string(path.as_ref())
27    }
28}
29
30impl<V> EncoderContextProvider for HashMap<String, V>
31where
32    V: AsRef<str>,
33{
34    fn contents<P>(&mut self, path: P) -> io::Result<String>
35    where
36        P: AsRef<str>,
37    {
38        self.get(path.as_ref())
39            .map(|p| p.as_ref().to_string())
40            .ok_or_else(|| {
41                io::Error::new(
42                    io::ErrorKind::NotFound,
43                    "the provided path was not found in the disk",
44                )
45            })
46    }
47}
48
49/// Context of encoding a CDF file
50#[derive(Debug, Default, Clone, PartialEq, Eq)]
51pub struct EncoderContext {
52    preamble: Preamble,
53    path_cache: HashMap<String, usize>,
54}
55
56impl EncoderContext {
57    /// Start a new context
58    ///
59    /// This function is not intended to be called outside the encoder initialization so we don't
60    /// have duplicated contexts
61    pub(crate) fn from_preamble(preamble: Preamble) -> Self {
62        Self {
63            preamble,
64            path_cache: HashMap::new(),
65        }
66    }
67
68    /// Configuration used for the encoding
69    pub const fn config(&self) -> &Config {
70        &self.preamble.config
71    }
72
73    /// Preamble of the context
74    pub const fn preamble(&self) -> &Preamble {
75        &self.preamble
76    }
77
78    /// Append a path to the encoding context, returning its index
79    pub fn add_path<P>(&mut self, path: P) -> usize
80    where
81        P: Into<String>,
82    {
83        let path = path.into();
84        let len = self.path_cache.len();
85
86        *self.path_cache.entry(path).or_insert(len)
87    }
88}
89
90impl EncoderContext {
91    pub fn write_all<P, W>(&self, mut writer: W, mut provider: P) -> io::Result<usize>
92    where
93        P: EncoderContextProvider,
94        W: io::Write,
95    {
96        let mut contents = self.path_cache.iter().collect::<Vec<_>>();
97
98        contents.as_mut_slice().sort_by_key(|(_p, i)| *i);
99
100        let paths = contents
101            .iter()
102            .map(|(p, _i)| p.to_string())
103            .collect::<Vec<_>>();
104
105        let contents = paths
106            .iter()
107            .map(|p| provider.contents(p))
108            .map(|p| p.map(Message::String))
109            .collect::<io::Result<Vec<_>>>()?;
110
111        let paths = paths.into_iter().map(Message::String).collect();
112
113        let n = Message::Array(paths).pack(&mut writer)?;
114        let n = n + Message::Array(contents).pack(&mut writer)?;
115
116        Ok(n)
117    }
118}
119
120impl Deref for EncoderContext {
121    type Target = HashMap<String, usize>;
122
123    fn deref(&self) -> &Self::Target {
124        &self.path_cache
125    }
126}
127
128#[cfg(test)]
129use std::path::PathBuf;
130
131#[test]
132fn path_cache_is_not_duplicated() {
133    let main = PathBuf::from("home")
134        .join("zkp-debugger")
135        .join("main.rs")
136        .display()
137        .to_string();
138
139    let lib = PathBuf::from("home")
140        .join("zkp-debugger")
141        .join("lib.rs")
142        .display()
143        .to_string();
144
145    let mut context = EncoderContext::from_preamble(Default::default());
146
147    let idx_main = context.add_path(main.clone());
148
149    // duplicated path is not added; same index should be returned
150    assert_eq!(idx_main, context.add_path(main.clone()));
151
152    let idx_lib = context.add_path(lib.clone());
153
154    // lib is a different path so it should have a different index
155    assert_ne!(idx_main, idx_lib);
156}
157
158#[test]
159fn context_derives_expected_map() {
160    let main = PathBuf::from("home")
161        .join("zkp-debugger")
162        .join("main.rs")
163        .display()
164        .to_string();
165
166    let lib = PathBuf::from("home")
167        .join("zkp-debugger")
168        .join("lib.rs")
169        .display()
170        .to_string();
171
172    let mut context = EncoderContext::from_preamble(Default::default());
173
174    let idx_main = context.add_path(main.clone());
175    let idx_lib = context.add_path(lib.clone());
176
177    let expected_map: HashMap<String, usize> = vec![(main.clone(), idx_main), (lib, idx_lib)]
178        .into_iter()
179        .collect();
180
181    // deref op should extend map methods to context
182    assert_eq!(expected_map[&main], context[&main]);
183
184    // resulting map should equal expect
185    assert_eq!(*context, expected_map);
186}
187
188#[test]
189fn preamble_is_correctly_created() {
190    // TODO test all permutations of preamble in integration/fuzz
191    let preamble = Preamble::default();
192    let context = EncoderContext::from_preamble(preamble);
193
194    // context was created with the right preamble
195    assert_eq!(&preamble, context.preamble());
196}