use std::sync::Arc;
use serde::Deserialize;
use serde::de::DeserializeOwned;
use super::error::AdminError;
use super::service::DesiredStateEdit;
use crate::desired_state::{
AliasTarget, BlobKind, BlobRef, BudgetBound, BudgetPolicy, CatalogOffering, Checksum,
ConcurrencyPolicy, DesiredState, DisplayName, InvalidDisplayName, InvalidId, InvalidSlug,
InvalidUuid7, ModelAliasBody, ModelEnablementBody, ModelLifecycle, ModelOwner, ObservedPrice,
OfferingId, PolicyBody, PolicyEpoch, PolicyScope, ProjectBody, ProjectId, ProviderBody,
ProviderCredentialBody, ResourceBody, ResourceId, ResourceKind, ResourceRef, ResourceScope,
ResourceVersion, ResourceVersionNumber, RevocationPolicy, SecretId, SecretLifecycle,
SecretOwner, SecretRef, SecretVersion, Slug, Surface, TenantBody, TenantId, TenantLifecycle,
ValidationError, WireFamily,
};
pub struct ResourcePlan {
pub scope: ResourceScope,
pub edit: Arc<dyn DesiredStateEdit>,
pub retires: bool,
}
impl ResourcePlan {
fn new<E>(scope: ResourceScope, edit: E) -> Self
where
E: Fn(&mut DesiredState) -> Result<(), ValidationError> + Send + Sync + 'static,
{
Self {
scope,
edit: Arc::new(edit),
retires: false,
}
}
#[must_use]
fn retiring(mut self, retires: bool) -> Self {
self.retires = retires;
self
}
}
pub trait AdminResourceRequest: DeserializeOwned + Send + Sync + 'static {
const SCHEMA: &'static str;
const SURFACE: Surface;
fn plan(self) -> Result<ResourcePlan, AdminError>;
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MutationEnvelope<R> {
pub summary: String,
#[serde(default)]
pub mutation: MutationKindInput,
pub resource: R,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MutationKindInput {
Create,
#[default]
Update,
Delete,
Rotate,
}
impl MutationKindInput {
pub const fn kind(self) -> crate::desired_state::MutationKind {
use crate::desired_state::MutationKind;
match self {
Self::Create => MutationKind::Create,
Self::Update => MutationKind::Update,
Self::Delete => MutationKind::Delete,
Self::Rotate => MutationKind::Rotate,
}
}
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RollbackRequest {
pub summary: String,
pub revision: String,
#[serde(default)]
pub tenant: Option<String>,
#[serde(default)]
pub project: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TenantRequest {
pub tenant: String,
pub slug: String,
pub display_name: String,
#[serde(default)]
pub lifecycle: Option<String>,
}
impl AdminResourceRequest for TenantRequest {
const SCHEMA: &'static str = "tenant";
const SURFACE: Surface = Surface::Tenant;
fn plan(self) -> Result<ResourcePlan, AdminError> {
let tenant = tenant_id::<Self>(&self.tenant)?;
let slug = slug::<Self>(&self.slug)?;
let display_name = display_name::<Self>(&self.display_name)?;
let lifecycle = match self.lifecycle.as_deref() {
None => TenantLifecycle::Active,
Some(text) => TenantLifecycle::parse(text).ok_or_else(|| {
unknown::<Self>(
"lifecycle",
TenantLifecycle::ALL.iter().map(|state| state.as_str()),
)
})?,
};
Ok(ResourcePlan::new(
ResourceScope::Deployment,
move |state: &mut DesiredState| {
let body = TenantBody::new(tenant, display_name.clone()).in_lifecycle(lifecycle);
let version = next_version(state, ResourceKind::Tenant, body.resource_id());
publish(state, body.version_at(slug.clone(), version))?;
Ok(())
},
)
.retiring(lifecycle == TenantLifecycle::Deleted))
}
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProjectRequest {
pub project: String,
pub tenant: String,
pub slug: String,
pub display_name: String,
}
impl AdminResourceRequest for ProjectRequest {
const SCHEMA: &'static str = "project";
const SURFACE: Surface = Surface::Project;
fn plan(self) -> Result<ResourcePlan, AdminError> {
let project = project_id::<Self>(&self.project)?;
let tenant = tenant_id::<Self>(&self.tenant)?;
let slug = slug::<Self>(&self.slug)?;
let display_name = display_name::<Self>(&self.display_name)?;
let body = ProjectBody::new(project, tenant, display_name);
Ok(ResourcePlan::new(
body.scope(),
move |state: &mut DesiredState| {
let version = next_version(state, ResourceKind::Project, body.resource_id());
publish(state, body.version_at(slug.clone(), version))?;
Ok(())
},
))
}
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProviderRequest {
pub provider: String,
pub tenant: String,
#[serde(default)]
pub project: Option<String>,
pub slug: String,
pub display_name: String,
pub wire_family: String,
pub endpoint: String,
}
impl AdminResourceRequest for ProviderRequest {
const SCHEMA: &'static str = "provider";
const SURFACE: Surface = Surface::Provider;
fn plan(self) -> Result<ResourcePlan, AdminError> {
let provider = resource_id::<Self>("provider", &self.provider)?;
let tenant = tenant_id::<Self>(&self.tenant)?;
let project = self
.project
.as_deref()
.map(project_id::<Self>)
.transpose()?;
let slug = slug::<Self>(&self.slug)?;
let display_name = display_name::<Self>(&self.display_name)?;
let wire_family = wire_family::<Self>(&self.wire_family)?;
let body = ProviderBody::for_tenant(
provider,
tenant,
display_name,
wire_family,
self.endpoint.clone(),
);
let body = match project {
Some(project) => body.owned_by_project(project),
None => body,
};
Ok(ResourcePlan::new(
body.scope(),
move |state: &mut DesiredState| {
let version = next_version(state, ResourceKind::Provider, body.resource_id());
publish(state, body.version_at(slug.clone(), version))?;
Ok(())
},
))
}
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CredentialRequest {
pub credential: String,
pub tenant: String,
#[serde(default)]
pub project: Option<String>,
pub provider: String,
pub slug: String,
pub display_name: String,
pub secret: String,
#[serde(default)]
pub secret_version: Option<u64>,
#[serde(default)]
pub lifecycle: Option<String>,
#[serde(default)]
pub rotate: bool,
}
impl AdminResourceRequest for CredentialRequest {
const SCHEMA: &'static str = "provider-credential";
const SURFACE: Surface = Surface::Credential;
fn plan(self) -> Result<ResourcePlan, AdminError> {
let credential = resource_id::<Self>("credential", &self.credential)?;
let tenant = tenant_id::<Self>(&self.tenant)?;
let project = self
.project
.as_deref()
.map(project_id::<Self>)
.transpose()?;
let provider = resource_id::<Self>("provider", &self.provider)?;
let slug = slug::<Self>(&self.slug)?;
let display_name = display_name::<Self>(&self.display_name)?;
let secret = SecretId::parse(&self.secret).map_err(|error| match error {
InvalidId::Prefix { .. } => malformed::<Self>(
"secret",
&format!("is not a `{}`-prefixed secret id", SecretId::PREFIX),
),
InvalidId::Uuid(uuid) => malformed::<Self>(
"secret",
&format!("names a secret id whose uuid {}", uuid_detail(&uuid)),
),
})?;
let version = match self.secret_version {
None => None,
Some(version) => Some(
SecretVersion::new(version)
.ok_or_else(|| malformed::<Self>("secret_version", "versions start at 1"))?,
),
};
let lifecycle = match self.lifecycle.as_deref() {
None => None,
Some(text) => Some(SecretLifecycle::parse(text).ok_or_else(|| {
unknown::<Self>(
"lifecycle",
SecretLifecycle::ALL.iter().map(|state| state.as_str()),
)
})?),
};
let owner = match project {
Some(project) => SecretOwner::project(tenant, project),
None => SecretOwner::tenant(tenant),
};
let rotate = self.rotate;
Ok(
ResourcePlan::new(owner.scope(), move |state: &mut DesiredState| {
let reference = state.version_of(ResourceKind::ProviderCredential, credential);
let previous = match reference {
Some(resource) => Some(ProviderCredentialBody::read(resource)?),
None => None,
};
let staged = |material: SecretRef| {
ProviderCredentialBody::staged(
credential,
owner,
provider,
display_name.clone(),
material,
)
};
let in_force = previous.as_ref().map(ProviderCredentialBody::secret);
let authored = match (version, in_force) {
(Some(version), _) => SecretRef::new(secret, version),
(None, Some(held)) if held.secret == secret => held,
(None, _) => SecretRef::first(secret),
};
let body = match (previous, rotate) {
(Some(previous), true) => previous.reauthored(staged(authored)).rotated(),
(Some(previous), false) => previous.reauthored(staged(authored)),
(None, _) => staged(authored),
};
let body = match lifecycle {
Some(lifecycle) => body.transitioned(lifecycle)?,
None => body,
};
let next = next_version(state, ResourceKind::ProviderCredential, credential);
publish(state, body.version_at(slug.clone(), next))?;
Ok(())
})
.retiring(matches!(
lifecycle,
Some(SecretLifecycle::Revoked | SecretLifecycle::Tombstoned)
)),
)
}
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CatalogRequest {
pub catalog: String,
pub slug: String,
pub digest: String,
pub size_bytes: u64,
}
impl AdminResourceRequest for CatalogRequest {
const SCHEMA: &'static str = "catalog-snapshot";
const SURFACE: Surface = Surface::Model;
fn plan(self) -> Result<ResourcePlan, AdminError> {
let catalog = resource_id::<Self>("catalog", &self.catalog)?;
let slug = slug::<Self>(&self.slug)?;
let digest = checksum::<Self>("digest", &self.digest)?;
let blob = BlobRef {
kind: BlobKind::CatalogSnapshot,
digest,
size_bytes: self.size_bytes,
};
Ok(ResourcePlan::new(
ResourceScope::Deployment,
move |state: &mut DesiredState| {
refuse_withdrawing_a_pinned_snapshot(state, catalog, digest)?;
let version = next_version(state, ResourceKind::CatalogModel, catalog);
state.declare_blob(blob);
publish(
state,
ResourceVersion::new(
ResourceRef::new(ResourceKind::CatalogModel, catalog, version),
ResourceScope::Deployment,
slug.clone(),
ResourceBody::Blob(blob),
),
)?;
state.retain_referenced_blobs();
Ok(())
},
))
}
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ModelRequest {
pub enablement: String,
pub tenant: String,
#[serde(default)]
pub project: Option<String>,
pub slug: String,
pub offering: String,
pub catalog: String,
pub snapshot: String,
pub wire_family: String,
#[serde(default)]
pub state: Option<String>,
#[serde(default)]
pub observed_input_micros_per_million: Option<u64>,
#[serde(default)]
pub observed_output_micros_per_million: Option<u64>,
}
impl AdminResourceRequest for ModelRequest {
const SCHEMA: &'static str = "model-enablement";
const SURFACE: Surface = Surface::Model;
fn plan(self) -> Result<ResourcePlan, AdminError> {
let enablement = resource_id::<Self>("enablement", &self.enablement)?;
let tenant = tenant_id::<Self>(&self.tenant)?;
let project = self
.project
.as_deref()
.map(project_id::<Self>)
.transpose()?;
let slug = slug::<Self>(&self.slug)?;
let offering = offering::<Self>(&self.offering)?;
let catalog = resource_id::<Self>("catalog", &self.catalog)?;
let snapshot = checksum::<Self>("snapshot", &self.snapshot)?;
let wire_family = wire_family::<Self>(&self.wire_family)?;
let state = match self.state.as_deref() {
None => ModelLifecycle::Enabled,
Some(text) => ModelLifecycle::parse(text).ok_or_else(|| {
unknown::<Self>(
"state",
ModelLifecycle::ALL.iter().map(|state| state.as_str()),
)
})?,
};
let observed = match (
self.observed_input_micros_per_million,
self.observed_output_micros_per_million,
) {
(Some(input), Some(output)) => Some(ObservedPrice::new(input, output)),
(None, None) => None,
_ => {
return Err(malformed::<Self>(
"observed_input_micros_per_million",
"an observed price needs both an input and an output rate",
));
}
};
let owner = match project {
Some(project) => ModelOwner::project(tenant, project),
None => ModelOwner::tenant(tenant),
};
let body = ModelEnablementBody::new(
enablement,
owner,
CatalogOffering::new(offering, snapshot),
wire_family,
)
.transitioned(state);
let body = match observed {
Some(observed) => body.observing(observed),
None => body,
};
Ok(
ResourcePlan::new(owner.scope(), move |state: &mut DesiredState| {
let pinned = state
.version_of(ResourceKind::CatalogModel, catalog)
.map_or(
ResourceRef::new(
ResourceKind::CatalogModel,
catalog,
ResourceVersionNumber::FIRST,
),
|resource| resource.reference,
);
let version = next_version(state, ResourceKind::ModelEnablement, enablement);
publish(state, body.version_at(slug.clone(), version, pinned))?;
Ok(())
})
.retiring(state == ModelLifecycle::Disabled),
)
}
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AliasTargetRequest {
pub enablement: String,
#[serde(default)]
pub version: Option<u64>,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AliasRequest {
pub alias: String,
pub tenant: String,
pub project: String,
pub slug: String,
pub wire_family: String,
#[serde(default)]
pub state: Option<String>,
pub targets: Vec<AliasTargetRequest>,
}
impl AdminResourceRequest for AliasRequest {
const SCHEMA: &'static str = "model-alias";
const SURFACE: Surface = Surface::Alias;
fn plan(self) -> Result<ResourcePlan, AdminError> {
let alias = resource_id::<Self>("alias", &self.alias)?;
let tenant = tenant_id::<Self>(&self.tenant)?;
let project = project_id::<Self>(&self.project)?;
let slug = slug::<Self>(&self.slug)?;
let wire_family = wire_family::<Self>(&self.wire_family)?;
let lifecycle = match self.state.as_deref() {
None => ModelLifecycle::Enabled,
Some(text) => ModelLifecycle::parse(text).ok_or_else(|| {
unknown::<Self>(
"state",
ModelLifecycle::ALL.iter().map(|state| state.as_str()),
)
})?,
};
let mut targets = Vec::with_capacity(self.targets.len());
for target in &self.targets {
let enablement = resource_id::<Self>("targets.enablement", &target.enablement)?;
let version = match target.version {
None => None,
Some(version) => {
Some(ResourceVersionNumber::new(version).ok_or_else(|| {
malformed::<Self>("targets.version", "versions start at 1")
})?)
}
};
targets.push((enablement, version));
}
Ok(ResourcePlan::new(
ResourceScope::Project { tenant, project },
move |state: &mut DesiredState| {
let resolved = targets
.iter()
.map(|(enablement, version)| {
let version = version.unwrap_or_else(|| {
state
.version_of(ResourceKind::ModelEnablement, *enablement)
.map_or(ResourceVersionNumber::FIRST, |held| held.reference.version)
});
AliasTarget::new(*enablement, version)
})
.collect::<Vec<_>>();
let body = ModelAliasBody::new(alias, tenant, project, wire_family, resolved)
.transitioned(lifecycle);
let version = next_version(state, ResourceKind::Alias, alias);
publish(state, body.version_at(slug.clone(), version))?;
Ok(())
},
)
.retiring(lifecycle == ModelLifecycle::Disabled))
}
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PolicyRequest {
pub tenant: String,
#[serde(default)]
pub project: Option<String>,
pub slug: String,
pub epoch: u64,
pub subject_limit_microdollars: u64,
#[serde(default)]
pub namespace_limit_microdollars: Option<u64>,
pub reservation_ttl_seconds: u64,
pub max_in_flight_per_subject: u64,
pub lease_ttl_seconds: u64,
#[serde(default)]
pub minimum_token_epoch: u64,
}
impl AdminResourceRequest for PolicyRequest {
const SCHEMA: &'static str = "policy";
const SURFACE: Surface = Surface::Policy;
fn plan(self) -> Result<ResourcePlan, AdminError> {
let tenant = tenant_id::<Self>(&self.tenant)?;
let project = self
.project
.as_deref()
.map(project_id::<Self>)
.transpose()?;
let slug = slug::<Self>(&self.slug)?;
let epoch = PolicyEpoch::new(self.epoch)
.map_err(|error| malformed::<Self>("epoch", &error.to_string()))?;
let budget = BudgetPolicy::new(
self.subject_limit_microdollars,
self.namespace_limit_microdollars,
self.reservation_ttl_seconds,
)
.map_err(|error| {
let field = match BudgetPolicy::unmet_bound(
self.subject_limit_microdollars,
self.namespace_limit_microdollars,
) {
BudgetBound::SubjectLimit => "subject_limit_microdollars",
BudgetBound::NamespaceLimit => "namespace_limit_microdollars",
BudgetBound::ReservationTtl => "reservation_ttl_seconds",
};
malformed::<Self>(field, &error.to_string())
})?;
let concurrency =
ConcurrencyPolicy::new(self.max_in_flight_per_subject, self.lease_ttl_seconds)
.map_err(|error| {
let field = ConcurrencyPolicy::unmet_bound(self.max_in_flight_per_subject)
.document_field();
malformed::<Self>(field, &error.to_string())
})?;
let revocation = RevocationPolicy::new(self.minimum_token_epoch);
let scope = match project {
Some(project) => PolicyScope::Project { tenant, project },
None => PolicyScope::Tenant(tenant),
};
let body = PolicyBody::new(scope, epoch, budget, concurrency, revocation);
Ok(ResourcePlan::new(
scope.resource_scope(),
move |state: &mut DesiredState| {
let version = next_version(state, ResourceKind::Policy, body.resource_id());
publish(state, body.version_at(slug.clone(), version))?;
Ok(())
},
))
}
}
fn refuse_withdrawing_a_pinned_snapshot(
state: &DesiredState,
catalog: ResourceId,
digest: Checksum,
) -> Result<(), ValidationError> {
let Some(held) = state.version_of(ResourceKind::CatalogModel, catalog) else {
return Ok(());
};
let withdrawn = match held.body.blob() {
Some(blob) if blob.digest != digest => blob.digest,
_ => return Ok(()),
};
let catalog = held.reference;
for resource in state.resources() {
if resource.reference.kind != ResourceKind::ModelEnablement {
continue;
}
let pinned = ModelEnablementBody::read(resource)
.map(|body| body.offering().is_pinned_to(withdrawn))
.unwrap_or(false);
if pinned {
return Err(ValidationError::PinnedSnapshotWithdrawn {
catalog,
enablement: resource.reference,
digest: withdrawn,
});
}
}
Ok(())
}
fn publish(state: &mut DesiredState, resource: ResourceVersion) -> Result<(), ValidationError> {
let current = resource.reference;
let superseded = state
.version_of(current.kind, current.id)
.map(|held| held.reference);
state.supersede(resource)?;
match superseded {
Some(superseded) => restack(state, superseded, current),
None => Ok(()),
}
}
fn restack(
state: &mut DesiredState,
superseded: ResourceRef,
current: ResourceRef,
) -> Result<(), ValidationError> {
let dependents: Vec<ResourceVersion> = state
.resources()
.filter(|resource| resource.depends_on.contains(&superseded))
.cloned()
.collect();
for dependent in dependents {
let previous = dependent.reference;
let version = previous.version.next();
let advanced = if previous.kind == ResourceKind::Alias {
let body = ModelAliasBody::read(&dependent)?;
let targets = body.targets().iter().map(|target| {
if target.enablement == superseded.id && target.version == superseded.version {
AliasTarget::new(target.enablement, current.version)
} else {
*target
}
});
body.clone()
.retargeted(targets.collect::<Vec<_>>())
.version_at(dependent.slug.clone(), version)
} else {
let mut depends_on = dependent.depends_on.clone();
depends_on.remove(&superseded);
depends_on.insert(current);
ResourceVersion::new(
ResourceRef::new(previous.kind, previous.id, version),
dependent.scope.clone(),
dependent.slug.clone(),
dependent.body.clone(),
)
.depending_on(depends_on)
};
let advanced_reference = advanced.reference;
state.supersede(advanced)?;
restack(state, previous, advanced_reference)?;
}
Ok(())
}
fn next_version(state: &DesiredState, kind: ResourceKind, id: ResourceId) -> ResourceVersionNumber {
state
.version_of(kind, id)
.map_or(ResourceVersionNumber::FIRST, |resource| {
resource.reference.version.next()
})
}
fn resource_id<R: AdminResourceRequest>(
field: &'static str,
text: &str,
) -> Result<ResourceId, AdminError> {
ResourceId::parse(text).map_err(|error| malformed_id::<R>(field, ResourceId::PREFIX, error))
}
fn tenant_id<R: AdminResourceRequest>(text: &str) -> Result<TenantId, AdminError> {
TenantId::parse(text).map_err(|error| malformed_id::<R>("tenant", TenantId::PREFIX, error))
}
fn project_id<R: AdminResourceRequest>(text: &str) -> Result<ProjectId, AdminError> {
ProjectId::parse(text).map_err(|error| malformed_id::<R>("project", ProjectId::PREFIX, error))
}
fn slug<R: AdminResourceRequest>(text: &str) -> Result<Slug, AdminError> {
Slug::parse(text).map_err(|error| {
let detail = match error {
InvalidSlug::Empty => "must not be empty".to_owned(),
InvalidSlug::TooLong { max, .. } => format!("is over the {max}-character limit"),
InvalidSlug::Character { .. } => {
"contains a character outside ASCII letters, digits, `-`, and `_`".to_owned()
}
InvalidSlug::Boundary { .. } => "must start and end with a letter or digit".to_owned(),
InvalidSlug::IdLike { .. } => "looks like an id; ids are not names".to_owned(),
};
malformed::<R>("slug", &detail)
})
}
fn display_name<R: AdminResourceRequest>(text: &str) -> Result<DisplayName, AdminError> {
DisplayName::parse(text).map_err(|error| {
let detail = match error {
InvalidDisplayName::Empty => "must not be empty".to_owned(),
InvalidDisplayName::TooLong { max, .. } => {
format!("is over the {max}-character limit")
}
InvalidDisplayName::ControlCharacter { .. } => {
"contains a control character".to_owned()
}
InvalidDisplayName::ByteOrderMark => "contains a byte-order mark".to_owned(),
InvalidDisplayName::Untrimmed => "may not begin or end with whitespace".to_owned(),
};
malformed::<R>("display_name", &detail)
})
}
fn malformed_id<R: AdminResourceRequest>(
field: &'static str,
prefix: &'static str,
error: InvalidId,
) -> AdminError {
let detail = match error {
InvalidId::Prefix { .. } => format!("is not a `{prefix}`-prefixed id"),
InvalidId::Uuid(uuid) => format!("has a uuid that {}", uuid_detail(&uuid)),
};
malformed::<R>(field, &detail)
}
pub(super) fn uuid_detail(error: &InvalidUuid7) -> String {
match error {
InvalidUuid7::Shape(_) => "is not a hyphenated 8-4-4-4-12 uuid".to_owned(),
InvalidUuid7::Digit(_) => {
"contains a character that is not a lowercase hex digit".to_owned()
}
InvalidUuid7::Version { version } => {
format!("is version {version}, but only version 7 is accepted")
}
InvalidUuid7::Variant { variant } => {
format!("has variant bits {variant:#04b}, but only the RFC 9562 variant is accepted")
}
InvalidUuid7::Timestamp { .. } | InvalidUuid7::Sequence { .. } => {
"is not a version 7 uuid".to_owned()
}
}
}
fn offering<R: AdminResourceRequest>(text: &str) -> Result<OfferingId, AdminError> {
OfferingId::parse(text).map_err(|error| malformed::<R>("offering", &error.to_string()))
}
fn checksum<R: AdminResourceRequest>(
field: &'static str,
text: &str,
) -> Result<Checksum, AdminError> {
Checksum::parse(text).map_err(|error| malformed::<R>(field, &error.to_string()))
}
fn wire_family<R: AdminResourceRequest>(text: &str) -> Result<WireFamily, AdminError> {
WireFamily::parse(text).ok_or_else(|| {
unknown::<R>(
"wire_family",
WireFamily::ALL.iter().map(|family| family.as_str()),
)
})
}
fn malformed<R: AdminResourceRequest>(field: &'static str, detail: &str) -> AdminError {
AdminError::RequestInvalid {
schema: R::SCHEMA,
detail: format!("`{field}`: {detail}"),
}
}
fn unknown<R: AdminResourceRequest>(
field: &'static str,
accepted: impl IntoIterator<Item = &'static str>,
) -> AdminError {
let accepted = accepted
.into_iter()
.map(|value| format!("`{value}`"))
.collect::<Vec<_>>()
.join(", ");
AdminError::RequestInvalid {
schema: R::SCHEMA,
detail: format!("`{field}`: is not a value this build knows; it accepts {accepted}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::desired_state::fixtures;
const PASTED_MATERIAL: &str = "sk-axond-admin-sentinel-51H9xNEVERLOGME";
fn credential() -> CredentialRequest {
CredentialRequest {
credential: fixtures::resource_id(11).to_string(),
tenant: fixtures::tenant_id(1).to_string(),
project: None,
provider: fixtures::resource_id(10).to_string(),
slug: "openai-primary".to_owned(),
display_name: "OpenAI primary".to_owned(),
secret: fixtures::secret_ref(3).secret.to_string(),
secret_version: Some(1),
lifecycle: Some("active".to_owned()),
rotate: false,
}
}
fn refusal(request: CredentialRequest) -> String {
match request.plan() {
Ok(_) => panic!("the malformed credential document was accepted"),
Err(error) => error
.operator_detail()
.expect("a request refusal has operator detail")
.to_owned(),
}
}
#[test]
fn secret_reference_errors_distinguish_prefix_from_malformed_uuid_without_echoing() {
let mut wrong_prefix = credential();
wrong_prefix.secret = PASTED_MATERIAL.to_owned();
let prefix_detail = refusal(wrong_prefix);
assert_eq!(
prefix_detail,
"`secret`: is not a `sct_`-prefixed secret id"
);
assert!(!prefix_detail.contains(PASTED_MATERIAL));
const MALFORMED_REFERENCE: &str = "sct_not-a-hyphenated-uuid";
let mut malformed_uuid = credential();
malformed_uuid.secret = MALFORMED_REFERENCE.to_owned();
let detail = refusal(malformed_uuid);
assert_eq!(
detail,
"`secret`: names a secret id whose uuid is not a hyphenated 8-4-4-4-12 uuid"
);
assert!(!detail.contains(MALFORMED_REFERENCE));
}
#[test]
fn a_malformed_uuid_is_refused_for_the_reason_it_failed() {
const GOOD: &str = "0189f8c1-2a3b-7c4d-8e5f-6a7b8c9d0e1f";
assert!(
SecretId::parse(&format!("{}{GOOD}", SecretId::PREFIX)).is_ok(),
"the case base must be a uuid the parser accepts"
);
let version4 = GOOD.replacen("-7c4d-", "-4c4d-", 1);
let cases = [
("0189f8c1", "is not a hyphenated 8-4-4-4-12 uuid"),
(
&GOOD.replace('-', "_") as &str,
"is not a hyphenated 8-4-4-4-12 uuid",
),
(
&GOOD.to_uppercase(),
"contains a character that is not a lowercase hex digit",
),
(&version4, "is version 4, but only version 7 is accepted"),
];
for (uuid, reason) in cases {
let mut request = credential();
request.secret = format!("{}{uuid}", SecretId::PREFIX);
let detail = refusal(request);
assert_eq!(
detail,
format!("`secret`: names a secret id whose uuid {reason}"),
);
assert!(!detail.contains(uuid), "{uuid} was echoed: {detail}");
}
}
#[test]
fn identifier_fields_distinguish_a_wrong_prefix_from_a_malformed_uuid() {
let mut wrong_prefix = credential();
wrong_prefix.provider = format!("prv_{}", fixtures::resource_id(10).uuid());
assert_eq!(
refusal(wrong_prefix),
"`provider`: is not a `res_`-prefixed id"
);
let mut malformed_uuid = credential();
malformed_uuid.tenant = format!("{}not-a-uuid", TenantId::PREFIX);
assert_eq!(
refusal(malformed_uuid),
"`tenant`: has a uuid that is not a hyphenated 8-4-4-4-12 uuid"
);
}
#[test]
fn malformed_document_fields_do_not_echo_pasted_material() {
let cases = [
("credential", format!("{PASTED_MATERIAL}!")),
("tenant", format!("{PASTED_MATERIAL}!")),
("project", format!("{PASTED_MATERIAL}!")),
("provider", format!("{PASTED_MATERIAL}!")),
("slug", format!("{PASTED_MATERIAL}!")),
("display_name", format!("{PASTED_MATERIAL}\n")),
("lifecycle", PASTED_MATERIAL.to_owned()),
];
for (field, value) in cases {
let mut request = credential();
match field {
"credential" => request.credential = value.clone(),
"tenant" => request.tenant = value.clone(),
"project" => request.project = Some(value.clone()),
"provider" => request.provider = value.clone(),
"slug" => request.slug = value.clone(),
"display_name" => request.display_name = value.clone(),
"lifecycle" => request.lifecycle = Some(value.clone()),
_ => unreachable!("the test case names its field"),
}
let detail = refusal(request);
assert!(
!detail.contains(&value),
"{field} validation echoed pasted material: {detail}"
);
}
}
fn catalog() -> CatalogRequest {
CatalogRequest {
catalog: fixtures::resource_id(12).to_string(),
slug: "models-dev".to_owned(),
digest: format!("sha256:{}", "a".repeat(64)),
size_bytes: 4_096,
}
}
fn model() -> ModelRequest {
ModelRequest {
enablement: fixtures::resource_id(13).to_string(),
tenant: fixtures::tenant_id(1).to_string(),
project: None,
slug: "gpt-4o".to_owned(),
offering: format!("off_{}", "b".repeat(64)),
catalog: fixtures::resource_id(12).to_string(),
snapshot: format!("sha256:{}", "a".repeat(64)),
wire_family: "openai-chat".to_owned(),
state: None,
observed_input_micros_per_million: None,
observed_output_micros_per_million: None,
}
}
fn detail_of<R: AdminResourceRequest>(request: R) -> String {
match request.plan() {
Ok(_) => panic!("the malformed document was accepted"),
Err(error) => error
.operator_detail()
.expect("a request refusal has operator detail")
.to_owned(),
}
}
#[test]
fn digest_and_offering_refusals_name_the_form_and_not_the_text() {
let mut pasted_digest = catalog();
pasted_digest.digest = PASTED_MATERIAL.to_owned();
let detail = detail_of(pasted_digest);
assert_eq!(detail, "`digest`: is not prefixed `sha256:`");
assert!(!detail.contains(PASTED_MATERIAL));
let mut short_digest = catalog();
short_digest.digest = format!("sha256:{PASTED_MATERIAL}");
let detail = detail_of(short_digest);
assert_eq!(detail, "`digest`: does not carry 64 lowercase hex digits");
assert!(!detail.contains(PASTED_MATERIAL));
let mut pasted_offering = model();
pasted_offering.offering = PASTED_MATERIAL.to_owned();
let detail = detail_of(pasted_offering);
assert_eq!(detail, "`offering`: is not prefixed `off_`");
assert!(!detail.contains(PASTED_MATERIAL));
let mut short_offering = model();
short_offering.offering = format!("off_{PASTED_MATERIAL}");
let detail = detail_of(short_offering);
assert_eq!(detail, "`offering`: does not carry 64 lowercase hex digits");
assert!(!detail.contains(PASTED_MATERIAL));
let mut pasted_snapshot = model();
pasted_snapshot.snapshot = PASTED_MATERIAL.to_owned();
let detail = detail_of(pasted_snapshot);
assert_eq!(detail, "`snapshot`: is not prefixed `sha256:`");
assert!(!detail.contains(PASTED_MATERIAL));
}
#[test]
fn a_closed_set_field_is_refused_with_what_this_build_accepts() {
let mut unknown_family = model();
unknown_family.wire_family = PASTED_MATERIAL.to_owned();
let detail = detail_of(unknown_family);
assert_eq!(
detail,
"`wire_family`: is not a value this build knows; \
it accepts `openai-chat`, `anthropic-messages`"
);
assert!(!detail.contains(PASTED_MATERIAL));
let mut unknown_state = model();
unknown_state.state = Some(PASTED_MATERIAL.to_owned());
let detail = detail_of(unknown_state);
assert_eq!(
detail,
"`state`: is not a value this build knows; it accepts `enabled`, `disabled`"
);
assert!(!detail.contains(PASTED_MATERIAL));
let mut unknown_lifecycle = credential();
unknown_lifecycle.lifecycle = Some(PASTED_MATERIAL.to_owned());
let detail = refusal(unknown_lifecycle);
assert!(
detail.starts_with("`lifecycle`: is not a value this build knows; it accepts `"),
"{detail}"
);
assert!(!detail.contains(PASTED_MATERIAL));
}
}