• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cobalt-org / liquid-rust / 18166295834

01 Oct 2025 02:55PM UTC coverage: 56.271% (-0.2%) from 56.499%
18166295834

push

github

web-flow
Merge pull request #594 from cobalt-org/renovate/actions-checkout-5.x

chore(deps): Update actions/checkout action to v5

2737 of 4864 relevant lines covered (56.27%)

4.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

45.0
/crates/core/src/runtime/runtime.rs
1
use std::sync;
2

3
use crate::error::Error;
4
use crate::error::Result;
5
use crate::model::{Object, ObjectView, Scalar, ScalarCow, Value, ValueCow, ValueView};
6

7
use super::PartialStore;
8
use super::Renderable;
9

10
/// State for rendering a template
11
pub trait Runtime {
12
    /// Partial templates for inclusion.
13
    fn partials(&self) -> &dyn PartialStore;
14

15
    /// The name of the currently active template.
16
    fn name(&self) -> Option<crate::model::KStringRef<'_>>;
17

18
    /// All available values
19
    fn roots(&self) -> std::collections::BTreeSet<crate::model::KStringCow<'_>>;
20
    /// Recursively index into the stack.
21
    fn try_get(&self, path: &[ScalarCow<'_>]) -> Option<ValueCow<'_>>;
22
    /// Recursively index into the stack.
23
    fn get(&self, path: &[ScalarCow<'_>]) -> Result<ValueCow<'_>>;
24

25
    /// Sets a value in the global runtime.
26
    fn set_global(
27
        &self,
28
        name: crate::model::KString,
29
        val: crate::model::Value,
30
    ) -> Option<crate::model::Value>;
31

32
    /// Used by increment and decrement tags
33
    fn set_index(&self, name: crate::model::KString, val: Value) -> Option<Value>;
34
    /// Used by increment and decrement tags
35
    fn get_index<'a>(&'a self, name: &str) -> Option<ValueCow<'a>>;
36

37
    /// Unnamed state for plugins during rendering
38
    fn registers(&self) -> &Registers;
39
}
40

41
impl<R: Runtime + ?Sized> Runtime for &R {
42
    fn partials(&self) -> &dyn super::PartialStore {
2✔
43
        <R as Runtime>::partials(self)
2✔
44
    }
45

46
    fn name(&self) -> Option<crate::model::KStringRef<'_>> {
×
47
        <R as Runtime>::name(self)
×
48
    }
49

50
    fn roots(&self) -> std::collections::BTreeSet<crate::model::KStringCow<'_>> {
×
51
        <R as Runtime>::roots(self)
×
52
    }
53

54
    fn try_get(&self, path: &[ScalarCow<'_>]) -> Option<ValueCow<'_>> {
×
55
        <R as Runtime>::try_get(self, path)
×
56
    }
57

58
    fn get(&self, path: &[ScalarCow<'_>]) -> Result<ValueCow<'_>> {
2✔
59
        <R as Runtime>::get(self, path)
2✔
60
    }
61

62
    fn set_global(
2✔
63
        &self,
64
        name: crate::model::KString,
65
        val: crate::model::Value,
66
    ) -> Option<crate::model::Value> {
67
        <R as Runtime>::set_global(self, name, val)
2✔
68
    }
69

70
    fn set_index(&self, name: crate::model::KString, val: Value) -> Option<Value> {
1✔
71
        <R as Runtime>::set_index(self, name, val)
1✔
72
    }
73

74
    fn get_index<'a>(&'a self, name: &str) -> Option<ValueCow<'a>> {
1✔
75
        <R as Runtime>::get_index(self, name)
1✔
76
    }
77

78
    fn registers(&self) -> &super::Registers {
5✔
79
        <R as Runtime>::registers(self)
3✔
80
    }
81
}
82

83
/// Create processing runtime for a template.
84
pub struct RuntimeBuilder<'g, 'p> {
85
    globals: Option<&'g dyn ObjectView>,
86
    partials: Option<&'p dyn PartialStore>,
87
}
88

89
impl<'c, 'g: 'c, 'p: 'c> RuntimeBuilder<'g, 'p> {
90
    /// Creates a new, empty rendering runtime.
91
    pub fn new() -> Self {
10✔
92
        Self {
93
            globals: None,
94
            partials: None,
95
        }
96
    }
97

98
    /// Initialize the stack with the given globals.
99
    pub fn set_globals<'n>(self, values: &'n dyn ObjectView) -> RuntimeBuilder<'n, 'p> {
8✔
100
        RuntimeBuilder {
101
            globals: Some(values),
×
102
            partials: self.partials,
10✔
103
        }
104
    }
105

106
    /// Initialize partial-templates available for including.
107
    pub fn set_partials<'n>(self, values: &'n dyn PartialStore) -> RuntimeBuilder<'g, 'n> {
2✔
108
        RuntimeBuilder {
109
            globals: self.globals,
2✔
110
            partials: Some(values),
×
111
        }
112
    }
113

114
    /// Create the `Runtime`.
115
    pub fn build(self) -> impl Runtime + 'c {
10✔
116
        let partials = self.partials.unwrap_or(&NullPartials);
8✔
117
        let runtime = RuntimeCore {
118
            partials,
119
            ..Default::default()
120
        };
121
        let runtime = super::IndexFrame::new(runtime);
11✔
122
        let runtime = super::StackFrame::new(runtime, self.globals.unwrap_or(&NullObject));
8✔
123
        super::GlobalFrame::new(runtime)
8✔
124
    }
125
}
126

127
#[derive(Copy, Clone, Debug)]
128
struct NullObject;
129

130
impl ValueView for NullObject {
131
    fn as_debug(&self) -> &dyn std::fmt::Debug {
×
132
        self
133
    }
134

135
    fn render(&self) -> crate::model::DisplayCow<'_> {
×
136
        Value::Nil.render()
×
137
    }
138
    fn source(&self) -> crate::model::DisplayCow<'_> {
×
139
        Value::Nil.source()
×
140
    }
141
    fn type_name(&self) -> &'static str {
×
142
        "object"
143
    }
144
    fn query_state(&self, state: crate::model::State) -> bool {
×
145
        match state {
×
146
            crate::model::State::Truthy => true,
×
147
            crate::model::State::DefaultValue
×
148
            | crate::model::State::Empty
149
            | crate::model::State::Blank => false,
150
        }
151
    }
152

153
    fn to_kstr(&self) -> crate::model::KStringCow<'_> {
×
154
        crate::model::KStringCow::from_static("")
×
155
    }
156
    fn to_value(&self) -> Value {
×
157
        Value::Object(Object::new())
×
158
    }
159

160
    fn as_object(&self) -> Option<&dyn ObjectView> {
×
161
        Some(self)
162
    }
163
}
164

165
impl ObjectView for NullObject {
166
    fn as_value(&self) -> &dyn ValueView {
×
167
        self
168
    }
169

170
    fn size(&self) -> i64 {
×
171
        0
172
    }
173

174
    fn keys<'k>(&'k self) -> Box<dyn Iterator<Item = crate::model::KStringCow<'k>> + 'k> {
×
175
        let keys = Vec::new().into_iter();
×
176
        Box::new(keys)
×
177
    }
178

179
    fn values<'k>(&'k self) -> Box<dyn Iterator<Item = &'k dyn ValueView> + 'k> {
×
180
        let i = Vec::new().into_iter();
×
181
        Box::new(i)
×
182
    }
183

184
    fn iter<'k>(
×
185
        &'k self,
186
    ) -> Box<dyn Iterator<Item = (crate::model::KStringCow<'k>, &'k dyn ValueView)> + 'k> {
187
        let i = Vec::new().into_iter();
×
188
        Box::new(i)
×
189
    }
190

191
    fn contains_key(&self, _index: &str) -> bool {
×
192
        false
193
    }
194

195
    fn get<'s>(&'s self, _index: &str) -> Option<&'s dyn ValueView> {
×
196
        None
×
197
    }
198
}
199

200
impl Default for RuntimeBuilder<'static, 'static> {
201
    fn default() -> Self {
×
202
        Self::new()
×
203
    }
204
}
205

206
/// Processing runtime for a template.
207
pub struct RuntimeCore<'g> {
208
    partials: &'g dyn PartialStore,
209

210
    registers: Registers,
211
}
212

213
impl RuntimeCore<'_> {
214
    /// Create a default `RuntimeCore`.
215
    ///
216
    /// See `RuntimeBuilder` for more control.
217
    pub fn new() -> Self {
×
218
        RuntimeCore::default()
×
219
    }
220

221
    /// Partial templates for inclusion.
222
    pub fn partials(&self) -> &dyn PartialStore {
×
223
        self.partials
×
224
    }
225
}
226

227
impl Runtime for RuntimeCore<'_> {
228
    fn partials(&self) -> &dyn PartialStore {
2✔
229
        self.partials
2✔
230
    }
231

232
    fn name(&self) -> Option<crate::model::KStringRef<'_>> {
×
233
        None
×
234
    }
235

236
    fn roots(&self) -> std::collections::BTreeSet<crate::model::KStringCow<'_>> {
×
237
        // Indexes don't count
238
        std::collections::BTreeSet::new()
×
239
    }
240

241
    fn try_get(&self, _path: &[ScalarCow<'_>]) -> Option<ValueCow<'_>> {
5✔
242
        None
3✔
243
    }
244

245
    fn get(&self, path: &[ScalarCow<'_>]) -> Result<ValueCow<'_>> {
2✔
246
        let key = path.first().cloned().unwrap_or_else(|| Scalar::new("nil"));
2✔
247
        Error::with_msg("Unknown variable")
6✔
248
            .context("requested variable", key.to_kstr())
4✔
249
            .into_err()
250
    }
251

252
    fn set_global(
253
        &self,
254
        _name: crate::model::KString,
255
        _val: crate::model::Value,
256
    ) -> Option<crate::model::Value> {
257
        unreachable!("Must be masked by a global frame");
258
    }
259

260
    fn set_index(&self, _name: crate::model::KString, _val: Value) -> Option<Value> {
261
        unreachable!("Must be masked by a global frame");
262
    }
263

264
    fn get_index<'a>(&'a self, _name: &str) -> Option<ValueCow<'a>> {
×
265
        None
×
266
    }
267

268
    fn registers(&self) -> &Registers {
10✔
269
        &self.registers
8✔
270
    }
271
}
272

273
impl Default for RuntimeCore<'_> {
274
    fn default() -> Self {
10✔
275
        Self {
276
            partials: &NullPartials,
277
            registers: Default::default(),
8✔
278
        }
279
    }
280
}
281

282
/// Unnamed state for plugins during rendering
283
pub struct Registers {
284
    registers: std::cell::RefCell<anymap2::AnyMap>,
285
}
286

287
impl Registers {
288
    /// Data store for stateful tags/blocks.
289
    ///
290
    /// If a plugin needs state, it creates a `struct Register : Default` and accesses it via
291
    /// `get_mut`.
292
    pub fn get_mut<T: std::any::Any + Default>(&self) -> std::cell::RefMut<'_, T> {
12✔
293
        std::cell::RefMut::map(self.registers.borrow_mut(), |registers| {
22✔
294
            registers.entry::<T>().or_default()
10✔
295
        })
296
    }
297
}
298

299
impl Default for Registers {
300
    fn default() -> Self {
11✔
301
        Self {
302
            registers: std::cell::RefCell::new(anymap2::AnyMap::new()),
8✔
303
        }
304
    }
305
}
306

307
/// The current interrupt state. The interrupt state is used by
308
/// the `break` and `continue` tags to halt template rendering
309
/// at a given point and unwind the `render` call stack until
310
/// it reaches an enclosing `for_loop`. At that point the interrupt
311
/// is cleared, and the `for_loop` carries on processing as directed.
312
#[derive(Debug, Clone, PartialEq, Eq, Default)]
313
pub struct InterruptRegister {
314
    interrupt: Option<Interrupt>,
315
}
316

317
impl InterruptRegister {
318
    /// An interrupt state is active.
319
    pub fn interrupted(&self) -> bool {
8✔
320
        self.interrupt.is_some()
10✔
321
    }
322

323
    /// Sets the interrupt state. Any previous state is obliterated.
324
    pub fn set(&mut self, interrupt: Interrupt) {
1✔
325
        self.interrupt.replace(interrupt);
1✔
326
    }
327

328
    /// Fetches and clears the interrupt state.
329
    pub fn reset(&mut self) -> Option<Interrupt> {
3✔
330
        self.interrupt.take()
2✔
331
    }
332
}
333

334
/// Block processing interrupt state.
335
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
336
pub enum Interrupt {
337
    /// Restart processing the current block.
338
    Continue,
339
    /// Stop processing the current block.
340
    Break,
341
}
342

343
#[derive(Copy, Clone, Debug)]
344
struct NullPartials;
345

346
impl PartialStore for NullPartials {
347
    fn contains(&self, _name: &str) -> bool {
×
348
        false
349
    }
350

351
    fn names(&self) -> Vec<&str> {
×
352
        Vec::new()
×
353
    }
354

355
    fn try_get(&self, _name: &str) -> Option<sync::Arc<dyn Renderable>> {
×
356
        None
357
    }
358

359
    fn get(&self, name: &str) -> Result<sync::Arc<dyn Renderable>> {
×
360
        Err(Error::with_msg("Partial does not exist").context("name", name.to_owned()))
×
361
    }
362
}
363

364
#[cfg(test)]
365
mod test {
366
    use super::*;
367

368
    use crate::model::Scalar;
369
    use crate::model::Value;
370
    use crate::model::ValueViewCmp;
371

372
    #[test]
373
    fn mask_variables() {
374
        let test_path = [Scalar::new("test")];
375

376
        let rt = RuntimeBuilder::new().build();
377
        rt.set_global("test".into(), Value::scalar(42f64));
378
        assert_eq!(&rt.get(&test_path).unwrap(), &ValueViewCmp::new(&42f64));
379

380
        {
381
            let data = crate::object!({"test": 3});
382
            let new_scope = super::super::StackFrame::new(&rt, &data);
383

384
            // assert that values are chained to the parent scope
385
            assert_eq!(&new_scope.get(&test_path).unwrap(), &ValueViewCmp::new(&3));
386
        }
387

388
        // assert that the value has reverted to the old one
389
        assert_eq!(&rt.get(&test_path).unwrap(), &ValueViewCmp::new(&42));
390
    }
391

392
    #[test]
393
    fn global_variables() {
394
        let global_path = [Scalar::new("global")];
395

396
        let rt = RuntimeBuilder::new().build();
397

398
        {
399
            let data = crate::object!({"test": 3});
400
            let new_scope = super::super::StackFrame::new(&rt, &data);
401

402
            // sat a new val that we will pick up outside the scope
403
            new_scope.set_global("global".into(), Value::scalar("some value"));
404
        }
405
        assert_eq!(
406
            &rt.get(&global_path).unwrap(),
407
            &ValueViewCmp::new(&"some value")
408
        );
409
    }
410
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc