From 311c66047f7187feaf4ed33eea0cc64baf933874 Mon Sep 17 00:00:00 2001 From: stuebinm Date: Mon, 5 Apr 2021 00:57:16 +0200 Subject: add utility program This can be used to write configs in dhall (instead of plain json), combined with some input validation and (optionally) automatic encryption via the age rust crate. --- utils/src/main.rs | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 utils/src/main.rs (limited to 'utils/src/main.rs') diff --git a/utils/src/main.rs b/utils/src/main.rs new file mode 100644 index 0000000..d49a3d7 --- /dev/null +++ b/utils/src/main.rs @@ -0,0 +1,103 @@ +use serde::{Deserialize, Serialize}; +use serde_dhall::StaticType; +use serde_json as json; + +use std::fs; +use std::io::Write; + +use std::path::PathBuf; +use structopt::StructOpt; + +use age::x25519::Recipient; + +#[derive(Deserialize, Serialize, StaticType, Debug)] +struct Survey { + title: String, + description: String, + questions: Vec, + pubkey: Option +} + +#[derive(Deserialize, Serialize, StaticType, Debug)] +struct Question { + question: String, + name: String, + space: AnswerSpace, +} + +#[derive(Deserialize, Serialize, StaticType, Debug)] +enum AnswerSpace { + Single(Vec), + Multiple(Vec), + YesOrNo, + Freeform(String), +} + +#[derive(StructOpt, Debug)] +#[structopt(name = "basic")] +struct Options { + /// a dhall configuration file that describes a survey + #[structopt(long, short, parse(from_os_str))] + config_file: PathBuf, + /// encrypt the survey with the given password (not yet implemented) + #[structopt(long, short)] + password: Option>, +} + +fn main () { + let opt = Options::from_args(); + + let config_file = std::fs::read_to_string(opt.config_file).unwrap(); + + // hacky way to get a "prelude" in dhall which doesn't have to be + // imported: just wrap our input code into a dhall "let"-statement. + // Probably doesn't scale very vell, though ... + let code = format!( + "let Question = {} \nlet Answers = {} \nin {}", + Question::static_type(), + AnswerSpace::static_type(), + config_file + ); + match serde_dhall::from_str(&code) + .static_type_annotation() + .parse::() + { + Err(e) => { + eprintln!("There is an error in your dhall code!\n{}", e); + std::process::exit(1); + }, + Ok(data) => { + let json = json::to_string(&data).unwrap(); + + // if a public key is given to encrypt the survey, ad-hoc typecheck it + // (not sure if the dhall crate allows defining custom types which are + // opaque to dhall ...) + match data.pubkey { + Some (key) => key.parse::().is_err(), + None => false + }.then(|| { + println!("field pubkey is not a valid public key, aborting ..."); + std::process::exit(1); + }); + + + // out here to avoid borrowing issues — if it were in the password + // branch below, it would go out of scope at its end, since .as_slice() + // just borrows its argument + let mut encrypted = vec![]; + // are we restricting access to the survey? if so, encrypt it with + // the password as passphrase. + let outdata = match opt.password { + None => json.as_bytes(), + Some(password) => { + let encryptor = age::Encryptor::with_user_passphrase(password); + let mut writer = encryptor.wrap_output(&mut encrypted).unwrap(); + writer.write_all(&json.as_bytes()).unwrap(); + writer.finish().unwrap(); + encrypted.as_slice() + } + }; + fs::write("outfile", outdata).expect("cannot write to outfile!"); + } + } +} -- cgit v1.2.3