use std::collections::HashMap;
use std::{fmt, path::PathBuf};
use anyhow::{anyhow, Context};
use clap::ValueEnum;
use serde::de::MapAccess;
use serde::{
de::{self, Visitor},
Deserialize, Deserializer,
};
use ia_sandbox::config::{Mount, MountOptions, SpaceUsage};
#[derive(Debug, Clone, ValueEnum, Deserialize, Default)]
#[allow(clippy::doc_markdown)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum OutputType {
#[default]
Human,
Oneline,
Json,
}
pub(crate) fn parse_space_usage(string: &str) -> anyhow::Result<SpaceUsage> {
let number_index = string
.find(|c: char| !c.is_ascii_digit())
.with_context(|| {
anyhow!("Could not find space usage suffix (b/kb/mb/gb/kib/mib/gib): {string}",)
})?;
let (number, suffix) = string.split_at(number_index);
let number = number
.parse::<u64>()
.with_context(|| anyhow!("Could not parse number {number}"))?;
match suffix {
"b" => Ok(SpaceUsage::from_bytes(number)),
"kb" => Ok(SpaceUsage::from_kilobytes(number)),
"mb" => Ok(SpaceUsage::from_megabytes(number)),
"gb" => Ok(SpaceUsage::from_gigabytes(number)),
"kib" => Ok(SpaceUsage::from_kibibytes(number)),
"mib" => Ok(SpaceUsage::from_mebibytes(number)),
"gib" => Ok(SpaceUsage::from_gibibytes(number)),
suffix => Err(anyhow!("Unrecognized suffix: {suffix}")),
}
}
pub(crate) fn parse_space_usage_serde<'de, D: Deserializer<'de>>(
d: D,
) -> Result<Option<SpaceUsage>, D::Error> {
struct Inner(SpaceUsage);
impl<'de> Deserialize<'de> for Inner {
fn deserialize<D>(d: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct V;
impl<'de> Visitor<'de> for V {
type Value = SpaceUsage;
fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("valid space usage string")
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<SpaceUsage, E> {
parse_space_usage(v).map_err(E::custom)
}
}
d.deserialize_str(V).map(Inner)
}
}
Ok(Option::<Inner>::deserialize(d)?.map(|Inner(usage)| usage))
}
fn parse_mount_options(string: &str) -> anyhow::Result<MountOptions> {
let mut mount_options = MountOptions::default();
for option in string.split(',') {
match option {
"rw" => mount_options.set_read_only(false),
"dev" => mount_options.set_dev(true),
"exec" => mount_options.set_exec(true),
_ => {
return Err(anyhow!(
"Could not parse mount option, unrecognized `{option}`",
));
}
}
}
Ok(mount_options)
}
fn parse_mount_options_serde<'de, D: Deserializer<'de>>(d: D) -> Result<MountOptions, D::Error> {
struct V;
impl<'de2> Visitor<'de2> for V {
type Value = MountOptions;
fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("a mount option as a string")
}
fn visit_str<E>(self, s: &str) -> Result<MountOptions, E>
where
E: de::Error,
{
parse_mount_options(s).map_err(E::custom)
}
}
d.deserialize_str(V)
}
pub(crate) fn parse_mount(string: &str) -> anyhow::Result<Mount> {
let parts: Vec<&str> = string.split(':').collect();
match parts.as_slice() {
[source] => Ok(Mount::new(
PathBuf::from(source),
PathBuf::from(source),
MountOptions::default(),
)),
[source, destination] => Ok(Mount::new(
PathBuf::from(source),
PathBuf::from(destination),
MountOptions::default(),
)),
[source, destination, options] => Ok(Mount::new(
PathBuf::from(source),
PathBuf::from(destination),
parse_mount_options(options)?,
)),
_ => Err(anyhow!("Could not parse mount")),
}
}
pub(crate) fn parse_mounts_serde<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<Mount>, D::Error> {
struct MountWrapper(Mount);
impl<'de> Deserialize<'de> for MountWrapper {
fn deserialize<D>(d: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct InnerMount {
source: PathBuf,
destination: PathBuf,
#[serde(deserialize_with = "parse_mount_options_serde")]
options: MountOptions,
}
struct V;
impl<'de> Visitor<'de> for V {
type Value = Mount;
fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("a mount as a string or a source/destination/options table")
}
fn visit_str<E>(self, s: &str) -> Result<Mount, E>
where
E: de::Error,
{
parse_mount(s).map_err(E::custom)
}
fn visit_map<M>(self, map: M) -> Result<Mount, M::Error>
where
M: MapAccess<'de>,
{
Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)).map(
|InnerMount {
source,
destination,
options,
}| Mount::new(source, destination, options),
)
}
}
d.deserialize_any(V).map(MountWrapper)
}
}
struct V;
impl<'de> Visitor<'de> for V {
type Value = Vec<Mount>;
fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("a list of mounts")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Vec<Mount>, A::Error>
where
A: de::SeqAccess<'de>,
{
let mut result = Vec::new();
if let Some(size_hint) = seq.size_hint() {
result.reserve(size_hint);
}
while let Some(MountWrapper(next)) = seq.next_element()? {
result.push(next);
}
Ok(result)
}
}
d.deserialize_any(V)
}
pub(crate) fn parse_environment(string: &str) -> anyhow::Result<(String, String)> {
string
.split_once('=')
.with_context(|| anyhow!("Could not parse env `{string}`"))
.map(|(key, value)| (key.to_string(), value.to_string()))
}
pub(crate) fn parse_environment_serde<'de, D: Deserializer<'de>>(
d: D,
) -> Result<Vec<(String, String)>, D::Error> {
Ok(HashMap::<String, String>::deserialize(d)?
.into_iter()
.collect())
}