Repository: djc/askama
Pull request #32 (2017-08-19 08:45:52)

Adding optional json filter (Take #2)
I didn't forget about this; just got buried.

The last PR wasn't going to merge cleanly anymore, so I opened a new one rebased from master. I believe this addresses all the discussion items except that the json filter can still panic - which I think will require having all the filters return `Result` - but haven't jumped into that yet.

djc: ec5a2ebbb375d5ad49daeba72ec6aeb874d4a81e requires filters to return `Result`.

(BTW, feel free to force-push rebased changes to your pull request branches, rather than opening a new PR for them.)
anowell: rebased and force-pushed with `Result` support.

Patch:
diff --git a/askama/Cargo.toml b/askama/Cargo.toml
index 57708ee..32a8c03 100644
--- a/askama/Cargo.toml
+++ b/askama/Cargo.toml
@@ -18,3 +18,12 @@ travis-ci = { repository = "djc/askama" }
 [dependencies]
 askama_derive = { path = "../askama_derive", version = "0.3.4" }
 error-chain = "0.10"
+serde = { version = "1.0", optional = true }
+serde_json = { version = "1.0", optional = true }
+
+[features]
+serde-json = ["serde", "serde_json"]
+default = []
+
+[package.metadata.docs.rs]
+features = [ "serde-json" ]
diff --git a/askama/src/filters/json.rs b/askama/src/filters/json.rs
new file mode 100644
index 0000000..4443fb4
--- /dev/null
+++ b/askama/src/filters/json.rs
@@ -0,0 +1,30 @@
+use serde::Serialize;
+use serde_json;
+use errors::{Error, Result};
+
+/// Serialize to JSON (requires `serde-json` feature)
+///
+/// ## Errors
+///
+/// This will panic if `S`'s implementation of `Serialize` decides to fail,
+/// or if `T` contains a map with non-string keys.
+pub fn json<S: Serialize>(s: &S) -> Result<String> {
+    serde_json::to_string_pretty(s).map_err(Error::from)
+}
+
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_json() {
+        assert_eq!(json(&true).unwrap(), "true");
+        assert_eq!(json(&"foo").unwrap(), r#""foo""#);
+        assert_eq!(json(&vec!["foo", "bar"]).unwrap(),
+r#"[
+  "foo",
+  "bar"
+]"#);
+    }
+}
diff --git a/askama/src/filters.rs b/askama/src/filters/mod.rs
similarity index 96%
rename from askama/src/filters.rs
rename to askama/src/filters/mod.rs
index b8673e6..ca229ca 100644
--- a/askama/src/filters.rs
+++ b/askama/src/filters/mod.rs
@@ -2,9 +2,17 @@
 //!
 //! Contains all the built-in filter functions for use in templates.
 //! Currently, there is no way to define filters outside this module.
+
+#[cfg(feature = "serde-json")]
+mod json;
+
+#[cfg(feature = "serde-json")]
+pub use self::json::json;
+
 use std::fmt;
 use super::Result;
 
+
 fn escapable(b: &u8) -> bool {
     *b == b'<' || *b == b'>' || *b == b'&'
 }
diff --git a/askama/src/lib.rs b/askama/src/lib.rs
index 9c1d648..1870363 100644
--- a/askama/src/lib.rs
+++ b/askama/src/lib.rs
@@ -216,6 +216,11 @@ extern crate askama_derive;
 #[macro_use]
 extern crate error_chain;
 
+#[cfg(feature = "serde-json")]
+extern crate serde;
+#[cfg(feature = "serde-json")]
+extern crate serde_json;
+
 use std::env;
 use std::fmt;
 use std::fs::{self, DirEntry};
@@ -279,6 +284,7 @@ mod errors {
     error_chain! {
         foreign_links {
             Fmt(::std::fmt::Error);
+            Json(::serde_json::Error) #[cfg(feature = "serde-json")];
         }
     }
 }


Test patch:
diff --git a/testing/Cargo.toml b/testing/Cargo.toml
index bb6b534..be9af26 100644
--- a/testing/Cargo.toml
+++ b/testing/Cargo.toml
@@ -6,7 +6,8 @@ workspace = ".."
 build = "build.rs"
 
 [dependencies]
-askama = { path = "../askama", version = "*" }
+serde_json = "1.0"
+askama = { path = "../askama", version = "*", features = ["serde-json"] }
 
 [build-dependencies]
-askama = { path = "../askama", version = "*" }
+askama = { path = "../askama", version = "*", features = ["serde-json"] }
diff --git a/testing/templates/json.html b/testing/templates/json.html
index e711c10..250b7be 100644
--- a/testing/templates/json.html
+++ b/testing/templates/json.html
@@ -1,1 +1,4 @@
-{"foo": "{{ foo }}", "bar": "{{ bar }}"}
+{
+  "foo": "{{ foo }}",
+  "bar": {{ bar|json }}
+}
diff --git a/testing/tests/simple.rs b/testing/tests/simple.rs
index 896e082..93faf50 100644
--- a/testing/tests/simple.rs
+++ b/testing/tests/simple.rs
@@ -1,7 +1,10 @@
 #[macro_use]
 extern crate askama;
+#[macro_use]
+extern crate serde_json;
 
 use askama::Template;
+use serde_json::Value;
 
 #[derive(Template)]
 #[template(path = "simple.html")]
@@ -146,16 +149,27 @@ fn test_generics() {
 #[template(path = "json.html")]
 struct JsonTemplate<'a> {
     foo: &'a str,
-    bar: &'a str,
+    bar: &'a Value,
 }
 
 #[test]
 fn test_json() {
-    let t = JsonTemplate { foo: "a", bar: "b" };
-    assert_eq!(t.render().unwrap(), "{\"foo\": \"a\", \"bar\": \"b\"}");
+    let val =  json!({"arr": [ "one", 2, true, null ]});
+    let t = JsonTemplate { foo: "a", bar: &val };
+    // Note: the json filter lacks a way to specify initial indentation
+    assert_eq!(t.render().unwrap(), r#"{
+  "foo": "a",
+  "bar": {
+  "arr": [
+    "one",
+    2,
+    true,
+    null
+  ]
+}
+}"#);
 }
 
-
 #[derive(Template)]
 #[template(path = "composition.html")]
 struct CompositionTemplate {