use super::{
canonical_fingerprint, invalid_plan, is_canonical_sha256, ContractVersion,
OperationProviderDescriptor, OperationResourceEstimate, ProviderId,
ProviderWorkspaceRequirement, ProviderWorkspaceReusePolicy, ProviderWorkspaceScope, Serialize,
VNextError,
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ProviderResourcePlan {
pub(super) provider_id: ProviderId,
pub(super) estimator_id: String,
pub(super) estimator_version: ContractVersion,
pub(super) estimator_implementation_fingerprint: String,
pub(super) estimator_input_fingerprint: String,
pub(super) estimate_fingerprint: String,
pub(super) value_alignment_bytes: u64,
pub(super) scratch: Option<ProviderWorkspaceRequirement>,
pub(super) binding: Option<ProviderWorkspaceRequirement>,
pub(super) persistent: Option<ProviderWorkspaceRequirement>,
}
impl ProviderResourcePlan {
#[allow(clippy::too_many_arguments)]
pub(super) fn from_provider_output(
descriptor: &OperationProviderDescriptor,
estimator_input_fingerprint: &str,
estimate: OperationResourceEstimate,
) -> Result<Self, VNextError> {
if estimate.estimator_id() != descriptor.resource_estimator_id()
|| estimate.estimator_version() != descriptor.resource_estimator_version()
|| estimate.estimator_implementation_fingerprint()
!= descriptor.resource_estimator_implementation_fingerprint()
|| estimate.claimed_input_fingerprint() != estimator_input_fingerprint
{
return Err(invalid_plan(
"provider raw resource estimate identity or input claim differs from the selected registered implementation",
));
}
let mut plan = Self {
provider_id: descriptor.provider_id().clone(),
estimator_id: descriptor.resource_estimator_id().to_owned(),
estimator_version: descriptor.resource_estimator_version(),
estimator_implementation_fingerprint: descriptor
.resource_estimator_implementation_fingerprint()
.to_owned(),
estimator_input_fingerprint: estimator_input_fingerprint.to_owned(),
estimate_fingerprint: String::new(),
value_alignment_bytes: estimate.value_alignment_bytes(),
scratch: estimate.scratch().cloned(),
binding: estimate.binding().cloned(),
persistent: estimate.persistent().cloned(),
};
plan.validate_fields()?;
plan.estimate_fingerprint = plan.compute_estimate_fingerprint()?;
plan.validate_shape()?;
Ok(plan)
}
pub(super) fn validate_fields(&self) -> Result<(), VNextError> {
if self.estimator_id.is_empty()
|| self.estimator_id.len() > 160
|| !self.estimator_id.bytes().all(|byte| {
byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-' | b':' | b'/')
})
|| self.estimator_version.major == 0
|| !is_canonical_sha256(&self.estimator_implementation_fingerprint)
|| !is_canonical_sha256(&self.estimator_input_fingerprint)
|| self.value_alignment_bytes == 0
|| !self.value_alignment_bytes.is_power_of_two()
|| self.scratch.as_ref().is_some_and(|workspace| {
workspace.scope != ProviderWorkspaceScope::Invocation
|| workspace.reuse_policy == ProviderWorkspaceReusePolicy::Preserve
|| ProviderWorkspaceRequirement::from_formula(
workspace.size_formula.clone(),
workspace.alignment_bytes,
workspace.scope,
workspace.reuse_policy,
workspace.storage.clone(),
)
.is_err()
})
|| self.binding.as_ref().is_some_and(|workspace| {
workspace.scope != ProviderWorkspaceScope::Invocation
|| workspace.reuse_policy != ProviderWorkspaceReusePolicy::OverwriteBeforeRead
|| ProviderWorkspaceRequirement::from_formula(
workspace.size_formula.clone(),
workspace.alignment_bytes,
workspace.scope,
workspace.reuse_policy,
workspace.storage.clone(),
)
.is_err()
})
|| self.persistent.as_ref().is_some_and(|workspace| {
workspace.scope == ProviderWorkspaceScope::Invocation
|| workspace.reuse_policy != ProviderWorkspaceReusePolicy::Preserve
|| ProviderWorkspaceRequirement::from_formula(
workspace.size_formula.clone(),
workspace.alignment_bytes,
workspace.scope,
workspace.reuse_policy,
workspace.storage.clone(),
)
.is_err()
})
{
return Err(invalid_plan(
"provider resource estimate identity, alignment, scope, or reuse policy is invalid",
));
}
Ok(())
}
pub(super) fn validate_shape(&self) -> Result<(), VNextError> {
self.validate_fields()?;
if !is_canonical_sha256(&self.estimate_fingerprint)
|| self.estimate_fingerprint != self.compute_estimate_fingerprint()?
{
return Err(invalid_plan(
"provider resource estimate fingerprint does not match its typed fields",
));
}
Ok(())
}
pub(super) fn compute_estimate_fingerprint(&self) -> Result<String, VNextError> {
canonical_fingerprint(
&ProviderEstimateFingerprintMaterial {
provider_id: &self.provider_id,
estimator_id: &self.estimator_id,
estimator_version: self.estimator_version,
estimator_implementation_fingerprint: &self.estimator_implementation_fingerprint,
estimator_input_fingerprint: &self.estimator_input_fingerprint,
value_alignment_bytes: self.value_alignment_bytes,
scratch: &self.scratch,
binding: &self.binding,
persistent: &self.persistent,
},
"fingerprint provider resource estimate",
)
}
pub fn provider_id(&self) -> &ProviderId {
&self.provider_id
}
pub fn estimator_id(&self) -> &str {
&self.estimator_id
}
pub const fn estimator_version(&self) -> ContractVersion {
self.estimator_version
}
pub fn estimator_implementation_fingerprint(&self) -> &str {
&self.estimator_implementation_fingerprint
}
pub fn estimator_input_fingerprint(&self) -> &str {
&self.estimator_input_fingerprint
}
pub fn estimate_fingerprint(&self) -> &str {
&self.estimate_fingerprint
}
pub const fn value_alignment_bytes(&self) -> u64 {
self.value_alignment_bytes
}
pub fn scratch(&self) -> Option<&ProviderWorkspaceRequirement> {
self.scratch.as_ref()
}
pub fn binding(&self) -> Option<&ProviderWorkspaceRequirement> {
self.binding.as_ref()
}
pub fn persistent(&self) -> Option<&ProviderWorkspaceRequirement> {
self.persistent.as_ref()
}
}
#[derive(Serialize)]
pub(super) struct ProviderEstimateFingerprintMaterial<'a> {
pub(super) provider_id: &'a ProviderId,
pub(super) estimator_id: &'a str,
pub(super) estimator_version: ContractVersion,
pub(super) estimator_implementation_fingerprint: &'a str,
pub(super) estimator_input_fingerprint: &'a str,
pub(super) value_alignment_bytes: u64,
pub(super) scratch: &'a Option<ProviderWorkspaceRequirement>,
pub(super) binding: &'a Option<ProviderWorkspaceRequirement>,
pub(super) persistent: &'a Option<ProviderWorkspaceRequirement>,
}