diff options
author | stuebinm | 2021-04-05 00:57:16 +0200 |
---|---|---|
committer | stuebinm | 2021-04-05 00:57:16 +0200 |
commit | 311c66047f7187feaf4ed33eea0cc64baf933874 (patch) | |
tree | e938325f7f7268c822f0580a56f1193da97565f7 /utils/src/main.rs | |
parent | 0f8b78c0b7e2aca94a14b36af020d0f7d8301cc5 (diff) |
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.
Diffstat (limited to '')
-rw-r--r-- | utils/src/main.rs | 103 |
1 files changed, 103 insertions, 0 deletions
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<Question>, + pubkey: Option<String> +} + +#[derive(Deserialize, Serialize, StaticType, Debug)] +struct Question { + question: String, + name: String, + space: AnswerSpace, +} + +#[derive(Deserialize, Serialize, StaticType, Debug)] +enum AnswerSpace { + Single(Vec<String>), + Multiple(Vec<String>), + 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<secrecy::Secret<String>>, +} + +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::<Survey>() + { + 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::<Recipient>().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!"); + } + } +} |