Skip to content

Commit 264fc3a

Browse files
authored
Merge pull request #23 from LilyFoote/test-filesystem-loader
Add tests for FileSystemLoader and improve errors
2 parents 84c5538 + f6e3cee commit 264fc3a

File tree

3 files changed

+110
-20
lines changed

3 files changed

+110
-20
lines changed

src/loaders.rs

+103-14
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ use std::collections::HashMap;
22
use std::path::{Path, PathBuf};
33

44
use encoding_rs::Encoding;
5+
use pyo3::exceptions::PyUnicodeError;
56
use pyo3::prelude::*;
67

78
use crate::template::django_rusty_templates::Template;
89

9-
#[derive(Clone)]
10+
#[derive(Clone, Debug, PartialEq, Eq)]
1011
pub struct LoaderError {
1112
pub tried: Vec<(String, String)>,
1213
}
@@ -30,7 +31,11 @@ impl FileSystemLoader {
3031
}
3132
}
3233

33-
fn get_template(&self, template_name: &str) -> Result<PyResult<Template>, LoaderError> {
34+
fn get_template(
35+
&self,
36+
py: Python<'_>,
37+
template_name: &str,
38+
) -> Result<PyResult<Template>, LoaderError> {
3439
let mut tried = Vec::new();
3540
for template_dir in &self.dirs {
3641
let path = match safe_join(template_dir, template_name) {
@@ -47,7 +52,13 @@ impl FileSystemLoader {
4752
continue;
4853
}
4954
};
50-
let (contents, _, _) = self.encoding.decode(&bytes);
55+
let (contents, encoding, malformed) = self.encoding.decode(&bytes);
56+
if malformed {
57+
return Ok(Err(PyUnicodeError::new_err(format!(
58+
"Could not open {path:?} with {} encoding.",
59+
encoding.name()
60+
))));
61+
}
5162
return Ok(Template::new(&contents, path));
5263
}
5364
Err(LoaderError { tried })
@@ -57,7 +68,11 @@ impl FileSystemLoader {
5768
pub struct AppDirsLoader {}
5869

5970
impl AppDirsLoader {
60-
fn get_template(&self, template_name: &str) -> Result<PyResult<Template>, LoaderError> {
71+
fn get_template(
72+
&self,
73+
py: Python<'_>,
74+
template_name: &str,
75+
) -> Result<PyResult<Template>, LoaderError> {
6176
todo!()
6277
}
6378
}
@@ -75,14 +90,18 @@ impl CachedLoader {
7590
}
7691
}
7792

78-
fn get_template(&mut self, template_name: &str) -> Result<PyResult<Template>, LoaderError> {
93+
fn get_template(
94+
&mut self,
95+
py: Python<'_>,
96+
template_name: &str,
97+
) -> Result<PyResult<Template>, LoaderError> {
7998
match self.cache.get(template_name) {
8099
Some(Ok(template)) => Ok(Ok(template.clone())),
81100
Some(Err(e)) => Err(e.clone()),
82101
None => {
83102
let mut tried = Vec::new();
84103
for loader in &mut self.loaders {
85-
match loader.get_template(template_name) {
104+
match loader.get_template(py, template_name) {
86105
Ok(Ok(template)) => {
87106
self.cache
88107
.insert(template_name.to_string(), Ok(template.clone()));
@@ -101,15 +120,23 @@ impl CachedLoader {
101120
pub struct LocMemLoader {}
102121

103122
impl LocMemLoader {
104-
fn get_template(&self, template_name: &str) -> Result<PyResult<Template>, LoaderError> {
123+
fn get_template(
124+
&self,
125+
py: Python<'_>,
126+
template_name: &str,
127+
) -> Result<PyResult<Template>, LoaderError> {
105128
todo!()
106129
}
107130
}
108131

109132
pub struct ExternalLoader {}
110133

111134
impl ExternalLoader {
112-
fn get_template(&self, template_name: &str) -> Result<PyResult<Template>, LoaderError> {
135+
fn get_template(
136+
&self,
137+
py: Python<'_>,
138+
template_name: &str,
139+
) -> Result<PyResult<Template>, LoaderError> {
113140
todo!()
114141
}
115142
}
@@ -123,13 +150,75 @@ pub enum Loader {
123150
}
124151

125152
impl Loader {
126-
pub fn get_template(&mut self, template_name: &str) -> Result<PyResult<Template>, LoaderError> {
153+
pub fn get_template(
154+
&mut self,
155+
py: Python<'_>,
156+
template_name: &str,
157+
) -> Result<PyResult<Template>, LoaderError> {
127158
match self {
128-
Self::FileSystem(loader) => loader.get_template(template_name),
129-
Self::AppDirs(loader) => loader.get_template(template_name),
130-
Self::Cached(loader) => loader.get_template(template_name),
131-
Self::LocMem(loader) => loader.get_template(template_name),
132-
Self::External(loader) => loader.get_template(template_name),
159+
Self::FileSystem(loader) => loader.get_template(py, template_name),
160+
Self::AppDirs(loader) => loader.get_template(py, template_name),
161+
Self::Cached(loader) => loader.get_template(py, template_name),
162+
Self::LocMem(loader) => loader.get_template(py, template_name),
163+
Self::External(loader) => loader.get_template(py, template_name),
133164
}
134165
}
135166
}
167+
168+
#[cfg(test)]
169+
mod tests {
170+
use super::*;
171+
172+
#[test]
173+
fn test_filesystem_loader() {
174+
pyo3::prepare_freethreaded_python();
175+
176+
Python::with_gil(|py| {
177+
let loader =
178+
FileSystemLoader::new(vec!["tests/templates".to_string()], encoding_rs::UTF_8);
179+
let template = loader.get_template(py, "basic.txt").unwrap().unwrap();
180+
181+
assert_eq!(
182+
template.filename.unwrap(),
183+
PathBuf::from("tests/templates/basic.txt")
184+
);
185+
})
186+
}
187+
188+
#[test]
189+
fn test_filesystem_loader_missing_template() {
190+
pyo3::prepare_freethreaded_python();
191+
192+
Python::with_gil(|py| {
193+
let loader =
194+
FileSystemLoader::new(vec!["tests/templates".to_string()], encoding_rs::UTF_8);
195+
let error = loader.get_template(py, "missing.txt").unwrap_err();
196+
197+
assert_eq!(
198+
error,
199+
LoaderError {
200+
tried: vec![(
201+
"tests/templates/missing.txt".to_string(),
202+
"Source does not exist".to_string(),
203+
)],
204+
},
205+
);
206+
})
207+
}
208+
209+
#[test]
210+
fn test_filesystem_loader_invalid_encoding() {
211+
pyo3::prepare_freethreaded_python();
212+
213+
Python::with_gil(|py| {
214+
let loader =
215+
FileSystemLoader::new(vec!["tests/templates".to_string()], encoding_rs::UTF_8);
216+
let error = loader.get_template(py, "invalid.txt").unwrap().unwrap_err();
217+
218+
assert_eq!(
219+
error.to_string(),
220+
"UnicodeError: Could not open \"tests/templates/invalid.txt\" with UTF-8 encoding."
221+
);
222+
})
223+
}
224+
}

src/template.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,10 @@ pub mod django_rusty_templates {
106106
})
107107
}
108108

109-
fn get_template(&mut self, template_name: String) -> PyResult<Template> {
109+
fn get_template(&mut self, py: Python<'_>, template_name: String) -> PyResult<Template> {
110110
let mut tried = Vec::new();
111111
for loader in &mut self.template_loaders {
112-
match loader.get_template(&template_name) {
112+
match loader.get_template(py, &template_name) {
113113
Ok(template) => return template,
114114
Err(e) => tried.push(e.tried),
115115
}
@@ -118,12 +118,12 @@ pub mod django_rusty_templates {
118118
}
119119
}
120120

121-
#[derive(Clone)]
121+
#[derive(Clone, Debug)]
122122
#[pyclass]
123123
pub struct Template {
124-
filename: Option<PathBuf>,
125-
template: String,
126-
nodes: Vec<TokenTree>,
124+
pub filename: Option<PathBuf>,
125+
pub template: String,
126+
pub nodes: Vec<TokenTree>,
127127
}
128128

129129
impl Template {

tests/templates/invalid.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
�(�

0 commit comments

Comments
 (0)