1#![doc = include_str!("../README.md")]
3#![allow(unknown_lints)]
4#![deny(
5 clippy::expect_used,
6 clippy::explicit_deref_methods,
7 clippy::option_if_let_else,
8 clippy::await_holding_lock,
9 clippy::cloned_instead_of_copied,
10 clippy::explicit_into_iter_loop,
11 clippy::flat_map_option,
12 clippy::fn_params_excessive_bools,
13 clippy::implicit_clone,
14 clippy::inefficient_to_string,
15 clippy::large_types_passed_by_value,
16 clippy::manual_ok_or,
17 clippy::map_flatten,
18 clippy::map_unwrap_or,
19 clippy::must_use_candidate,
20 clippy::needless_for_each,
21 clippy::needless_pass_by_value,
22 clippy::option_option,
23 clippy::redundant_else,
24 clippy::semicolon_if_nothing_returned,
25 clippy::too_many_lines,
26 clippy::trivially_copy_pass_by_ref,
27 clippy::unnested_or_patterns,
28 clippy::future_not_send,
29 clippy::useless_let_if_seq,
30 clippy::str_to_string,
31 clippy::inherent_to_string,
32 clippy::let_and_return,
33 clippy::string_to_string,
34 clippy::try_err,
35 clippy::unused_async,
36 clippy::missing_enforced_import_renames,
37 clippy::nonstandard_macro_braces,
38 clippy::rc_mutex,
39 clippy::unwrap_or_else_default,
40 clippy::manual_split_once,
41 clippy::derivable_impls,
42 clippy::needless_option_as_deref,
43 clippy::iter_not_returning_iterator,
44 clippy::same_name_method,
45 clippy::manual_assert,
46 clippy::non_send_fields_in_send_ty,
47 clippy::equatable_if_let,
48 bad_style,
49 clashing_extern_declarations,
50 dead_code,
51 deprecated,
52 explicit_outlives_requirements,
53 improper_ctypes,
54 invalid_value,
55 missing_copy_implementations,
56 missing_debug_implementations,
57 mutable_transmutes,
58 no_mangle_generic_items,
59 non_shorthand_field_patterns,
60 overflowing_literals,
61 path_statements,
62 patterns_in_fns_without_body,
63 private_in_public,
64 trivial_bounds,
65 trivial_casts,
66 trivial_numeric_casts,
67 type_alias_bounds,
68 unconditional_recursion,
69 unreachable_pub,
70 unsafe_code,
71 unstable_features,
72 unused,
73 unused_allocation,
74 unused_comparisons,
75 unused_import_braces,
76 unused_parens,
77 unused_qualifications,
78 while_true,
79 missing_docs
80)]
81#![allow(clippy::derive_partial_eq_without_eq, clippy::box_default)]
82
83mod error;
84mod filters;
85mod liquid_json;
86#[cfg(feature = "serde")]
87mod liquid_json_value;
88mod options;
89
90use std::sync::Arc;
91
92pub use error::Error;
93use liquid::{Parser, ValueView};
94use liquid_core::{
95 model::ScalarCow,
96 runtime::{RuntimeBuilder, Variable},
97 Language, Runtime,
98};
99#[cfg(feature = "serde")]
100pub use liquid_json_value::LiquidJsonValue;
101use once_cell::sync::Lazy;
102use serde_json::Number;
103
104pub use crate::liquid_json::LiquidJson;
105
106use self::options::OptionsBuilder;
107
108static PARSER: Lazy<Arc<Parser>> = Lazy::new(|| {
109 let builder = liquid::ParserBuilder::with_stdlib()
110 .filter(filters::Each::new())
111 .filter(filters::Output)
112 .filter(filters::Base64Decode)
113 .filter(filters::Base64Encode);
114 #[cfg(feature = "serde")]
115 let builder = builder.filter(filters::Json);
116 Arc::new(builder.build().unwrap())
117});
118
119static OPTIONS: Lazy<Arc<Language>> = Lazy::new(|| {
120 let builder = OptionsBuilder::new()
121 .stdlib()
122 .filter(filters::Each::new())
123 .filter(filters::Output)
124 .filter(filters::Base64Decode)
125 .filter(filters::Base64Encode);
126 #[cfg(feature = "serde")]
127 let builder = builder.filter(filters::Json);
128 builder.build()
129});
130
131pub fn render_string(template: &str, data: &serde_json::Value) -> Result<String, Error> {
133 let template = PARSER.parse(template)?;
134 let data = to_liquid_obj(data)?;
135 Ok(template.render(&data)?)
136}
137
138fn to_liquid_obj(value: &serde_json::Value) -> Result<liquid::Object, Error> {
139 match value {
141 serde_json::Value::Object(v) => v
142 .into_iter()
143 .map(|(k, v)| {
144 Ok((
145 liquid::model::KString::from_string(k.clone()),
146 to_liquid_value(v)?,
147 ))
148 })
149 .collect::<Result<liquid::Object, Error>>(),
150 _ => Err(Error::InvalidContext(value.clone())),
151 }
152}
153
154fn to_liquid_value(value: &serde_json::Value) -> Result<liquid::model::Value, Error> {
155 Ok(match value {
156 serde_json::Value::Null => liquid::model::Value::Nil,
157 serde_json::Value::Bool(v) => liquid::model::Value::Scalar(liquid::model::Scalar::from(*v)),
158 serde_json::Value::Number(v) => {
159 if v.is_f64() {
160 liquid::model::Value::Scalar(liquid::model::Scalar::from(v.as_f64().unwrap()))
161 } else if v.is_i64() {
162 liquid::model::Value::Scalar(liquid::model::Scalar::from(v.as_i64().unwrap()))
163 } else {
164 let num = v.as_u64().unwrap();
165 if num < u32::MAX as u64 {
166 liquid::model::Value::Scalar(liquid::model::Scalar::new(ScalarCow::new(
167 v.as_u64().unwrap() as u32,
168 )))
169 } else {
170 return Err(Error::U64);
171 }
172 }
173 }
174 serde_json::Value::String(v) => {
175 liquid::model::Value::Scalar(liquid::model::Scalar::from(v.clone()))
176 }
177 serde_json::Value::Array(v) => {
178 liquid::model::Value::Array(v.iter().map(to_liquid_value).collect::<Result<_, _>>()?)
179 }
180 serde_json::Value::Object(v) => liquid::model::Value::Object(
181 v.into_iter()
182 .map(|(k, v)| {
183 Ok((
184 liquid::model::KString::from_string(k.clone()),
185 to_liquid_value(v)?,
186 ))
187 })
188 .collect::<Result<liquid::model::Object, Error>>()?,
189 ),
190 })
191}
192
193fn to_json_value(value: liquid::model::Value) -> serde_json::Value {
194 match value {
195 liquid::model::Value::Scalar(v) => {
196 let name = v.type_name();
198 match name {
199 "string" => serde_json::Value::String(v.to_kstr().to_string()),
200 "whole number" => serde_json::Value::Number(Number::from(v.to_integer().unwrap())),
201 "fractional number" => {
202 serde_json::Value::Number(Number::from_f64(v.to_float().unwrap()).unwrap())
203 }
204 "boolean" => serde_json::Value::Bool(v.to_bool().unwrap()),
205 _ => panic!("Unknown scalar type: {}", name),
206 }
207 }
208 liquid::model::Value::Array(v) => {
209 serde_json::Value::Array(v.into_iter().map(to_json_value).collect())
210 }
211 liquid::model::Value::Object(v) => serde_json::Value::Object(
212 v.into_iter()
213 .map(|(k, v)| (k.to_string(), to_json_value(v)))
214 .collect(),
215 ),
216 liquid::model::Value::State(_v) => panic!("State not supported"),
217 liquid::model::Value::Nil => serde_json::Value::Null,
218 }
219}
220
221static SINGLE_VALUE: Lazy<regex::Regex> =
222 Lazy::new(|| regex::Regex::new(r"^\{\{\s*(\w*)\s*\}\}$").unwrap());
223
224fn render_value(
225 value: &serde_json::Value,
226 data: &liquid::Object,
227) -> Result<serde_json::Value, Error> {
228 match value {
229 serde_json::Value::String(s) => {
230 if let Some(cap) = SINGLE_VALUE.captures(s) {
232 let key = cap.get(1).unwrap().as_str();
233 if let Some(val) = data.get(key) {
234 return Ok(to_json_value(val.clone()));
235 }
236 }
237 let mut output = Vec::new();
238 let runtime = RuntimeBuilder::new().set_globals(data).build();
239
240 let elements = liquid_core::parser::parse(s, &OPTIONS)?;
241 for element in elements {
242 element.render_to(&mut output, &runtime)?;
243 }
244 let sentinel = Variable::with_literal("__output__");
245 if let Some(output) = sentinel.try_evaluate(&runtime) {
246 if let Some(value) = runtime.try_get(&output) {
247 return Ok(to_json_value(value.to_value()));
248 }
249 }
250 let output = String::from_utf8(output).unwrap();
251 Ok(serde_json::Value::String(output))
252 }
253 serde_json::Value::Array(a) => Ok(serde_json::Value::Array(
254 a.iter()
255 .map(|v| render_value(v, data))
256 .collect::<Result<Vec<serde_json::Value>, _>>()?,
257 )),
258 serde_json::Value::Object(o) => {
259 let map = o
260 .into_iter()
261 .map(|(k, v)| Ok((k.clone(), render_value(v, data)?)))
262 .collect::<Result<serde_json::Map<String, serde_json::Value>, Error>>()?;
263 Ok(serde_json::Value::Object(map))
264 }
265 _ => Ok(value.clone()),
266 }
267}