summaryrefslogtreecommitdiff
path: root/utils/src/main.rs
blob: ea267d8ec34478d0cd1c04a74ce56f894344c333 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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 secrecy::ExposeSecret;

use age::x25519::Recipient;

#[derive(Deserialize, Serialize, StaticType, Debug)]
#[serde(rename_all="lowercase")]
enum Lang {
    de,
    en
}

#[derive(Deserialize, Serialize, StaticType, Debug)]
struct Survey {
    title: String,
    description: String,
    questions: Vec<Question>,
    pubkey: Option<String>,
    lang: Lang
}

#[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)]
struct Options {
    /// a dhall configuration file that describes a survey
    #[structopt(long, short, parse(from_os_str))]
    config_file: PathBuf,
    /// encrypt the survey with a passphrase (will be printed to stderr)
    #[structopt(long, short)]
    encrypt: bool,
    /// file to write the configuration to (will otherwise print to stdout)
    #[structopt(long, short)]
    out_file: Option<PathBuf>
}

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 Lang = {}\nin {}",
        Question::static_type(),
        Lang::static_type(),
        config_file
    );
    match serde_dhall::from_str(&code)
        .substitute_names(vec![
            ("Question".to_string(),Question::static_type()),
            ("Answers".to_string(), AnswerSpace::static_type()),
            ("Lang".to_string(), Lang::static_type())
        ].iter().cloned().collect())
        .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.encrypt {
                false => json.as_bytes(),
                true => {
                    let key = age::x25519::Identity::generate();
                    let pubkey = key.to_public();
                    let encryptor = age::Encryptor::with_recipients(vec![Box::new(pubkey)]);
                    let mut writer = encryptor.wrap_output(&mut encrypted).unwrap();
                    writer.write_all(&json.as_bytes()).unwrap();
                    writer.finish().unwrap();
                    eprintln!("Passphrase for this survey: {}", key.to_string().expose_secret());
                    encrypted.as_slice()
                }
            };
            match opt.out_file {
                Some(file) => fs::write(file.clone(), outdata)
                    .expect(&format!("cannot write to file {:?}!", file)),
                None => {
                    let mut out = std::io::stdout();
                    out.write_all(outdata).unwrap();
                    out.flush().unwrap()
                }
            };
        }
    }
}