isoloom_spec/syntax/step/
accounts.rs1use std::borrow::Cow;
5use std::fmt;
6
7use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
8use serde::de::{self, IgnoredAny, MapAccess, Visitor};
9use serde::{Deserialize, Deserializer};
10
11use super::super::mistake::Mistake;
12use super::super::problem::Code;
13use super::words::{AbsPath, GroupName, UserName};
14
15#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, JsonSchema)]
17#[serde(deny_unknown_fields)]
18pub struct UserStep {
19 pub name: UserName,
21 #[serde(default)]
23 pub uid: Option<u32>,
24 #[serde(default)]
26 pub group: Option<GroupName>,
27 #[serde(default)]
29 pub groups: Vec<GroupName>,
30 #[serde(default)]
32 pub home: Option<AbsPath>,
33 #[serde(default)]
35 pub shell: Option<AbsPath>,
36 #[serde(default)]
38 pub system: bool,
39 #[serde(default)]
42 pub password: Option<Password>,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, JsonSchema)]
47#[serde(deny_unknown_fields)]
48pub struct GroupStep {
49 pub name: GroupName,
51 #[serde(default)]
53 pub gid: Option<u32>,
54 #[serde(default)]
56 pub system: bool,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Hash)]
61pub struct Password(String);
62
63impl Password {
64 #[must_use]
66 pub fn as_str(&self) -> &str {
67 &self.0
68 }
69}
70
71impl<'de> Deserialize<'de> for Password {
72 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Password, D::Error> {
73 d.deserialize_any(PasswordVisitor)
74 }
75}
76
77struct PasswordVisitor;
79
80impl<'de> Visitor<'de> for PasswordVisitor {
81 type Value = Password;
82
83 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
84 f.write_str("a password, as text")
85 }
86
87 fn visit_str<E: de::Error>(self, v: &str) -> Result<Password, E> {
88 if v.is_empty() {
89 return Err(E::custom("an empty password: leave `password` out for an account without one"));
90 }
91 Ok(Password(v.to_owned()))
92 }
93
94 fn visit_u64<E: de::Error>(self, v: u64) -> Result<Password, E> {
95 Ok(Password(v.to_string()))
96 }
97
98 fn visit_i64<E: de::Error>(self, v: i64) -> Result<Password, E> {
99 Ok(Password(v.to_string()))
100 }
101
102 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Password, A::Error> {
103 while map.next_entry::<IgnoredAny, IgnoredAny>()?.is_some() {}
104 Err(de::Error::custom(
105 Mistake::new(
106 Code::SensitiveValue,
107 "steps have no secrets: a step's password is lab content, written in the generated files",
108 )
109 .fix("a literal (lab content), or a catalogue entry's `users` setting for a secret or an input"),
110 ))
111 }
112}
113
114impl JsonSchema for Password {
115 fn schema_name() -> Cow<'static, str> {
116 "Password".into()
117 }
118
119 fn inline_schema() -> bool {
120 true
121 }
122
123 fn json_schema(_: &mut SchemaGenerator) -> Schema {
124 json_schema!({ "type": ["string", "integer"], "minLength": 1 })
125 }
126}