use std::borrow::Cow;
use std::fmt;
use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
use serde::de::{self, IgnoredAny, MapAccess, Visitor};
use serde::{Deserialize, Deserializer};
use super::super::mistake::Mistake;
use super::super::problem::Code;
use super::words::{AbsPath, GroupName, UserName};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct UserStep {
pub name: UserName,
#[serde(default)]
pub uid: Option<u32>,
#[serde(default)]
pub group: Option<GroupName>,
#[serde(default)]
pub groups: Vec<GroupName>,
#[serde(default)]
pub home: Option<AbsPath>,
#[serde(default)]
pub shell: Option<AbsPath>,
#[serde(default)]
pub system: bool,
#[serde(default)]
pub password: Option<Password>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct GroupStep {
pub name: GroupName,
#[serde(default)]
pub gid: Option<u32>,
#[serde(default)]
pub system: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Password(String);
impl Password {
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl<'de> Deserialize<'de> for Password {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Password, D::Error> {
d.deserialize_any(PasswordVisitor)
}
}
struct PasswordVisitor;
impl<'de> Visitor<'de> for PasswordVisitor {
type Value = Password;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a password, as text")
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<Password, E> {
if v.is_empty() {
return Err(E::custom("an empty password: leave `password` out for an account without one"));
}
Ok(Password(v.to_owned()))
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<Password, E> {
Ok(Password(v.to_string()))
}
fn visit_i64<E: de::Error>(self, v: i64) -> Result<Password, E> {
Ok(Password(v.to_string()))
}
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Password, A::Error> {
while map.next_entry::<IgnoredAny, IgnoredAny>()?.is_some() {}
Err(de::Error::custom(
Mistake::new(
Code::SensitiveValue,
"steps have no secrets: a step's password is lab content, written in the generated files",
)
.fix("a literal (lab content), or a catalogue entry's `users` setting for a secret or an input"),
))
}
}
impl JsonSchema for Password {
fn schema_name() -> Cow<'static, str> {
"Password".into()
}
fn inline_schema() -> bool {
true
}
fn json_schema(_: &mut SchemaGenerator) -> Schema {
json_schema!({ "type": ["string", "integer"], "minLength": 1 })
}
}