refactor(snix/eval,snix/glue): add snix_eval::try_cek! macros

Fixes #146

Change-Id: I971fac0d9d18e4ea73a527e499ac7ac213658477
Reviewed-on: https://cl.snix.dev/c/snix/+/30638
Reviewed-by: Florian Klink <flokli@flokli.de>
Autosubmit: Axel Karjalainen <axel@axka.fi>
Tested-by: besadii
This commit is contained in:
Axel Karjalainen 2025-08-01 18:01:23 +03:00 committed by clbot
parent 8d0ae4f7ae
commit 4aa1137d8e
7 changed files with 227 additions and 210 deletions

View file

@ -16,7 +16,10 @@ mod impure_builtins {
use std::os::unix::ffi::OsStrExt; use std::os::unix::ffi::OsStrExt;
use super::*; use super::*;
use crate::builtins::{coerce_value_to_path, hash::hash_nix_string}; use crate::{
builtins::{coerce_value_to_path, hash::hash_nix_string},
try_cek_to_value,
};
#[builtin("getEnv")] #[builtin("getEnv")]
async fn builtin_get_env(co: GenCo, var: Value) -> Result<Value, ErrorKind> { async fn builtin_get_env(co: GenCo, var: Value) -> Result<Value, ErrorKind> {
@ -28,67 +31,52 @@ mod impure_builtins {
#[builtin("hashFile")] #[builtin("hashFile")]
async fn builtin_hash_file(co: GenCo, algo: Value, path: Value) -> Result<Value, ErrorKind> { async fn builtin_hash_file(co: GenCo, algo: Value, path: Value) -> Result<Value, ErrorKind> {
let path = match coerce_value_to_path(&co, path).await? { let path = try_cek_to_value!(coerce_value_to_path(&co, path).await?);
Err(cek) => return Ok(Value::from(cek)),
Ok(p) => p,
};
let r = generators::request_open_file(&co, path).await; let r = generators::request_open_file(&co, path).await;
hash_nix_string(algo.to_str()?, r).map(Value::from) hash_nix_string(algo.to_str()?, r).map(Value::from)
} }
#[builtin("pathExists")] #[builtin("pathExists")]
async fn builtin_path_exists(co: GenCo, path: Value) -> Result<Value, ErrorKind> { async fn builtin_path_exists(co: GenCo, path: Value) -> Result<Value, ErrorKind> {
match coerce_value_to_path(&co, path).await? { let path = try_cek_to_value!(coerce_value_to_path(&co, path).await?);
Err(cek) => Ok(Value::from(cek)), Ok(generators::request_path_exists(&co, path).await)
Ok(path) => Ok(generators::request_path_exists(&co, path).await),
}
} }
#[builtin("readDir")] #[builtin("readDir")]
async fn builtin_read_dir(co: GenCo, path: Value) -> Result<Value, ErrorKind> { async fn builtin_read_dir(co: GenCo, path: Value) -> Result<Value, ErrorKind> {
match coerce_value_to_path(&co, path).await? { let path = try_cek_to_value!(coerce_value_to_path(&co, path).await?);
Err(cek) => Ok(Value::from(cek)), let dir = generators::request_read_dir(&co, path).await;
Ok(path) => { let res = dir.into_iter().map(|(name, ftype)| {
let dir = generators::request_read_dir(&co, path).await; (
let res = dir.into_iter().map(|(name, ftype)| { // TODO: propagate Vec<u8> or bytes::Bytes into NixString.
( NixString::from(
// TODO: propagate Vec<u8> or bytes::Bytes into NixString. String::from_utf8(name.to_vec()).expect("parsing file name as string"),
NixString::from( ),
String::from_utf8(name.to_vec()).expect("parsing file name as string"), Value::from(ftype.to_string()),
), )
Value::from(ftype.to_string()), });
)
});
Ok(Value::attrs(NixAttrs::from_iter(res))) Ok(Value::attrs(NixAttrs::from_iter(res)))
}
}
} }
#[builtin("readFile")] #[builtin("readFile")]
async fn builtin_read_file(co: GenCo, path: Value) -> Result<Value, ErrorKind> { async fn builtin_read_file(co: GenCo, path: Value) -> Result<Value, ErrorKind> {
match coerce_value_to_path(&co, path).await? { let path = try_cek_to_value!(coerce_value_to_path(&co, path).await?);
Err(cek) => Ok(Value::from(cek)), let mut buf = Vec::new();
Ok(path) => { generators::request_open_file(&co, path)
let mut buf = Vec::new(); .await
generators::request_open_file(&co, path) .read_to_end(&mut buf)?;
.await Ok(Value::from(buf))
.read_to_end(&mut buf)?;
Ok(Value::from(buf))
}
}
} }
#[builtin("readFileType")] #[builtin("readFileType")]
async fn builtin_read_file_type(co: GenCo, path: Value) -> Result<Value, ErrorKind> { async fn builtin_read_file_type(co: GenCo, path: Value) -> Result<Value, ErrorKind> {
match coerce_value_to_path(&co, path).await? { let path = try_cek_to_value!(coerce_value_to_path(&co, path).await?);
Err(cek) => Ok(Value::from(cek)), Ok(Value::from(
Ok(path) => Ok(Value::from( generators::request_read_file_type(&co, path)
generators::request_read_file_type(&co, path) .await
.await .to_string(),
.to_string(), ))
)),
}
} }
} }

View file

@ -14,7 +14,6 @@ use std::collections::VecDeque;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Mutex, OnceLock}; use std::sync::{Mutex, OnceLock};
use crate::arithmetic_op;
use crate::value::PointerEquality; use crate::value::PointerEquality;
use crate::vm::generators::{self, GenCo}; use crate::vm::generators::{self, GenCo};
use crate::warnings::WarningKind; use crate::warnings::WarningKind;
@ -24,6 +23,7 @@ use crate::{
errors::{CatchableErrorKind, ErrorKind}, errors::{CatchableErrorKind, ErrorKind},
value::{CoercionKind, NixAttrs, NixList, NixString, Thunk, Value}, value::{CoercionKind, NixAttrs, NixList, NixString, Thunk, Value},
}; };
use crate::{arithmetic_op, try_cek};
use self::versions::{VersionPart, VersionPartsIter}; use self::versions::{VersionPart, VersionPartsIter};
@ -59,25 +59,23 @@ pub async fn coerce_value_to_path(
return Ok(Ok(*p)); return Ok(Ok(*p));
} }
match generators::request_string_coerce( let vs = try_cek!(
co, generators::request_string_coerce(
value, co,
CoercionKind { value,
strong: false, CoercionKind {
import_paths: false, strong: false,
}, import_paths: false,
) },
.await )
{ .await
Ok(vs) => { );
let path = vs.to_path()?.to_owned();
if path.is_absolute() { let path = vs.to_path()?.to_owned();
Ok(Ok(path)) if path.is_absolute() {
} else { Ok(Ok(path))
Err(ErrorKind::NotAnAbsolutePath(path)) } else {
} Err(ErrorKind::NotAnAbsolutePath(path))
}
Err(cek) => Ok(Err(cek)),
} }
} }
@ -106,7 +104,9 @@ mod pure_builtins {
use os_str_bytes::OsStringBytes; use os_str_bytes::OsStringBytes;
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use crate::{AddContext, NixContext, NixContextElement, value::PointerEquality}; use crate::{
AddContext, NixContext, NixContextElement, try_cek_to_value, value::PointerEquality,
};
use super::*; use super::*;
@ -302,23 +302,20 @@ mod pure_builtins {
if i != 0 { if i != 0 {
res.push_str(&separator); res.push_str(&separator);
} }
match generators::request_string_coerce( let mut s = try_cek_to_value!(
&co, generators::request_string_coerce(
val, &co,
CoercionKind { val,
strong: false, CoercionKind {
import_paths: true, strong: false,
}, import_paths: true,
) },
.await )
{ .await
Ok(mut s) => { );
res.push_str(&s); res.push_str(&s);
if let Some(other_context) = s.take_context() { if let Some(other_context) = s.take_context() {
context.extend(other_context.into_iter()); context.extend(other_context.into_iter());
}
}
Err(c) => return Ok(Value::Catchable(Box::new(c))),
} }
} }
// FIXME: pass immediately the string res. // FIXME: pass immediately the string res.
@ -372,11 +369,11 @@ mod pure_builtins {
#[builtin("elem")] #[builtin("elem")]
async fn builtin_elem(co: GenCo, x: Value, xs: Value) -> Result<Value, ErrorKind> { async fn builtin_elem(co: GenCo, x: Value, xs: Value) -> Result<Value, ErrorKind> {
for val in xs.to_list()? { for val in xs.to_list()? {
match generators::check_equality(&co, x.clone(), val, PointerEquality::AllowAll).await? match try_cek_to_value!(
{ generators::check_equality(&co, x.clone(), val, PointerEquality::AllowAll).await?
Ok(true) => return Ok(true.into()), ) {
Ok(false) => continue, true => return Ok(true.into()),
Err(cek) => return Ok(Value::from(cek)), false => continue,
} }
} }
Ok(false.into()) Ok(false.into())
@ -504,13 +501,10 @@ mod pure_builtins {
let attrs = val.to_attrs()?; let attrs = val.to_attrs()?;
let key = attrs.select_required("key")?; let key = attrs.select_required("key")?;
let value_missing = bgc_insert_key(&co, key.clone(), &mut done_keys).await?; let value_missing =
try_cek_to_value!(bgc_insert_key(&co, key.clone(), &mut done_keys).await?);
if let Err(cek) = value_missing { if !value_missing {
return Ok(Value::Catchable(Box::new(cek)));
}
if let Ok(false) = value_missing {
continue; continue;
} }
@ -909,10 +903,9 @@ mod pure_builtins {
#[builtin("lessThan")] #[builtin("lessThan")]
async fn builtin_less_than(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> { async fn builtin_less_than(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
let span = generators::request_span(&co).await; let span = generators::request_span(&co).await;
match x.nix_cmp_ordering(y, co, span).await? { match try_cek_to_value!(x.nix_cmp_ordering(y, co, span).await?) {
Err(cek) => Ok(Value::from(cek)), Ordering::Less => Ok(Value::Bool(true)),
Ok(Ordering::Less) => Ok(Value::Bool(true)), _ => Ok(Value::Bool(false)),
Ok(_) => Ok(Value::Bool(false)),
} }
} }
@ -1474,23 +1467,18 @@ mod pure_builtins {
return Ok(s); return Ok(s);
} }
match coerce_value_to_path(&co, s).await? { let path = try_cek_to_value!(coerce_value_to_path(&co, s).await?);
Err(cek) => Ok(Value::from(cek)), let path: Value = crate::value::canon_path(path).into();
Ok(path) => { let span = generators::request_span(&co).await;
let path: Value = crate::value::canon_path(path).into(); path.coerce_to_string(
let span = generators::request_span(&co).await; co,
Ok(path CoercionKind {
.coerce_to_string( strong: false,
co, import_paths: false,
CoercionKind { },
strong: false, span,
import_paths: false, )
}, .await
span,
)
.await?)
}
}
} }
#[builtin("tryEval")] #[builtin("tryEval")]
@ -1521,18 +1509,17 @@ async fn bgc_insert_key(
done: &mut Vec<Value>, done: &mut Vec<Value>,
) -> Result<Result<bool, CatchableErrorKind>, ErrorKind> { ) -> Result<Result<bool, CatchableErrorKind>, ErrorKind> {
for existing in done.iter() { for existing in done.iter() {
match generators::check_equality( if try_cek!(
co, generators::check_equality(
existing.clone(), co,
key.clone(), existing.clone(),
// TODO(tazjin): not actually sure which semantics apply here key.clone(),
PointerEquality::ForbidAll, // TODO(tazjin): not actually sure which semantics apply here
) PointerEquality::ForbidAll,
.await? )
{ .await?
Ok(true) => return Ok(Ok(false)), ) {
Ok(false) => (), return Ok(Ok(false));
Err(cek) => return Ok(Err(cek)),
} }
} }

View file

@ -14,6 +14,7 @@ use crate::{
builtins::coerce_value_to_path, builtins::coerce_value_to_path,
generators::pin_generator, generators::pin_generator,
observer::NoOpObserver, observer::NoOpObserver,
try_cek_to_value,
value::{Builtin, Thunk}, value::{Builtin, Thunk},
vm::generators::{self, GenCo}, vm::generators::{self, GenCo},
}; };
@ -25,10 +26,7 @@ async fn import_impl(
mut args: Vec<Value>, mut args: Vec<Value>,
) -> Result<Value, ErrorKind> { ) -> Result<Value, ErrorKind> {
// TODO(sterni): canon_path()? // TODO(sterni): canon_path()?
let mut path = match coerce_value_to_path(&co, args.pop().unwrap()).await? { let mut path = try_cek_to_value!(coerce_value_to_path(&co, args.pop().unwrap()).await?);
Err(cek) => return Ok(Value::Catchable(Box::new(cek))),
Ok(path) => path,
};
if path.is_dir() { if path.is_dir() {
path.push("default.nix"); path.push("default.nix");

View file

@ -16,6 +16,81 @@ use crate::spans::ToSpan;
use crate::value::{CoercionKind, NixString}; use crate::value::{CoercionKind, NixString};
use crate::{SourceCode, Value}; use crate::{SourceCode, Value};
/// Propagates a [catchable error](CatchableErrorKind) as `Ok(Err(_))` or returns the unwrapped value in `Ok`.
///
/// You should use the try operator (`?`) to propagate uncatchable errors before passing catchable
/// errors to `try_cek`.
///
/// **Input type:** `Result<T, CatchableErrorKind>`
///
/// **Output type:** `T`
///
/// **Return type of containing function:** `Result<Result<_, CatchableErrorKind>, _>`
///
/// # Example
///
/// ```
/// use snix_eval::{try_cek, CatchableErrorKind};
///
/// # #[derive(Debug)] struct MyUncatchableError;
/// # fn example() -> Result<Result<(), CatchableErrorKind>, MyUncatchableError> {
/// fn my_fn() -> Result<Result<i32, CatchableErrorKind>, MyUncatchableError> {
/// Ok(Ok(42))
/// }
///
/// let value: i32 = try_cek!(my_fn()?);
/// assert_eq!(value, 42);
///
/// fn my_other_fn() -> Result<Result<i32, CatchableErrorKind>, MyUncatchableError> {
/// Ok(Err(CatchableErrorKind::AssertionFailed))
/// }
///
/// try_cek!(my_other_fn()?); // results in `return Ok(Err(cek))`
/// unreachable!();
///
/// # Ok(Ok(()))
/// # }
/// # fn main() {
/// # pretty_assertions::assert_matches!(example().unwrap(), Err(CatchableErrorKind::AssertionFailed));
/// # }
/// ```
#[macro_export]
macro_rules! try_cek {
($result:expr) => {
match $result {
Ok(s) => s,
Err(cek) => {
// Type-check to avoid accidental misuse
let cek: $crate::CatchableErrorKind = cek;
return Ok(Err(cek));
}
}
};
}
/// Propagates a [catchable error](CatchableErrorKind) as `Ok(Value::Catchable(_))` or returns the unwrapped value in `Ok`.
///
/// **Input type:** `Result<T, CatchableErrorKind>`
///
/// **Output type:** `T`
///
/// **Return type of containing function:** `Result<Value, _>`
///
/// See [`try_cek!`]'s documentation for more.
#[macro_export]
macro_rules! try_cek_to_value {
($result:expr) => {
match $result {
Ok(s) => s,
Err(cek) => {
// Type-check to avoid accidental misuse
let cek: $crate::CatchableErrorKind = cek;
return Ok(Value::Catchable(Box::new(cek)));
}
}
};
}
/// "CatchableErrorKind" errors -- those which can be detected by /// "CatchableErrorKind" errors -- those which can be detected by
/// `builtins.tryEval`. /// `builtins.tryEval`.
/// ///

View file

@ -176,7 +176,7 @@ pub(crate) mod derivation_builtins {
use nix_compat::store_path::hash_placeholder; use nix_compat::store_path::hash_placeholder;
use snix_eval::generators::Gen; use snix_eval::generators::Gen;
use snix_eval::{NixContext, NixContextElement, NixString}; use snix_eval::{NixContext, NixContextElement, NixString, try_cek_to_value};
use crate::builtins::utils::{select_string, strong_importing_coerce_to_string}; use crate::builtins::utils::{select_string, strong_importing_coerce_to_string};
use crate::fetchurl::fetchurl_derivation_to_fetch; use crate::fetchurl::fetchurl_derivation_to_fetch;
@ -279,13 +279,10 @@ pub(crate) mod derivation_builtins {
// These are only set in drv.arguments. // These are only set in drv.arguments.
"args" => { "args" => {
for arg in value.to_list()? { for arg in value.to_list()? {
match strong_importing_coerce_to_string(&co, arg).await { let s =
Err(cek) => return Ok(Value::from(cek)), try_cek_to_value!(strong_importing_coerce_to_string(&co, arg).await);
Ok(s) => { input_context.mimic(&s);
input_context.mimic(&s); drv.arguments.push(s.to_str()?.to_owned())
drv.arguments.push(s.to_str()?.to_owned())
}
}
} }
} }
@ -338,28 +335,23 @@ pub(crate) mod derivation_builtins {
// handle builder and system. // handle builder and system.
"builder" | "system" => { "builder" | "system" => {
match strong_importing_coerce_to_string(&co, value).await { let val_str =
Err(cek) => return Ok(Value::from(cek)), try_cek_to_value!(strong_importing_coerce_to_string(&co, value).await);
Ok(val_str) => { input_context.mimic(&val_str);
input_context.mimic(&val_str);
if arg_name == "builder" { if arg_name == "builder" {
val_str.to_str()?.clone_into(&mut drv.builder); val_str.to_str()?.clone_into(&mut drv.builder);
} else { } else {
val_str.to_str()?.clone_into(&mut drv.system); val_str.to_str()?.clone_into(&mut drv.system);
} }
// Either populate drv.environment or structured_attrs. // Either populate drv.environment or structured_attrs.
if let Some(ref mut structured_attrs) = structured_attrs { if let Some(ref mut structured_attrs) = structured_attrs {
// No need to check for dups, we only iterate over every attribute name once // No need to check for dups, we only iterate over every attribute name once
structured_attrs.insert( structured_attrs
arg_name.to_owned(), .insert(arg_name.to_owned(), val_str.to_str()?.to_owned().into());
val_str.to_str()?.to_owned().into(), } else {
); insert_env(&mut drv, arg_name, val_str.as_bytes().into())?;
} else {
insert_env(&mut drv, arg_name, val_str.as_bytes().into())?;
}
}
} }
} }
@ -384,14 +376,11 @@ pub(crate) mod derivation_builtins {
// No need to check for dups, we only iterate over every attribute name once // No need to check for dups, we only iterate over every attribute name once
structured_attrs.insert(arg_name.to_owned(), val_json); structured_attrs.insert(arg_name.to_owned(), val_json);
} else { } else {
match strong_importing_coerce_to_string(&co, value).await { let val_str =
Err(cek) => return Ok(Value::from(cek)), try_cek_to_value!(strong_importing_coerce_to_string(&co, value).await);
Ok(val_str) => { input_context.mimic(&val_str);
input_context.mimic(&val_str);
insert_env(&mut drv, arg_name, val_str.as_bytes().into())?; insert_env(&mut drv, arg_name, val_str.as_bytes().into())?;
}
}
} }
} }
} }
@ -400,27 +389,21 @@ pub(crate) mod derivation_builtins {
// Configure fixed-output derivations if required. // Configure fixed-output derivations if required.
{ {
let output_hash = match select_string(&co, &input, "outputHash") let output_hash = try_cek_to_value!(
.await select_string(&co, &input, "outputHash")
.context("evaluating the `outputHash` parameter")? .await
{ .context("evaluating the `outputHash` parameter")?
Err(cek) => return Ok(Value::from(cek)), );
Ok(s) => s, let output_hash_algo = try_cek_to_value!(
}; select_string(&co, &input, "outputHashAlgo")
let output_hash_algo = match select_string(&co, &input, "outputHashAlgo") .await
.await .context("evaluating the `outputHashAlgo` parameter")?
.context("evaluating the `outputHashAlgo` parameter")? );
{ let output_hash_mode = try_cek_to_value!(
Err(cek) => return Ok(Value::from(cek)), select_string(&co, &input, "outputHashMode")
Ok(s) => s, .await
}; .context("evaluating the `outputHashMode` parameter")?
let output_hash_mode = match select_string(&co, &input, "outputHashMode") );
.await
.context("evaluating the `outputHashMode` parameter")?
{
Err(cek) => return Ok(Value::from(cek)),
Ok(s) => s,
};
if let Some(warning) = if let Some(warning) =
handle_fixed_output(&mut drv, output_hash, output_hash_algo, output_hash_mode)? handle_fixed_output(&mut drv, output_hash, output_hash_algo, output_hash_mode)?

View file

@ -9,7 +9,7 @@ use nix_compat::nixhash::{HashAlgo, NixHash};
use snix_eval::builtin_macros::builtins; use snix_eval::builtin_macros::builtins;
use snix_eval::generators::Gen; use snix_eval::generators::Gen;
use snix_eval::generators::GenCo; use snix_eval::generators::GenCo;
use snix_eval::{CatchableErrorKind, ErrorKind, Value}; use snix_eval::{CatchableErrorKind, ErrorKind, Value, try_cek};
use std::rc::Rc; use std::rc::Rc;
use url::Url; use url::Url;
@ -56,18 +56,10 @@ async fn extract_fetch_args(
)); ));
} }
let url_str = match select_string(co, &attrs, "url").await? { let url_str = try_cek!(select_string(co, &attrs, "url").await?)
Ok(s) => s.ok_or_else(|| ErrorKind::AttributeNotFound { name: "url".into() })?, .ok_or_else(|| ErrorKind::AttributeNotFound { name: "url".into() })?;
Err(cek) => return Ok(Err(cek)), let name = try_cek!(select_string(co, &attrs, "name").await?);
}; let sha256_str = try_cek!(select_string(co, &attrs, "sha256").await?);
let name = match select_string(co, &attrs, "name").await? {
Ok(s) => s,
Err(cek) => return Ok(Err(cek)),
};
let sha256_str = match select_string(co, &attrs, "sha256").await? {
Ok(s) => s,
Err(cek) => return Ok(Err(cek)),
};
Ok(Ok(NixFetchArgs { Ok(Ok(NixFetchArgs {
url: Url::parse(&url_str).map_err(|e| ErrorKind::SnixError(Rc::new(e)))?, url: Url::parse(&url_str).map_err(|e| ErrorKind::SnixError(Rc::new(e)))?,
@ -89,6 +81,7 @@ async fn extract_fetch_args(
pub(crate) mod fetcher_builtins { pub(crate) mod fetcher_builtins {
use bstr::ByteSlice; use bstr::ByteSlice;
use nix_compat::{flakeref, nixhash::NixHash}; use nix_compat::{flakeref, nixhash::NixHash};
use snix_eval::try_cek_to_value;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use super::*; use super::*;
@ -137,10 +130,7 @@ pub(crate) mod fetcher_builtins {
co: GenCo, co: GenCo,
args: Value, args: Value,
) -> Result<Value, ErrorKind> { ) -> Result<Value, ErrorKind> {
let args = match extract_fetch_args(&co, args).await? { let args = try_cek_to_value!(extract_fetch_args(&co, args).await?);
Ok(args) => args,
Err(cek) => return Ok(Value::from(cek)),
};
// Derive the name from the URL basename if not set explicitly. // Derive the name from the URL basename if not set explicitly.
let name = args let name = args
@ -163,10 +153,7 @@ pub(crate) mod fetcher_builtins {
co: GenCo, co: GenCo,
args: Value, args: Value,
) -> Result<Value, ErrorKind> { ) -> Result<Value, ErrorKind> {
let args = match extract_fetch_args(&co, args).await? { let args = try_cek_to_value!(extract_fetch_args(&co, args).await?);
Ok(args) => args,
Err(cek) => return Ok(Value::from(cek)),
};
// Name defaults to "source" if not set explicitly. // Name defaults to "source" if not set explicitly.
const DEFAULT_NAME_FETCH_TARBALL: &str = "source"; const DEFAULT_NAME_FETCH_TARBALL: &str = "source";

View file

@ -2,6 +2,7 @@ use bstr::ByteSlice;
use snix_eval::{ use snix_eval::{
CatchableErrorKind, CoercionKind, ErrorKind, NixAttrs, NixString, Value, CatchableErrorKind, CoercionKind, ErrorKind, NixAttrs, NixString, Value,
generators::{self, GenCo}, generators::{self, GenCo},
try_cek,
}; };
pub(super) async fn strong_importing_coerce_to_string( pub(super) async fn strong_importing_coerce_to_string(
@ -26,10 +27,8 @@ pub(super) async fn select_string(
key: &str, key: &str,
) -> Result<Result<Option<String>, CatchableErrorKind>, ErrorKind> { ) -> Result<Result<Option<String>, CatchableErrorKind>, ErrorKind> {
if let Some(attr) = attrs.select(key) { if let Some(attr) = attrs.select(key) {
match strong_importing_coerce_to_string(co, attr.clone()).await { let str = try_cek!(strong_importing_coerce_to_string(co, attr.clone()).await);
Err(cek) => return Ok(Err(cek)), return Ok(Ok(Some(str.to_str()?.to_owned())));
Ok(str) => return Ok(Ok(Some(str.to_str()?.to_owned()))),
}
} }
Ok(Ok(None)) Ok(Ok(None))