use std::borrow::Cow;
use std::fmt;
use schemars::{Schema, SchemaGenerator, json_schema};
use serde::de::{Error as DeError, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::super::images::ChallengeImageReference;
use super::super::names::{ResourceProfileName, TargetName};
use crate::zip_project::ZipProjectNetworkAccess;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub enum DockerPlatform {
#[serde(rename = "linux/arm64")]
LinuxArm64,
#[serde(rename = "linux/amd64")]
LinuxAmd64,
}
impl DockerPlatform {
pub fn as_str(self) -> &'static str {
match self {
Self::LinuxArm64 => "linux/arm64",
Self::LinuxAmd64 => "linux/amd64",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TargetAccelerator {
None,
Gpu,
}
impl TargetAccelerator {
pub fn as_str(self) -> &'static str {
match self {
Self::None => "none",
Self::Gpu => "gpu",
}
}
pub fn from_storage_value(value: &str) -> Option<Self> {
match value {
"none" => Some(Self::None),
"gpu" => Some(Self::Gpu),
_ => None,
}
}
}
impl Serialize for TargetAccelerator {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::None => serializer.serialize_none(),
Self::Gpu => serializer.serialize_str("gpu"),
}
}
}
impl<'de> Deserialize<'de> for TargetAccelerator {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct TargetAcceleratorVisitor;
impl<'de> Visitor<'de> for TargetAcceleratorVisitor {
type Value = TargetAccelerator;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("null for no accelerator or \"gpu\" for GPU acceleration")
}
fn visit_none<E>(self) -> std::result::Result<Self::Value, E>
where
E: DeError,
{
Ok(TargetAccelerator::None)
}
fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
where
E: DeError,
{
Ok(TargetAccelerator::None)
}
fn visit_some<D>(self, deserializer: D) -> std::result::Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(self)
}
fn visit_str<E>(self, value: &str) -> std::result::Result<Self::Value, E>
where
E: DeError,
{
match value {
"gpu" => Ok(TargetAccelerator::Gpu),
"cpu" => Err(E::custom(
"accelerator must be explicit null when no accelerator is required, not \"cpu\"",
)),
other => Err(E::unknown_variant(other, &["gpu"])),
}
}
}
deserializer.deserialize_any(TargetAcceleratorVisitor)
}
}
impl schemars::JsonSchema for TargetAccelerator {
fn inline_schema() -> bool {
true
}
fn schema_name() -> Cow<'static, str> {
Cow::Borrowed("TargetAccelerator")
}
fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
json_schema!({
"x-agentics-preserve-null": true,
"oneOf": [
{ "type": "null" },
{ "type": "string", "enum": ["gpu"] }
]
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ChallengeTargetSpec {
pub name: TargetName,
pub docker_platform: DockerPlatform,
pub accelerator: TargetAccelerator,
pub validation_enabled: bool,
pub resource_profile: ResourceProfileSpec,
}
#[derive(Debug, Clone, Serialize, Deserialize, garde::Validate, schemars::JsonSchema)]
#[garde(allow_unvalidated)]
#[serde(deny_unknown_fields)]
pub struct ResourceProfileSpec {
pub name: ResourceProfileName,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[garde(custom(crate::validation::optional_trimmed_non_empty))]
pub resource_description: Option<String>,
pub solution_image: ChallengeImageReference,
pub evaluator_image: ChallengeImageReference,
pub solution: SolutionStageProfiles,
pub evaluator: EvaluatorStageProfiles,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hardware_metadata: Option<HardwareProfileSpec>,
}
#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SolutionStageProfiles {
pub setup: StageResourceProfile,
pub build: StageResourceProfile,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub run: Option<StageResourceProfile>,
}
#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct EvaluatorStageProfiles {
pub setup: StageResourceProfile,
pub run: StageResourceProfile,
}
#[derive(Debug, Clone, Serialize, Deserialize, garde::Validate, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct StageResourceProfile {
#[garde(range(min = 1))]
pub timeout_sec: u64,
#[garde(range(min = 1))]
pub memory_limit_mb: u64,
#[garde(range(min = 1))]
pub cpu_limit_millis: u32,
#[garde(range(min = 1))]
pub disk_limit_mb: u64,
#[garde(skip)]
pub network_access: ZipProjectNetworkAccess,
}
#[derive(Debug, Clone, Serialize, Deserialize, garde::Validate, schemars::JsonSchema)]
#[garde(allow_unvalidated)]
pub struct HardwareProfileSpec {
#[garde(custom(crate::validation::trimmed_non_empty))]
pub kind: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[garde(custom(crate::validation::optional_trimmed_non_empty))]
pub gpu_model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[garde(range(min = 1))]
pub gpu_count: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[garde(range(min = 1))]
pub gpu_memory_gb: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[garde(custom(crate::validation::optional_trimmed_non_empty))]
pub cuda_variant: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[garde(custom(crate::validation::optional_trimmed_non_empty))]
pub cuda_version: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[garde(custom(crate::validation::optional_trimmed_non_empty))]
pub driver_minimum: Option<String>,
}