use axum::{
extract::{Path, State},
Json,
};
use serde::{Deserialize, Serialize};
use crate::error::{Error, Result};
use crate::middleware::{is_in_namespace, RequestNamespace};
use crate::rest::triples::{TripleDto, ValueDto};
use crate::state::{AppState, Event};
use aingle_graph::{NodeId, Predicate, Triple, Value};
#[derive(Debug, Deserialize)]
pub struct ValidateRequest {
pub triples: Vec<ValidateTripleInput>,
pub rule_set: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct ValidateTripleInput {
pub subject: String,
pub predicate: String,
pub object: ValueDto,
}
#[derive(Debug, Serialize)]
pub struct ValidateResponse {
pub valid: bool,
pub results: Vec<TripleValidationResult>,
pub proof_hash: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct TripleValidationResult {
pub triple: TripleDto,
pub valid: bool,
pub messages: Vec<ValidationMessage>,
}
#[derive(Debug, Serialize)]
pub struct ValidationMessage {
pub level: String,
pub message: String,
pub rule: Option<String>,
}
pub async fn validate_triples(
State(state): State<AppState>,
ns_ext: Option<axum::Extension<RequestNamespace>>,
Json(req): Json<ValidateRequest>,
) -> Result<Json<ValidateResponse>> {
let logic = state.logic.read().await;
let ns_filter = ns_ext.and_then(|axum::Extension(RequestNamespace(ns))| ns);
let mut results = Vec::new();
let mut all_valid = true;
for input in req.triples {
if let Some(ref ns) = ns_filter {
if !is_in_namespace(&input.subject, ns) {
return Err(Error::Forbidden(format!(
"Subject \"{}\" is not in namespace \"{}\"",
input.subject, ns
)));
}
}
let object: Value = input.object.clone().into();
let triple = Triple::new(
NodeId::named(&input.subject),
Predicate::named(&input.predicate),
object,
);
let validation = logic.validate(&triple);
let valid = validation.is_valid();
if !valid {
all_valid = false;
}
let mut messages = Vec::new();
for rejection in &validation.rejections {
messages.push(ValidationMessage {
level: "error".to_string(),
message: rejection.reason.clone(),
rule: Some(rejection.rule_id.clone()),
});
}
for warning in &validation.warnings {
messages.push(ValidationMessage {
level: "warning".to_string(),
message: warning.message.clone(),
rule: Some(warning.rule_id.clone()),
});
}
let triple_dto = TripleDto {
id: Some(triple.id().to_hex()),
subject: input.subject.clone(),
predicate: input.predicate.clone(),
object: input.object,
created_at: None,
};
results.push(TripleValidationResult {
triple: triple_dto,
valid,
messages,
});
}
let proof_hash = if all_valid {
let mut hasher = blake3::Hasher::new();
for result in &results {
if let Some(ref id) = result.triple.id {
hasher.update(id.as_bytes());
}
}
Some(hasher.finalize().to_hex().to_string())
} else {
None
};
if let Some(ref hash) = proof_hash {
state.broadcaster.broadcast(Event::ValidationCompleted {
hash: hash.clone(),
valid: all_valid,
proof_hash: proof_hash.clone(),
});
}
Ok(Json(ValidateResponse {
valid: all_valid,
results,
proof_hash,
}))
}
#[derive(Debug, Serialize)]
pub struct ProofDto {
pub hash: String,
pub steps: Vec<ProofStepDto>,
pub valid: bool,
pub verified_at: String,
pub root: String,
}
#[derive(Debug, Serialize)]
pub struct ProofStepDto {
pub index: usize,
pub rule: String,
pub premises: Vec<String>,
pub conclusion: String,
}
pub async fn get_proof(
State(_state): State<AppState>,
Path(hash): Path<String>,
) -> Result<Json<ProofDto>> {
Err(Error::NotFound(format!("Proof {} not found", hash)))
}
#[derive(Debug, Deserialize)]
pub struct VerifyProofRequest {
pub proof_hash: String,
pub statements: Option<Vec<StatementInput>>,
}
#[derive(Debug, Deserialize)]
pub struct StatementInput {
pub subject: String,
pub predicate: String,
pub object: ValueDto,
}
#[derive(Debug, Serialize)]
pub struct VerifyProofResponse {
pub valid: bool,
pub details: VerificationDetails,
}
#[derive(Debug, Serialize)]
pub struct VerificationDetails {
pub proof_hash: String,
pub steps_verified: usize,
pub statements_covered: usize,
pub verified_at: String,
}
pub async fn verify_proof(
State(_state): State<AppState>,
Json(req): Json<VerifyProofRequest>,
) -> Result<Json<VerifyProofResponse>> {
Err(Error::NotFound(format!(
"Proof {} not found",
req.proof_hash
)))
}