use crate::{
Capability, ChatCapability, DecisionCapability, EmbeddingCapability, Result, Subject, Warning,
};
#[derive(Debug, Clone)]
#[must_use]
#[non_exhaustive]
pub struct Validation<C: Capability> {
warnings: Vec<Warning>,
relied_on_unknown: Vec<(C, Subject)>,
coverage: ValidationCoverage,
fallback: Option<Box<FallbackValidation<C>>>,
}
pub type ChatValidation = Validation<ChatCapability>;
pub type EmbeddingValidation = Validation<EmbeddingCapability>;
pub type DecisionValidation = Validation<DecisionCapability>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ValidationCoverage {
Capabilities,
Encoding,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct FallbackValidation<C: Capability> {
pub provider: String,
pub result: Result<Validation<C>>,
}
impl<C: Capability> Validation<C> {
pub fn new(coverage: ValidationCoverage) -> Self {
Self {
warnings: Vec::new(),
relied_on_unknown: Vec::new(),
coverage,
fallback: None,
}
}
#[must_use]
pub fn warnings(&self) -> &[Warning] {
&self.warnings
}
#[must_use]
pub fn relied_on_unknown(&self) -> &[(C, Subject)] {
&self.relied_on_unknown
}
#[must_use]
pub fn coverage(&self) -> ValidationCoverage {
self.coverage
}
#[must_use]
pub fn fallback(&self) -> Option<&FallbackValidation<C>> {
self.fallback.as_deref()
}
#[must_use]
pub fn into_warnings(self) -> Vec<Warning> {
self.warnings
}
pub fn with_warning(mut self, warning: Warning) -> Self {
self.warnings.push(warning);
self
}
pub fn with_unknown(mut self, capability: C, subject: Subject) -> Self {
if !self.relied_on_unknown.iter().any(|(c, _)| *c == capability) {
self.relied_on_unknown.push((capability, subject));
}
self
}
pub fn without_unknown(mut self, capability: C) -> Self {
self.relied_on_unknown.retain(|(c, _)| *c != capability);
self
}
pub fn with_fallback(mut self, fallback: FallbackValidation<C>) -> Self {
self.fallback = Some(Box::new(fallback));
self
}
pub fn with_coverage(mut self, coverage: ValidationCoverage) -> Self {
self.coverage = coverage;
self
}
pub(crate) fn followed_by(mut self, inner: Self) -> Self {
self.warnings.extend(inner.warnings);
for (capability, subject) in inner.relied_on_unknown {
self = self.with_unknown(capability, subject);
}
self.coverage = inner.coverage;
self.fallback = inner.fallback;
self
}
pub(crate) fn map_warnings(mut self, f: impl Fn(Warning) -> Warning) -> Self {
self.warnings = self.warnings.into_iter().map(f).collect();
self
}
pub(crate) fn from_parts(
warnings: Vec<Warning>,
relied_on_unknown: Vec<(C, Subject)>,
coverage: ValidationCoverage,
) -> Self {
Self {
warnings,
relied_on_unknown,
coverage,
fallback: None,
}
}
}
impl<C: Capability> FallbackValidation<C> {
#[must_use]
pub fn new(provider: impl Into<String>, result: Result<Validation<C>>) -> Self {
Self {
provider: provider.into(),
result,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{RequestField, WarningKind};
#[test]
fn builders_compose() {
let tools = Subject::Field {
field: RequestField::Tools,
};
let validation = ChatValidation::new(ValidationCoverage::Encoding)
.with_warning(Warning::unsupported(RequestField::Seed, "no seed"))
.with_unknown(ChatCapability::ToolCalls, tools.clone())
.with_unknown(ChatCapability::ToolCalls, Subject::Response)
.with_unknown(ChatCapability::StructuredOutput, Subject::Response)
.without_unknown(ChatCapability::StructuredOutput)
.with_fallback(FallbackValidation::new(
"backup",
Ok(ChatValidation::new(ValidationCoverage::Capabilities)),
));
assert_eq!(validation.coverage(), ValidationCoverage::Encoding);
assert_eq!(validation.warnings()[0].kind, WarningKind::Unsupported);
assert_eq!(
validation.relied_on_unknown(),
[(ChatCapability::ToolCalls, tools)]
);
let fallback = validation.fallback().unwrap();
assert_eq!(fallback.provider, "backup");
assert_eq!(
fallback.result.as_ref().unwrap().coverage(),
ValidationCoverage::Capabilities
);
}
}