Repository: mitsuhiko/minijinja
Pull request #616 (2024-10-26 15:10:23)

Added improved support for bytes
This adds a `Value::from_bytes` method to directly create a bytes value, bypassing the serialization interface.  It also improves the debug printing of bytes meaningfully.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index db705bf..dfa94ff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,9 @@ All notable changes to MiniJinja are documented here.
 - Fixed an issue where CBOR was not correctly deserialized in
   `minijinja-cli`.  #611
 - Added a `lines` filter to split a string into lines.
+- Bytes are now better supported in MiniJinja.  They can be created from
+  `Value::from_bytes` without having to go via serde, and they are now
+  producing a nicer looking debug output.  #616
 
 ## 2.4.0
 
diff --git a/minijinja/src/value/mod.rs b/minijinja/src/value/mod.rs
index 2b2b32b..de289c6 100644
--- a/minijinja/src/value/mod.rs
+++ b/minijinja/src/value/mod.rs
@@ -439,7 +439,17 @@ impl fmt::Debug for ValueRepr {
             ValueRepr::I128(val) => fmt::Debug::fmt(&{ val.0 }, f),
             ValueRepr::String(val, _) => fmt::Debug::fmt(val, f),
             ValueRepr::SmallStr(val) => fmt::Debug::fmt(val.as_str(), f),
-            ValueRepr::Bytes(val) => fmt::Debug::fmt(val, f),
+            ValueRepr::Bytes(val) => {
+                write!(f, "b'")?;
+                for &b in val.iter() {
+                    if b == b'"' {
+                        write!(f, "\"")?
+                    } else {
+                        write!(f, "{}", b.escape_ascii())?;
+                    }
+                }
+                write!(f, "'")
+            }
             ValueRepr::Object(val) => val.render(f),
         }
     }
@@ -745,6 +755,20 @@ impl Value {
         ValueRepr::String(Arc::from(value), StringType::Safe).into()
     }
 
+    /// Creates a value from a byte vector.
+    ///
+    /// MiniJinja can hold on to bytes and has some limited built-in support for
+    /// working with them.  They are non iterable and not particularly useful
+    /// in the context of templates.  When they are stringified, they are assumed
+    /// to contain UTF-8 and will be treated as such.  They become more useful
+    /// when a filter can do something with them (eg: base64 encode them etc.).
+    ///
+    /// This method exists so that a value can be constructed as creating a
+    /// value from a `Vec<u8>` would normally just create a sequence.
+    pub fn from_bytes(value: Vec<u8>) -> Value {
+        ValueRepr::Bytes(value.into()).into()
+    }
+
     /// Creates a value from a dynamic object.
     ///
     /// For more information see [`Object`].


Test patch:
diff --git a/minijinja/tests/test_value.rs b/minijinja/tests/test_value.rs
index eb1ec23..051f6a4 100644
--- a/minijinja/tests/test_value.rs
+++ b/minijinja/tests/test_value.rs
@@ -4,7 +4,7 @@ use std::sync::Arc;
 use insta::{assert_debug_snapshot, assert_snapshot};
 use similar_asserts::assert_eq;
 
-use minijinja::value::{DynObject, Enumerator, Kwargs, Object, ObjectRepr, Rest, Value};
+use minijinja::value::{DynObject, Enumerator, Kwargs, Object, ObjectRepr, Rest, Value, ValueKind};
 use minijinja::{args, context, render, Environment, Error, ErrorKind};
 
 #[test]
@@ -1187,3 +1187,23 @@ fn test_sorting() {
     ]
     "###);
 }
+
+#[test]
+fn test_bytes() {
+    let bytes = vec![1u8, 2, 3, 4];
+    let byte_value = Value::from_bytes(bytes);
+    assert_eq!(byte_value.kind(), ValueKind::Bytes);
+    assert!(byte_value.try_iter().is_err());
+    assert_eq!(format!("{:?}", byte_value), "b'\\x01\\x02\\x03\\x04'");
+
+    let bytes = vec![1u8, 2, 3, 4];
+    let not_byte_value = Value::from(bytes);
+    assert_eq!(not_byte_value.kind(), ValueKind::Seq);
+    assert!(not_byte_value.try_iter().is_ok());
+    assert_eq!(format!("{:?}", not_byte_value), "[1, 2, 3, 4]");
+
+    assert_eq!(
+        format!("{:?}", Value::from_bytes((&b"'foo\""[..]).into())),
+        "b'\\'foo\"'"
+    );
+}