jsonnet/
lib.rs

1//! # jsonnet bindings for Rust
2//!
3//! This library contains bindings to the [jsonnet][1] C library,
4//! a template language that generates JSON and YAML.
5//!
6//! Almost all interaction with this module will be via the
7//! `JsonnetVm` type.
8//!
9//! [1]: https://jsonnet.org/
10//!
11//! # Examples
12//!
13//! ```
14//! use jsonnet::JsonnetVm;
15//!
16//! let mut vm = JsonnetVm::new();
17//! let output = vm.evaluate_snippet("example", "'Hello ' + 'World'").unwrap();
18//! assert_eq!(output.to_string(), "\"Hello World\"\n");
19//! ```
20
21#![deny(missing_docs)]
22#![allow(trivial_casts)]
23
24use libc::{c_char, c_int, c_uint, c_void};
25use std::ffi::{CStr, CString, OsStr};
26use std::ops::Deref;
27use std::path::{Path, PathBuf};
28use std::{error, fmt, iter, ptr};
29
30mod string;
31mod value;
32
33pub use crate::string::JsonnetString;
34use crate::string::JsonnetStringIter;
35pub use crate::value::{JsonVal, JsonValue};
36use jsonnet_sys::JsonnetJsonValue;
37
38/// Error returned from jsonnet routines on failure.
39#[derive(Debug, PartialEq, Eq)]
40pub struct Error<'a>(JsonnetString<'a>);
41
42impl<'a> From<JsonnetString<'a>> for Error<'a> {
43    fn from(s: JsonnetString<'a>) -> Self {
44        Error(s)
45    }
46}
47
48impl<'a> From<Error<'a>> for JsonnetString<'a> {
49    fn from(e: Error<'a>) -> Self {
50        e.0
51    }
52}
53
54impl<'a> Deref for Error<'a> {
55    type Target = JsonnetString<'a>;
56    fn deref(&self) -> &Self::Target {
57        &self.0
58    }
59}
60
61impl<'a> fmt::Display for Error<'a> {
62    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63        fmt::Display::fmt(&self.0, f)
64    }
65}
66
67impl<'a> error::Error for Error<'a> {
68    fn description(&self) -> &str {
69        "jsonnet error"
70    }
71}
72
73/// Result from "multi" eval methods.
74///
75/// Contains a list of (&str, &str) pairs.
76pub struct EvalMap<'a>(JsonnetString<'a>);
77impl<'a> EvalMap<'a> {
78    /// Returns an iterator over the contained values.
79    pub fn iter<'b>(&'b self) -> EvalMapIter<'b, 'a> {
80        EvalMapIter(unsafe { JsonnetStringIter::new(&self.0) })
81    }
82}
83
84impl<'a, 'b> IntoIterator for &'b EvalMap<'a> {
85    type Item = (&'b str, &'b str);
86    type IntoIter = EvalMapIter<'b, 'a>;
87    fn into_iter(self) -> Self::IntoIter {
88        self.iter()
89    }
90}
91
92/// Iterator returned from "multi" eval methods.
93///
94/// Yields (&str, &str) pairs.
95#[derive(Debug)]
96pub struct EvalMapIter<'a, 'b: 'a>(JsonnetStringIter<'a, 'b>);
97
98impl<'a, 'b> Iterator for EvalMapIter<'a, 'b> {
99    type Item = (&'a str, &'a str);
100    fn next(&mut self) -> Option<Self::Item> {
101        self.0.next().map(|first| {
102            let second = self.0.next().unwrap();
103            (first, second)
104        })
105    }
106}
107
108/// Result from "stream" eval methods.
109///
110/// Contains a list of &str.
111pub struct EvalList<'a>(JsonnetString<'a>);
112impl<'a> EvalList<'a> {
113    /// Returns an iterator over the contained values.
114    pub fn iter<'b>(&'b self) -> EvalListIter<'b, 'a> {
115        EvalListIter(unsafe { JsonnetStringIter::new(&self.0) })
116    }
117}
118
119/// Iterator returned from "stream" eval methods.
120///
121/// Yields &str items.
122pub struct EvalListIter<'a, 'b: 'a>(JsonnetStringIter<'a, 'b>);
123
124impl<'a, 'b> Iterator for EvalListIter<'a, 'b> {
125    type Item = &'a str;
126    fn next(&mut self) -> Option<Self::Item> {
127        self.0.next()
128    }
129}
130
131impl<'a, 'b> IntoIterator for &'b EvalList<'a> {
132    type Item = &'b str;
133    type IntoIter = EvalListIter<'b, 'a>;
134    fn into_iter(self) -> Self::IntoIter {
135        self.iter()
136    }
137}
138
139/// String literal style
140#[derive(Debug, PartialEq, Eq)]
141pub enum FmtString {
142    /// Double quoted `"foo"`.
143    Double,
144    /// Single quoted `'foo'`.
145    Single,
146    /// Leave quoting as-is.
147    Leave,
148}
149
150impl FmtString {
151    fn to_char(self) -> c_int {
152        let c = match self {
153            FmtString::Double => 'd',
154            FmtString::Single => 's',
155            FmtString::Leave => 'l',
156        };
157        c as c_int
158    }
159}
160
161impl Default for FmtString {
162    fn default() -> FmtString {
163        FmtString::Leave
164    }
165}
166
167/// Comment style
168#[derive(Debug, PartialEq, Eq)]
169pub enum FmtComment {
170    /// Hash comments (like shell).
171    Hash,
172    /// Double slash comments (like C++).
173    Slash,
174    /// Leave comments as-is.
175    Leave,
176}
177
178impl FmtComment {
179    fn to_char(self) -> c_int {
180        let c = match self {
181            FmtComment::Hash => 'h',
182            FmtComment::Slash => 's',
183            FmtComment::Leave => 'l',
184        };
185        c as c_int
186    }
187}
188
189impl Default for FmtComment {
190    fn default() -> FmtComment {
191        FmtComment::Leave
192    }
193}
194
195/// Return the version string of the Jsonnet interpreter.  Conforms to
196/// [semantic versioning][1].
197///
198/// [1]: http://semver.org/
199pub fn jsonnet_version() -> &'static str {
200    let s = unsafe { CStr::from_ptr(jsonnet_sys::jsonnet_version()) };
201    s.to_str().unwrap()
202}
203
204#[test]
205fn test_version() {
206    let s = jsonnet_version();
207    println!("Got version: {}", s);
208    // Should be 1.2.3 semver format
209    assert_eq!(s.split('.').count(), 3);
210}
211
212/// Callback used to load imports.
213struct ImportContext<'a, F> {
214    vm: &'a JsonnetVm,
215    cb: F,
216}
217
218#[cfg(unix)]
219fn bytes2osstr(b: &[u8]) -> &OsStr {
220    use std::os::unix::ffi::OsStrExt;
221    OsStr::from_bytes(b)
222}
223
224/// Panics if os contains invalid utf8!
225#[cfg(windows)]
226fn bytes2osstr(b: &[u8]) -> &OsStr {
227    let s = std::str::from_utf8(b).unwrap();
228    OsStr::new(s)
229}
230
231fn cstr2osstr(cstr: &CStr) -> &OsStr {
232    bytes2osstr(cstr.to_bytes())
233}
234
235#[cfg(unix)]
236fn osstr2bytes(os: &OsStr) -> &[u8] {
237    use std::os::unix::ffi::OsStrExt;
238    os.as_bytes()
239}
240
241/// Panics if os contains invalid utf8!
242#[cfg(windows)]
243fn osstr2bytes(os: &OsStr) -> &[u8] {
244    os.to_str().unwrap().as_bytes()
245}
246
247fn osstr2cstring<T: AsRef<OsStr>>(os: T) -> CString {
248    let b = osstr2bytes(os.as_ref());
249    CString::new(b).unwrap()
250}
251
252// `jsonnet_sys::JsonnetImportCallback`-compatible function that
253// interprets `ctx` as an `ImportContext` and converts arguments
254// appropriately.
255extern "C" fn import_callback<F>(
256    ctx: *mut c_void,
257    base: &c_char,
258    rel: &c_char,
259    found_here: &mut *mut c_char,
260    success: &mut c_int,
261) -> *mut c_char
262where
263    F: Fn(&JsonnetVm, &Path, &Path) -> Result<(PathBuf, String), String>,
264{
265    let ctx = unsafe { &*(ctx as *mut ImportContext<F>) };
266    let vm = ctx.vm;
267    let callback = &ctx.cb;
268    let base_path = Path::new(cstr2osstr(unsafe { CStr::from_ptr(base) }));
269    let rel_path = Path::new(cstr2osstr(unsafe { CStr::from_ptr(rel) }));
270    match callback(vm, base_path, rel_path) {
271        Ok((found_here_path, contents)) => {
272            *success = 1;
273
274            let v = {
275                // Note: PathBuf may not be valid utf8.
276                let b = osstr2bytes(found_here_path.as_os_str());
277                JsonnetString::from_bytes(vm, b)
278            };
279            *found_here = v.into_raw();
280
281            JsonnetString::new(vm, &contents).into_raw()
282        }
283        Err(err) => {
284            *success = 0;
285            JsonnetString::new(vm, &err).into_raw()
286        }
287    }
288}
289
290/// Callback to provide native extensions to Jsonnet.
291///
292/// This callback should not have side-effects!  Jsonnet is a lazy
293/// functional language and will call your function when you least
294/// expect it, more times than you expect, or not at all.
295struct NativeContext<'a, F> {
296    vm: &'a JsonnetVm,
297    argc: usize,
298    cb: F,
299}
300
301// `jsonnet_sys::JsonnetNativeCallback`-compatible function that
302// interprets `ctx` as a `NativeContext` and converts arguments
303// appropriately.
304extern "C" fn native_callback<'a, F>(
305    ctx: *mut libc::c_void,
306    argv: *const *const JsonnetJsonValue,
307    success: &mut c_int,
308) -> *mut JsonnetJsonValue
309where
310    F: Fn(&'a JsonnetVm, &[JsonVal<'a>]) -> Result<JsonValue<'a>, String>,
311{
312    let ctx = unsafe { &*(ctx as *mut NativeContext<F>) };
313    let vm = ctx.vm;
314    let callback = &ctx.cb;
315    let args: Vec<_> = (0..ctx.argc)
316        .map(|i| unsafe { JsonVal::from_ptr(vm, *argv.offset(i as isize)) })
317        .collect();
318    match callback(vm, &args) {
319        Ok(v) => {
320            *success = 1;
321            v.into_raw()
322        }
323        Err(err) => {
324            *success = 0;
325            let ret = JsonValue::from_str(vm, &err);
326            ret.into_raw()
327        }
328    }
329}
330
331/// Jsonnet virtual machine context.
332pub struct JsonnetVm(*mut jsonnet_sys::JsonnetVm);
333
334impl JsonnetVm {
335    /// Create a new Jsonnet virtual machine.
336    pub fn new() -> Self {
337        let vm = unsafe { jsonnet_sys::jsonnet_make() };
338        JsonnetVm(vm)
339    }
340
341    fn as_ptr(&self) -> *mut jsonnet_sys::JsonnetVm {
342        self.0
343    }
344
345    /// Set the maximum stack depth.
346    pub fn max_stack(&mut self, v: u32) {
347        unsafe { jsonnet_sys::jsonnet_max_stack(self.0, v as c_uint) }
348    }
349
350    /// Set the number of objects required before a garbage collection
351    /// cycle is allowed.
352    pub fn gc_min_objects(&mut self, v: u32) {
353        unsafe { jsonnet_sys::jsonnet_gc_min_objects(self.0, v as c_uint) }
354    }
355
356    /// Run the garbage collector after this amount of growth in the
357    /// number of objects.
358    pub fn gc_growth_trigger(&mut self, v: f64) {
359        unsafe { jsonnet_sys::jsonnet_gc_growth_trigger(self.0, v) }
360    }
361
362    /// Expect a string as output and don't JSON encode it.
363    pub fn string_output(&mut self, v: bool) {
364        unsafe { jsonnet_sys::jsonnet_string_output(self.0, v as c_int) }
365    }
366
367    /// Override the callback used to locate imports.
368    ///
369    /// # Examples
370    ///
371    /// ```
372    /// use std::path::{Path,PathBuf};
373    /// use std::ffi::OsStr;
374    /// use jsonnet::JsonnetVm;
375    ///
376    /// let mut vm = JsonnetVm::new();
377    /// vm.import_callback(|_vm, base, rel| {
378    ///    if rel.file_stem() == Some(OsStr::new("bar")) {
379    ///       let newbase = base.into();
380    ///       let contents = "2 + 3".to_owned();
381    ///       Ok((newbase, contents))
382    ///    } else {
383    ///       Err("not found".to_owned())
384    ///    }
385    /// });
386    ///
387    /// {
388    ///    let output = vm.evaluate_snippet("myimport", "import 'x/bar.jsonnet'").unwrap();
389    ///    assert_eq!(output.to_string(), "5\n");
390    /// }
391    ///
392    /// {
393    ///    let result = vm.evaluate_snippet("myimport", "import 'x/foo.jsonnet'");
394    ///    assert!(result.is_err());
395    /// }
396    /// ```
397    pub fn import_callback<F>(&mut self, cb: F)
398    where
399        F: Fn(&JsonnetVm, &Path, &Path) -> Result<(PathBuf, String), String>,
400    {
401        let ctx = ImportContext { vm: self, cb: cb };
402        unsafe {
403            jsonnet_sys::jsonnet_import_callback(
404                self.as_ptr(),
405                import_callback::<F> as *const _,
406                // TODO: ctx is leaked :(
407                Box::into_raw(Box::new(ctx)) as *mut _,
408            );
409        }
410    }
411
412    /// Register a native extension.
413    ///
414    /// This will appear in Jsonnet as a function type and can be
415    /// accessed from `std.native("foo")`.
416    ///
417    /// # Examples
418    ///
419    /// ```
420    /// use jsonnet::{JsonnetVm, JsonVal, JsonValue};
421    ///
422    /// fn myadd<'a>(vm: &'a JsonnetVm, args: &[JsonVal<'a>]) -> Result<JsonValue<'a>, String> {
423    ///    let a = args[0].as_num().ok_or("Expected a number")?;
424    ///    let b = args[1].as_num().ok_or("Expected a number")?;
425    ///    Ok(JsonValue::from_num(vm, a + b))
426    /// }
427    ///
428    /// let mut vm = JsonnetVm::new();
429    /// vm.native_callback("myadd", myadd, &["a", "b"]);
430    ///
431    /// {
432    ///    let result = vm.evaluate_snippet("nativetest",
433    ///       "std.native('myadd')(2, 3)");
434    ///    assert_eq!(result.unwrap().as_str(), "5\n");
435    /// }
436    ///
437    /// {
438    ///    let result = vm.evaluate_snippet("nativefail",
439    ///       "std.native('myadd')('foo', 'bar')");
440    ///    assert!(result.is_err());
441    /// }
442    ///
443    /// vm.native_callback("upcase", |vm, args| {
444    ///       let s = args[0].as_str().ok_or("Expected a string")?;
445    ///       Ok(JsonValue::from_str(vm, &s.to_uppercase()))
446    ///    }, &["s"]);
447    /// {
448    ///    let result = vm.evaluate_snippet("nativeclosure",
449    ///       "std.native('upcase')('foO')");
450    ///    assert_eq!(result.unwrap().as_str(), "\"FOO\"\n");
451    /// }
452    /// ```
453    ///
454    /// # Panics
455    ///
456    /// Panics if `name` or `params` contain any embedded nul characters.
457    pub fn native_callback<F>(&mut self, name: &str, cb: F, params: &[&str])
458    where
459        for<'a> F: Fn(&'a JsonnetVm, &[JsonVal<'a>]) -> Result<JsonValue<'a>, String>,
460    {
461        let ctx = NativeContext {
462            vm: self,
463            argc: params.len(),
464            cb: cb,
465        };
466        let cname = CString::new(name).unwrap();
467        let cparams: Vec<_> = params
468            .into_iter()
469            .map(|&p| CString::new(p).unwrap())
470            .collect();
471        let cptrs: Vec<_> = cparams
472            .iter()
473            .map(|p| p.as_ptr())
474            .chain(iter::once(ptr::null()))
475            .collect();
476        unsafe {
477            jsonnet_sys::jsonnet_native_callback(
478                self.as_ptr(),
479                cname.as_ptr(),
480                native_callback::<F> as *const _,
481                // TODO: ctx is leaked :(
482                Box::into_raw(Box::new(ctx)) as *mut _,
483                cptrs.as_slice().as_ptr(),
484            );
485        }
486    }
487
488    /// Bind a Jsonnet external var to the given string.
489    ///
490    /// # Panics
491    ///
492    /// Panics if `key` or `val` contain embedded nul characters.
493    pub fn ext_var(&mut self, key: &str, val: &str) {
494        let ckey = CString::new(key).unwrap();
495        let cval = CString::new(val).unwrap();
496        unsafe {
497            jsonnet_sys::jsonnet_ext_var(self.0, ckey.as_ptr(), cval.as_ptr());
498        }
499    }
500
501    /// Bind a Jsonnet external var to the given code.
502    ///
503    /// # Panics
504    ///
505    /// Panics if `key` or `code` contain embedded nul characters.
506    pub fn ext_code(&mut self, key: &str, code: &str) {
507        let ckey = CString::new(key).unwrap();
508        let ccode = CString::new(code).unwrap();
509        unsafe {
510            jsonnet_sys::jsonnet_ext_code(self.0, ckey.as_ptr(), ccode.as_ptr());
511        }
512    }
513
514    /// Bind a string top-level argument for a top-level parameter.
515    ///
516    /// # Panics
517    ///
518    /// Panics if `key` or `val` contain embedded nul characters.
519    pub fn tla_var(&mut self, key: &str, val: &str) {
520        let ckey = CString::new(key).unwrap();
521        let cval = CString::new(val).unwrap();
522        unsafe {
523            jsonnet_sys::jsonnet_tla_var(self.0, ckey.as_ptr(), cval.as_ptr());
524        }
525    }
526
527    /// Bind a code top-level argument for a top-level parameter.
528    ///
529    /// # Panics
530    ///
531    /// Panics if `key` or `code` contain embedded nul characters.
532    pub fn tla_code(&mut self, key: &str, code: &str) {
533        let ckey = CString::new(key).unwrap();
534        let ccode = CString::new(code).unwrap();
535        unsafe {
536            jsonnet_sys::jsonnet_tla_code(self.0, ckey.as_ptr(), ccode.as_ptr());
537        }
538    }
539
540    /// Indentation level when reformatting (number of spaces).
541    pub fn fmt_indent(&mut self, n: u32) {
542        unsafe {
543            jsonnet_sys::jsonnet_fmt_indent(self.0, n as c_int);
544        }
545    }
546
547    /// Maximum number of blank lines when reformatting.
548    pub fn fmt_max_blank_lines(&mut self, n: u32) {
549        unsafe {
550            jsonnet_sys::jsonnet_fmt_max_blank_lines(self.0, n as c_int);
551        }
552    }
553
554    /// Preferred style for string literals (`""` or `''`).
555    pub fn fmt_string(&mut self, fmt: FmtString) {
556        unsafe {
557            jsonnet_sys::jsonnet_fmt_string(self.0, fmt.to_char());
558        }
559    }
560
561    /// Preferred style for line comments (# or //).
562    pub fn fmt_comment(&mut self, fmt: FmtComment) {
563        unsafe {
564            jsonnet_sys::jsonnet_fmt_comment(self.0, fmt.to_char());
565        }
566    }
567
568    /// Whether to add an extra space on the inside of arrays.
569    pub fn fmt_pad_arrays(&mut self, pad: bool) {
570        unsafe {
571            jsonnet_sys::jsonnet_fmt_pad_arrays(self.0, pad as c_int);
572        }
573    }
574
575    /// Whether to add an extra space on the inside of objects.
576    pub fn fmt_pad_objects(&mut self, pad: bool) {
577        unsafe {
578            jsonnet_sys::jsonnet_fmt_pad_objects(self.0, pad as c_int);
579        }
580    }
581
582    /// Use syntax sugar where possible with field names.
583    pub fn fmt_pretty_field_names(&mut self, sugar: bool) {
584        unsafe {
585            jsonnet_sys::jsonnet_fmt_pretty_field_names(self.0, sugar as c_int);
586        }
587    }
588
589    /// Sort top-level imports in alphabetical order
590    pub fn fmt_sort_import(&mut self, sort: bool) {
591        unsafe {
592            jsonnet_sys::jsonnet_fmt_sort_imports(self.0, sort as c_int);
593        }
594    }
595
596    /// Reformat the Jsonnet input after desugaring.
597    pub fn fmt_debug_desugaring(&mut self, reformat: bool) {
598        unsafe {
599            jsonnet_sys::jsonnet_fmt_debug_desugaring(self.0, reformat as c_int);
600        }
601    }
602
603    /// Reformat a file containing Jsonnet code, return a Jsonnet string.
604    ///
605    /// # Panics
606    ///
607    /// Panics if `filename` contains embedded nul characters.
608    pub fn fmt_file<'a, P>(&'a mut self, filename: P) -> Result<JsonnetString<'a>, Error<'a>>
609    where
610        P: AsRef<OsStr>,
611    {
612        let fname = osstr2cstring(filename);
613        let mut error = 1;
614        let output = unsafe {
615            let v = jsonnet_sys::jsonnet_fmt_file(self.0, fname.as_ptr(), &mut error);
616            JsonnetString::from_ptr(self, v)
617        };
618        match error {
619            0 => Ok(output),
620            _ => Err(Error(output)),
621        }
622    }
623
624    /// Reformat a string containing Jsonnet code, return a Jsonnet string.
625    ///
626    /// # Panics
627    ///
628    /// Panics if `filename` or `snippet` contain embedded nul characters.
629    pub fn fmt_snippet<'a, P>(
630        &'a mut self,
631        filename: P,
632        snippet: &str,
633    ) -> Result<JsonnetString<'a>, Error<'a>>
634    where
635        P: AsRef<OsStr>,
636    {
637        let fname = osstr2cstring(filename);
638        let snippet = CString::new(snippet).unwrap();
639        let mut error = 1;
640        let output = unsafe {
641            let v = jsonnet_sys::jsonnet_fmt_snippet(
642                self.0,
643                fname.as_ptr(),
644                snippet.as_ptr(),
645                &mut error,
646            );
647            JsonnetString::from_ptr(self, v)
648        };
649        match error {
650            0 => Ok(output),
651            _ => Err(Error(output)),
652        }
653    }
654
655    /// Set the number of lines of stack trace to display (None for unlimited).
656    pub fn max_trace(&mut self, limit: Option<u32>) {
657        let v = limit.unwrap_or(0);
658        unsafe {
659            jsonnet_sys::jsonnet_max_trace(self.0, v);
660        }
661    }
662
663    /// Add to the default import callback's library search path.
664    ///
665    /// Search order is last to first, so more recently appended paths
666    /// take precedence.
667    ///
668    /// # Panics
669    ///
670    /// Panics if `path` contains embedded nul characters.
671    pub fn jpath_add<P>(&mut self, path: P)
672    where
673        P: AsRef<OsStr>,
674    {
675        let v = osstr2cstring(path);
676        unsafe {
677            jsonnet_sys::jsonnet_jpath_add(self.0, v.as_ptr());
678        }
679    }
680
681    /// Evaluate a file containing Jsonnet code, returning a JSON string.
682    ///
683    /// # Errors
684    ///
685    /// Returns any jsonnet error during evaluation.
686    ///
687    /// # Panics
688    ///
689    /// Panics if `filename` contains embedded nul characters.
690    pub fn evaluate_file<'a, P>(&'a mut self, filename: P) -> Result<JsonnetString<'a>, Error<'a>>
691    where
692        P: AsRef<OsStr>,
693    {
694        let fname = osstr2cstring(filename);
695        let mut error = 1;
696        let output = unsafe {
697            let v = jsonnet_sys::jsonnet_evaluate_file(self.0, fname.as_ptr(), &mut error);
698            JsonnetString::from_ptr(self, v)
699        };
700        match error {
701            0 => Ok(output),
702            _ => Err(Error(output)),
703        }
704    }
705
706    /// Evaluate a string containing Jsonnet code, returning a JSON string.
707    ///
708    /// The `filename` argument is only used in error messages.
709    ///
710    /// # Errors
711    ///
712    /// Returns any jsonnet error during evaluation.
713    ///
714    /// # Panics
715    ///
716    /// Panics if `filename` or `snippet` contain embedded nul characters.
717    pub fn evaluate_snippet<'a, P>(
718        &'a mut self,
719        filename: P,
720        snippet: &str,
721    ) -> Result<JsonnetString<'a>, Error<'a>>
722    where
723        P: AsRef<OsStr>,
724    {
725        let fname = osstr2cstring(filename);
726        let snip = CString::new(snippet).unwrap();
727        let mut error = 1;
728        let output = unsafe {
729            let v = jsonnet_sys::jsonnet_evaluate_snippet(
730                self.0,
731                fname.as_ptr(),
732                snip.as_ptr(),
733                &mut error,
734            );
735            JsonnetString::from_ptr(self, v)
736        };
737        match error {
738            0 => Ok(output),
739            _ => Err(Error(output)),
740        }
741    }
742
743    /// Evaluate a file containing Jsonnet code, returning a number of
744    /// named JSON files.
745    ///
746    /// # Errors
747    ///
748    /// Returns any jsonnet error during evaluation.
749    ///
750    /// # Panics
751    ///
752    /// Panics if `filename` contains embedded nul characters.
753    pub fn evaluate_file_multi<'a, P>(&'a mut self, filename: P) -> Result<EvalMap<'a>, Error<'a>>
754    where
755        P: AsRef<OsStr>,
756    {
757        let fname = osstr2cstring(filename);
758        let mut error = 1;
759        let output = unsafe {
760            let v = jsonnet_sys::jsonnet_evaluate_file_multi(self.0, fname.as_ptr(), &mut error);
761            JsonnetString::from_ptr(self, v)
762        };
763        match error {
764            0 => Ok(EvalMap(output)),
765            _ => Err(Error(output)),
766        }
767    }
768
769    /// Evaluate a file containing Jsonnet code, returning a number of
770    /// named JSON files.
771    ///
772    /// # Errors
773    ///
774    /// Returns any jsonnet error during evaluation.
775    ///
776    /// # Panics
777    ///
778    /// Panics if `filename` or `snippet` contain embedded nul characters.
779    ///
780    /// # Examples
781    ///
782    /// ```
783    /// use std::collections::HashMap;
784    /// use jsonnet::JsonnetVm;
785    ///
786    /// let mut vm = JsonnetVm::new();
787    /// let output = vm.evaluate_snippet_multi("multi",
788    ///    "{'foo.json': 'foo', 'bar.json': 'bar'}").unwrap();
789    ///
790    /// let map: HashMap<_,_> = output.iter().collect();
791    /// assert_eq!(*map.get("bar.json").unwrap(), "\"bar\"\n");
792    /// ```
793    pub fn evaluate_snippet_multi<'a, P>(
794        &'a mut self,
795        filename: P,
796        snippet: &str,
797    ) -> Result<EvalMap<'a>, Error<'a>>
798    where
799        P: AsRef<OsStr>,
800    {
801        let fname = osstr2cstring(filename);
802        let snippet = CString::new(snippet).unwrap();
803        let mut error = 1;
804        let output = unsafe {
805            let v = jsonnet_sys::jsonnet_evaluate_snippet_multi(
806                self.0,
807                fname.as_ptr(),
808                snippet.as_ptr(),
809                &mut error,
810            );
811            JsonnetString::from_ptr(self, v)
812        };
813        match error {
814            0 => Ok(EvalMap(output)),
815            _ => Err(Error(output)),
816        }
817    }
818
819    /// Evaluate a file containing Jsonnet code, returning a number of
820    /// JSON files.
821    ///
822    /// # Errors
823    ///
824    /// Returns any jsonnet error during evaluation.
825    ///
826    /// # Panics
827    ///
828    /// Panics if `filename` contains embedded nul characters.
829    pub fn evaluate_file_stream<'a, P>(&'a mut self, filename: P) -> Result<EvalList<'a>, Error<'a>>
830    where
831        P: AsRef<OsStr>,
832    {
833        let fname = osstr2cstring(filename);
834        let mut error = 1;
835        let output = unsafe {
836            let v = jsonnet_sys::jsonnet_evaluate_file_stream(self.0, fname.as_ptr(), &mut error);
837            JsonnetString::from_ptr(self, v)
838        };
839        match error {
840            0 => Ok(EvalList(output)),
841            _ => Err(Error(output)),
842        }
843    }
844
845    /// Evaluate a string containing Jsonnet code, returning a number
846    /// of JSON files.
847    ///
848    /// # Errors
849    ///
850    /// Returns any jsonnet error during evaluation.
851    ///
852    /// # Panics
853    ///
854    /// Panics if `filename` or `snippet` contain embedded nul characters.
855    ///
856    /// # Examples
857    ///
858    /// ```
859    /// use jsonnet::JsonnetVm;
860    ///
861    /// let mut vm = JsonnetVm::new();
862    /// let output = vm.evaluate_snippet_stream("stream",
863    ///    "['foo', 'bar']").unwrap();
864    ///
865    /// let list: Vec<_> = output.iter().collect();
866    /// assert_eq!(list, vec!["\"foo\"\n", "\"bar\"\n"]);
867    /// ```
868    pub fn evaluate_snippet_stream<'a, P>(
869        &'a mut self,
870        filename: P,
871        snippet: &str,
872    ) -> Result<EvalList<'a>, Error<'a>>
873    where
874        P: AsRef<OsStr>,
875    {
876        let fname = osstr2cstring(filename);
877        let snippet = CString::new(snippet).unwrap();
878        let mut error = 1;
879        let output = unsafe {
880            let v = jsonnet_sys::jsonnet_evaluate_snippet_stream(
881                self.0,
882                fname.as_ptr(),
883                snippet.as_ptr(),
884                &mut error,
885            );
886            JsonnetString::from_ptr(self, v)
887        };
888        match error {
889            0 => Ok(EvalList(output)),
890            _ => Err(Error(output)),
891        }
892    }
893}
894
895impl Drop for JsonnetVm {
896    fn drop(&mut self) {
897        assert!(!self.0.is_null());
898        unsafe { jsonnet_sys::jsonnet_destroy(self.0) }
899    }
900}
901
902#[test]
903fn basic_eval() {
904    let mut vm = JsonnetVm::new();
905    let result = vm.evaluate_snippet("example", "'Hello ' + 'World'");
906    println!("result is {:?}", result);
907    assert!(result.is_ok());
908    assert_eq!(result.unwrap().to_string(), "\"Hello World\"\n");
909}
910
911#[test]
912fn basic_eval_err() {
913    let mut vm = JsonnetVm::new();
914    let result = vm.evaluate_snippet("example", "bogus");
915    println!("result is {:?}", result);
916    assert!(result.is_err());
917    assert!(result.unwrap_err().contains("Unknown variable: bogus"));
918}