Examples on integrating nickel-based configs in other tools
Is your feature request related to a problem? Please describe. Many tools have configuration files with various constraints (or in nickel terms: contracts). Nickel can fit nicely into that by providing a library where tool authors are only required to provide a contract of their configuration and get the nice error messages of nickel.
Describe the solution you'd like
### More goal-oriented how-to guides (examples) in the Documentation
- [ ] Example how to utilize the Nickel CLI for verifying tool configs
- [ ] Example how to utilize the nickel-core library from rust code
### A wrapper around nickel-core to use in rust code
- [ ] Simplified opinionated API
- [ ] Possibly Allow any Serde-Deserializable type to be used as a contract
Describe alternatives you've considered I have spent a bit of time trying to get the nice diagnostics in rust code using nickel, and got to this point. The questions in the comments are of rhetoric nature, to showcase which parts of the API felt unclear to me
use anyhow::anyhow;
use nickel_lang_core::eval::cache::lazy::CBNCache;
use nickel_lang_core::program::Program;
use tokio::fs::File;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let buffer = File::create("foo.txt").await?.into_std().await; //why is this trace buffer needed? It created an empty file
let mut test: Program<CBNCache> = Program::new_from_file("./test.ncl", buffer)?; //What is the CNBCache, why do I need it?
let result = test.eval_full().map_err(|e| anyhow!("{:?}",e))?;
// I know that Error impls IntoDiagnostics but that requires codespan::Files and codespan::FileId which is not re-exported by nickel
println!("{}",result);
Ok(())
}
Unfortunately, the nickel-core library is indeed currently a bit provided "as it is" without much guidance nor backward-compatibility guarantee. I must say that it's not currently a high priority to work on that front. However, there might still be small effort improvements we can make.
First, would re-exporting codespan::Files and codespan::FileIds (or even the whole of codespan, I don't know), would help?
I think the right place for these kinds of integrations would be a dedicated crate. Similar to how nickel-cli exposes nickel functionality to the CLI, a serde-nickel crate could be used to provide nickel-as-a-library. This would also allow nickel-core to develop without strict compatibility requirements.
A serde_nickel crate would be nice and could provide methods like these:
#[derive(Debug, Deserialize)]
struct Fruit {
name: String,
calories: usize,
}
#[test]
fn test(){
let fruit = serde_nickel::from_file("./path/to/fruit.ncl").unwrap();
//evaluates at comptime () and embeds the exported nickel expression in the binary.
// at runtime, this expression is transformed into the target type using serde.
// The serde type could act as a contract and allow the nice error messages of nickel to be displayed.
// (at comptime the fruit type is not yet compiled, so the checking has to happen at runtime)
let fruit = serde_nickel::include_nickel!("./path/to/fruit.ncl").unwrap();
// maybe even inline nickel inside rust :)
let fruit = serde_nickel::nickel!(
{name="Banana", calories=135,}
);
}
Regarding small effort improvements, I realized that the errors produced by the Program::eval() methods can be reported using Program::report(). There is probably a good reason for this and therefore the API is a bit unintuitive.
I think some more examples in that regard would already help, even if they only consist of: "Hey, look at this implementation in nickel_cli: https://github.com/tweag/nickel/blob/master/cli/bin/nickel.rs#L209-L232 and how to do error reporting: https://github.com/tweag/nickel/blob/master/cli/bin/nickel.rs#L180C5-L183C6"