summaryrefslogtreecommitdiff
path: root/utils/src/main.rs
diff options
context:
space:
mode:
authorstuebinm2021-04-05 00:57:16 +0200
committerstuebinm2021-04-05 00:57:16 +0200
commit311c66047f7187feaf4ed33eea0cc64baf933874 (patch)
treee938325f7f7268c822f0580a56f1193da97565f7 /utils/src/main.rs
parent0f8b78c0b7e2aca94a14b36af020d0f7d8301cc5 (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.rs103
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!");
+ }
+ }
+}