use crate::hardware::Resource;
use crate::standard::ocp::{ManagementProfileClaim, Recognition, RecognitionProgram};
use crate::standard::StandardsClaim;
use crate::types::{deserialize_optional_one_or_many, deserialize_optional_one_or_many_strings};
use crate::validation::{Validate, ValidationReport};
use crate::OneOrMany;
use acorn_core::prelude::alloc::{format, String};
use acorn_core::validation::ValidationError;
use alloc::collections::BTreeSet;
use bon::Builder;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_trim::{option_string_trim, string_trim};
use serde_with::skip_serializing_none;
#[skip_serializing_none]
#[derive(Builder, Clone, Debug, Deserialize, JsonSchema, Serialize, Validate)]
#[builder(start_fn = init, on(String, into))]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
#[validate(schema(function = "ComputeEnvironment::is_valid", skip_on_field_errors = false))]
pub struct ComputeEnvironment {
#[validate(nonempty)]
#[serde(deserialize_with = "string_trim")]
pub identifier: String,
#[validate(nonempty)]
#[serde(deserialize_with = "string_trim")]
pub name: String,
#[builder(into)]
#[validate(nested)]
pub platforms: OneOrMany<ComputePlatform>,
#[validate(nested)]
pub facility: Option<ComputeFacility>,
#[builder(into)]
#[validate(nested)]
#[serde(default, deserialize_with = "deserialize_optional_one_or_many")]
pub recognitions: Option<OneOrMany<Recognition>>,
#[validate(timestamp)]
#[serde(default, deserialize_with = "option_string_trim")]
pub observed_at: Option<String>,
}
#[skip_serializing_none]
#[derive(Builder, Clone, Debug, Deserialize, JsonSchema, Serialize, Validate)]
#[builder(start_fn = init, on(String, into))]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
#[validate(schema(function = "ComputeFacility::is_valid", skip_on_field_errors = false))]
pub struct ComputeFacility {
#[validate(nonempty)]
#[serde(deserialize_with = "string_trim")]
pub identifier: String,
#[validate(nonempty)]
#[serde(deserialize_with = "string_trim")]
pub name: String,
#[validate(nonempty)]
#[serde(default, deserialize_with = "option_string_trim")]
pub operator: Option<String>,
#[validate(nonempty)]
#[serde(default, deserialize_with = "option_string_trim")]
pub location: Option<String>,
#[builder(into)]
#[validate(each(ror))]
#[serde(default, deserialize_with = "deserialize_optional_one_or_many_strings")]
pub ror: Option<OneOrMany<String>>,
#[builder(into)]
#[validate(nested)]
#[serde(default, deserialize_with = "deserialize_optional_one_or_many")]
pub standards_claims: Option<OneOrMany<StandardsClaim>>,
#[builder(into)]
#[validate(nested)]
#[serde(default, deserialize_with = "deserialize_optional_one_or_many")]
pub recognitions: Option<OneOrMany<Recognition>>,
}
#[skip_serializing_none]
#[derive(Builder, Clone, Debug, Deserialize, JsonSchema, Serialize, Validate)]
#[builder(start_fn = init, on(String, into))]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
#[validate(schema(function = "ComputePlatform::is_valid", skip_on_field_errors = false))]
pub struct ComputePlatform {
#[validate(nonempty)]
#[serde(deserialize_with = "string_trim")]
pub identifier: String,
#[validate(nonempty)]
#[serde(deserialize_with = "string_trim")]
pub name: String,
#[validate(nonempty)]
#[serde(default, deserialize_with = "option_string_trim")]
pub manufacturer: Option<String>,
#[validate(nonempty)]
#[serde(default, deserialize_with = "option_string_trim")]
pub model: Option<String>,
#[builder(into)]
#[validate(nested)]
pub resources: OneOrMany<Resource>,
#[builder(into)]
#[validate(nested)]
#[serde(default, deserialize_with = "deserialize_optional_one_or_many")]
pub standards_claims: Option<OneOrMany<StandardsClaim>>,
#[builder(into)]
#[validate(nested)]
#[serde(default, deserialize_with = "deserialize_optional_one_or_many")]
pub recognitions: Option<OneOrMany<Recognition>>,
#[builder(into)]
#[validate(nested)]
#[serde(default, deserialize_with = "deserialize_optional_one_or_many")]
pub management_profile_claims: Option<OneOrMany<ManagementProfileClaim>>,
}
impl ComputeEnvironment {
fn is_valid(&self, _context: &()) -> Result<(), ValidationReport> {
ValidationReport::empty("platforms", self.platforms.is_empty())
.invalid_recognition_scopes(&self.recognitions, RecognitionProgram::is_environment_recognition)
.finish()
}
}
impl ComputeFacility {
fn is_valid(&self, _context: &()) -> Result<(), ValidationReport> {
ValidationReport::new()
.duplicate_claims(&self.standards_claims)
.invalid_recognition_scopes(&self.recognitions, RecognitionProgram::is_facility_recognition)
.finish()
}
}
impl ComputePlatform {
fn is_valid(&self, _context: &()) -> Result<(), ValidationReport> {
ValidationReport::empty("resources", self.resources.is_empty())
.duplicate_claims(&self.standards_claims)
.invalid_recognition_scopes(&self.recognitions, RecognitionProgram::is_platform_recognition)
.finish()
}
}
impl ValidationReport {
fn duplicate_claims(self, claims: &Option<OneOrMany<StandardsClaim>>) -> Self {
self.merge(
"",
claims.as_ref().map_or_else(ValidationReport::new, |claims| {
claims
.iter()
.enumerate()
.fold(
(BTreeSet::new(), ValidationReport::new()),
|(mut identities, mut report), (index, claim)| {
let identity = (
claim.standard.authority.clone(),
claim.standard.identifier.clone(),
claim.standard.version.clone(),
claim.relationship.clone(),
);
if !identities.insert(identity) {
report.add(
format!("standards_claims[{index}]"),
ValidationError::new("duplicate_standard_claim").with_message("Duplicate standards claim"),
);
}
(identities, report)
},
)
.1
}),
)
}
fn invalid_recognition_scopes(self, recognitions: &Option<OneOrMany<Recognition>>, accepts: fn(&RecognitionProgram) -> bool) -> Self {
self.merge(
"",
recognitions.as_ref().map_or_else(ValidationReport::new, |recognitions| {
recognitions
.iter()
.enumerate()
.filter(|(_, recognition)| !accepts(&recognition.program))
.fold(ValidationReport::new(), |mut report, (index, _)| {
report.add(
format!("recognitions[{index}].program"),
ValidationError::new("recognition_scope").with_message("Recognition program is not valid for this infrastructure type"),
);
report
})
}),
)
}
}