From af8672838625d9db797cd9474496470f5910019e Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Thu, 4 Apr 2024 16:38:12 +0200 Subject: [PATCH] feat: EvalState.require_string (cherry picked from commit ac29248e10d6e2364138d11196151a9f445b379d) --- rust/nix-expr/src/eval_state.rs | 102 ++++++++++++++++++++++++++++++++ rust/nix-expr/src/value.rs | 2 +- 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/rust/nix-expr/src/eval_state.rs b/rust/nix-expr/src/eval_state.rs index e1ec8a0..a5ed53a 100644 --- a/rust/nix-expr/src/eval_state.rs +++ b/rust/nix-expr/src/eval_state.rs @@ -106,6 +106,24 @@ impl EvalState { let r = unsafe { raw::nix_get_type(self.context.ptr(), value.raw_ptr()) }; Ok(ValueType::from_raw(r)) } + /// Not exposed, because the caller must always explicitly handle the context or not accept one at all. + fn get_string(&self, value: &Value) -> Result { + let c_str_raw = unsafe { raw::nix_get_string(self.context.ptr(), value.raw_ptr()) }; + self.context.check_err()?; + let cstring = unsafe { std::ffi::CStr::from_ptr(c_str_raw) }; + let str = cstring + .to_str() + .map_err(|e| anyhow::format_err!("Nix string is not valid UTF-8: {}", e))?; + Ok(str.to_owned()) + } + /// NOTE: this will be replaced by two methods, one that also returns the context, and one that checks that the context is empty + pub fn require_string(&self, value: &Value) -> Result { + let t = self.value_type(value)?; + if t != ValueType::String { + bail!("expected a string, but got a {:?}", t); + } + self.get_string(value) + } fn new_value_uninitialized(&self) -> Value { let value = unsafe { raw::nix_alloc_value(self.context.ptr(), self.raw_ptr()) }; @@ -229,6 +247,90 @@ mod tests { es.force(&v).unwrap(); let t = es.value_type(&v).unwrap(); assert!(t == ValueType::String); + let s = es.require_string(&v).unwrap(); + assert!(s == "hello"); + }) + .unwrap(); + } + + #[test] + fn eval_state_value_string_unexpected_bool() { + gc_registering_current_thread(|| { + let store = Store::open("auto").unwrap(); + let es = EvalState::new(store).unwrap(); + let v = es + .eval_from_string("true".to_string(), "".to_string()) + .unwrap(); + es.force(&v).unwrap(); + let r = es.require_string(&v); + assert!(r.is_err()); + // TODO: safe print value (like Nix would) + assert_eq!( + r.unwrap_err().to_string(), + "expected a string, but got a Bool" + ); + }) + .unwrap() + } + + #[test] + fn eval_state_value_string_unexpected_path_value() { + gc_registering_current_thread(|| { + let store = Store::open("auto").unwrap(); + let es = EvalState::new(store).unwrap(); + let v = es + .eval_from_string("/foo".to_string(), "".to_string()) + .unwrap(); + es.force(&v).unwrap(); + let r = es.require_string(&v); + assert!(r.is_err()); + assert_eq!( + r.unwrap_err().to_string(), + "expected a string, but got a Path" + ); + }) + .unwrap() + } + + #[test] + fn eval_state_value_string_bad_utf() { + gc_registering_current_thread(|| { + let store = Store::open("auto").unwrap(); + let es = EvalState::new(store).unwrap(); + let v = es + .eval_from_string( + "builtins.substring 0 1 \"ΓΌ\"".to_string(), + "".to_string(), + ) + .unwrap(); + es.force(&v).unwrap(); + let t = es.value_type(&v).unwrap(); + assert!(t == ValueType::String); + let r = es.require_string(&v); + assert!(r.is_err()); + assert!(r + .unwrap_err() + .to_string() + .contains("Nix string is not valid UTF-8")); + }) + .unwrap(); + } + + #[test] + fn eval_state_value_string_unexpected_context() { + gc_registering_current_thread(|| { + let store = Store::open("auto").unwrap(); + let es = EvalState::new(store).unwrap(); + let v = es + .eval_from_string("(derivation { name = \"hello\"; system = \"dummy\"; builder = \"cmd.exe\"; }).outPath".to_string(), "".to_string()) + .unwrap(); + es.force(&v).unwrap(); + let t = es.value_type(&v).unwrap(); + assert!(t == ValueType::String); + // TODO + // let r = es.require_string_without_context(&v); + // assert!(r.is_err()); + // assert!(r.unwrap_err().to_string().contains("unexpected context")); }) .unwrap(); } diff --git a/rust/nix-expr/src/value.rs b/rust/nix-expr/src/value.rs index cbe1164..0f4ec13 100644 --- a/rust/nix-expr/src/value.rs +++ b/rust/nix-expr/src/value.rs @@ -5,7 +5,7 @@ use std::ptr::NonNull; // TODO: test: cloning a thunk does not duplicate the evaluation. /** The type of a value (or thunk) */ -#[derive(Eq, PartialEq)] +#[derive(Eq, PartialEq, Debug)] pub enum ValueType { AttrSet, Bool,