sbol_db_postgres/repo/
validation.rs1use sbol::v3::ValidationReport;
2use sbol_db_core::{DomainError, GraphId, Severity, ValidationRunId, ValidationStatus};
3use sqlx::Row;
4use uuid::Uuid;
5
6use crate::repo::db_err;
7use crate::PgPool;
8
9#[derive(Clone)]
10pub struct ValidationRepository {
11 _pool: PgPool,
12}
13
14#[derive(Debug, Clone)]
15pub struct RecordedValidation {
16 pub run_id: ValidationRunId,
17 pub status: ValidationStatus,
18 pub issue_count: usize,
19}
20
21impl ValidationRepository {
22 pub fn new(pool: PgPool) -> Self {
23 Self { _pool: pool }
24 }
25
26 #[allow(clippy::too_many_arguments)]
29 pub async fn record_run(
30 &self,
31 conn: &mut sqlx::PgConnection,
32 target_iri: &str,
33 graph_id: Option<GraphId>,
34 validator_name: &str,
35 validator_version: Option<&str>,
36 ruleset: &str,
37 report: &ValidationReport,
38 ) -> Result<RecordedValidation, DomainError> {
39 let status = classify(report);
40 let run_row = sqlx::query(
41 r#"
42 INSERT INTO sbol_validation_runs (
43 target_iri, graph_id, validator_name, validator_version,
44 ruleset, status, finished_at, summary
45 ) VALUES ($1, $2, $3, $4, $5, $6, now(), $7)
46 RETURNING id
47 "#,
48 )
49 .bind(target_iri)
50 .bind(graph_id.map(|d| d.0))
51 .bind(validator_name)
52 .bind(validator_version)
53 .bind(ruleset)
54 .bind(status.as_db_str())
55 .bind(serde_json::json!({
56 "issue_count": report.issues().len(),
57 "error_count": report.errors().count(),
58 "warning_count": report.warnings().count(),
59 }))
60 .fetch_one(&mut *conn)
61 .await
62 .map_err(db_err)?;
63 let run_id: Uuid = run_row.try_get("id").map_err(db_err)?;
64
65 for issue in report.issues() {
66 let severity = match issue.severity {
67 sbol::v3::Severity::Warning => Severity::Warning,
68 sbol::v3::Severity::Error => Severity::Error,
69 _ => Severity::Warning,
70 };
71 let subject_iri = issue.subject.as_iri().map(|i| i.as_str().to_owned());
72 sqlx::query(
73 r#"
74 INSERT INTO sbol_validation_findings (
75 validation_run_id, severity, rule_id, message, subject_iri, path
76 ) VALUES ($1, $2, $3, $4, $5, $6)
77 "#,
78 )
79 .bind(run_id)
80 .bind(severity.as_db_str())
81 .bind(issue.rule)
82 .bind(&issue.message)
83 .bind(subject_iri)
84 .bind(issue.property)
85 .execute(&mut *conn)
86 .await
87 .map_err(db_err)?;
88 }
89
90 Ok(RecordedValidation {
91 run_id: ValidationRunId(run_id),
92 status,
93 issue_count: report.issues().len(),
94 })
95 }
96}
97
98fn classify(report: &ValidationReport) -> ValidationStatus {
99 if report.has_errors() {
100 ValidationStatus::Failed
101 } else if report.warnings().next().is_some() {
102 ValidationStatus::Warning
103 } else {
104 ValidationStatus::Passed
105 }
106}