airlab_lib/model/
validation.rs

1use crate::ctx::Ctx;
2use crate::model::ModelManager;
3use crate::model::Result;
4use crate::model::base::{self, DbBmc};
5//use chrono::prelude::*;
6use modql::field::Fields;
7use modql::filter::{FilterNodes, ListOptions, OpValsInt64, OpValsString};
8use serde::{Deserialize, Serialize};
9use sqlx::FromRow;
10
11impl ValidationBmc {
12    #[must_use]
13    pub fn get_create_sql(drop_table: bool) -> String {
14        let table = Self::TABLE;
15        format!(
16            r##"{}
17create table if not exists "{table}" (
18  id serial primary key,
19  group_id integer NOT NULL,
20  created_by integer NOT NULL,
21  clone_id integer NOT NULL,
22  lot_id integer,
23  conjugate_id integer,
24  species_id integer,
25  application integer NOT NULL,
26  positive_control character varying,
27  negative_control character varying,
28  incubation_conditions character varying,
29  concentration character varying,
30  concentration_unit character varying,
31  tissue character varying,
32  fixation integer,
33  fixation_notes character varying,
34  notes character varying,
35  status integer DEFAULT 3 NOT NULL,
36  antigen_retrieval_type character varying,
37  antigen_retrieval_time character varying,
38  antigen_retrieval_temperature character varying,
39  saponin boolean,
40  saponin_concentration character varying,
41  methanol_treatment boolean,
42  methanol_treatment_concentration character varying,
43  surface_staining boolean,
44  surface_staining_concentration character varying,
45  meta jsonb,
46  file_id integer,
47  is_archived boolean DEFAULT false NOT NULL,
48  created_at timestamp with time zone DEFAULT now() NOT NULL,
49  updated_at timestamp with time zone DEFAULT now() NOT NULL
50);
51CREATE INDEX "IDX_validation_application" ON validation USING btree (application);
52CREATE INDEX "IDX_validation_clone_id" ON validation USING btree (clone_id);
53CREATE INDEX "IDX_validation_conjugate_id" ON validation USING btree (conjugate_id);
54CREATE INDEX "IDX_validation_created_by" ON validation USING btree (created_by);
55CREATE INDEX "IDX_validation_group_id" ON validation USING btree (group_id);
56CREATE INDEX "IDX_validation_lot_id" ON validation USING btree (lot_id);
57CREATE INDEX "IDX_validation_species_id" ON validation USING btree (species_id);
58CREATE INDEX "IDX_validation_status" ON validation USING btree (status);
59        "##,
60            if drop_table {
61                format!("drop table if exists {table};")
62            } else {
63                String::new()
64            }
65        )
66    }
67}
68
69#[derive(Debug, Clone, Fields, FromRow, Serialize, Deserialize, Default)]
70pub struct MinValidation {
71    pub id: i32,
72    #[serde(rename = "cloneId")]
73    pub clone_id: i32,
74    pub application: i32,
75    pub status: i32,
76}
77
78#[derive(Debug, Clone, Fields, FromRow, Serialize, Deserialize, Default)]
79pub struct Validation {
80    pub id: i32,
81    #[serde(rename = "groupId")]
82    pub group_id: i32,
83
84    #[serde(rename = "createdBy")]
85    pub created_by: i32,
86    #[serde(rename = "cloneId")]
87    pub clone_id: i32,
88    #[serde(rename = "lotId")]
89    pub lot_id: Option<i32>,
90    #[serde(rename = "conjugateId")]
91    pub conjugate_id: Option<i32>,
92    #[serde(rename = "speciesId")]
93    pub species_id: Option<i32>,
94    pub application: i32,
95    #[serde(rename = "positiveControl")]
96    pub positive_control: Option<String>,
97    #[serde(rename = "negativeControl")]
98    pub negative_control: Option<String>,
99    #[serde(rename = "incubationConditions")]
100    pub incubation_conditions: Option<String>,
101    pub concentration: Option<String>,
102    #[serde(rename = "concentrationUnit")]
103    pub concentration_unit: Option<String>,
104    pub tissue: Option<String>,
105    pub fixation: Option<i32>,
106    #[serde(rename = "fixationNotes")]
107    pub fixation_notes: Option<String>,
108    pub notes: Option<String>,
109    pub status: i32,
110    #[serde(rename = "antigenRetrievalType")]
111    pub antigen_retrieval_type: Option<String>,
112    #[serde(rename = "antigenRetrievalTime")]
113    pub antigen_retrieval_time: Option<String>,
114    #[serde(rename = "antigenRetrievalTemperature")]
115    pub antigen_retrieval_temperature: Option<String>,
116    pub saponin: Option<bool>,
117    #[serde(rename = "saponinConcentration")]
118    pub saponin_concentration: Option<String>,
119    #[serde(rename = "saponinTreatment")]
120    pub methanol_treatment: Option<bool>,
121    #[serde(rename = "methanolTreatmentConcentration")]
122    pub methanol_treatment_concentration: Option<String>,
123    #[serde(rename = "methanolStaining")]
124    pub surface_staining: Option<bool>,
125    #[serde(rename = "surfaceStainingConcentration")]
126    pub surface_staining_concentration: Option<String>,
127    pub meta: Option<serde_json::Value>,
128    #[serde(rename = "fileId")]
129    pub file_id: Option<i32>,
130    #[serde(rename = "isArchived")]
131    pub is_archived: bool,
132    #[serde(rename = "createdAt")]
133    pub created_at: chrono::DateTime<chrono::Utc>,
134    #[serde(rename = "updatedAt")]
135    pub updated_at: chrono::DateTime<chrono::Utc>,
136}
137
138#[derive(Fields, Deserialize, Clone, Debug)]
139pub struct ValidationForCreate {
140    #[serde(rename = "groupId")]
141    pub group_id: i32,
142    #[serde(rename = "createdBy")]
143    pub created_by: Option<i32>,
144    #[serde(rename = "cloneId")]
145    pub clone_id: i32,
146    #[serde(rename = "lotId")]
147    pub lot_id: Option<i32>,
148    #[serde(rename = "conjugateId")]
149    pub conjugate_id: Option<i32>,
150    #[serde(rename = "speciesId")]
151    pub species_id: Option<i32>,
152    pub application: Option<i32>,
153    #[serde(rename = "positiveControl")]
154    pub positive_control: Option<String>,
155    #[serde(rename = "negativeControl")]
156    pub negative_control: Option<String>,
157    #[serde(rename = "incubationConditions")]
158    pub incubation_conditions: Option<String>,
159    pub concentration: Option<String>,
160    #[serde(rename = "concentrationUnit")]
161    pub concentration_unit: Option<String>,
162    pub tissue: Option<String>,
163    pub fixation: Option<i32>,
164    #[serde(rename = "fixationNotes")]
165    pub fixation_notes: Option<String>,
166    pub notes: Option<String>,
167    pub status: Option<i32>,
168    #[serde(rename = "antigenRetrievalType")]
169    pub antigen_retrieval_type: Option<String>,
170    #[serde(rename = "antigenRetrievalTime")]
171    pub antigen_retrieval_time: Option<String>,
172    #[serde(rename = "antigenRetrievalTemperature")]
173    pub antigen_retrieval_temperature: Option<String>,
174    pub saponin: Option<bool>,
175    #[serde(rename = "saponinConcentration")]
176    pub saponin_concentration: Option<String>,
177    #[serde(rename = "saponinTreatment")]
178    pub methanol_treatment: Option<bool>,
179    #[serde(rename = "methanolTreatmentConcentration")]
180    pub methanol_treatment_concentration: Option<String>,
181    #[serde(rename = "methanolStaining")]
182    pub surface_staining: Option<bool>,
183    #[serde(rename = "surfaceStainingConcentration")]
184    pub surface_staining_concentration: Option<String>,
185    //pub meta: Option<String>,
186    #[serde(rename = "fileId")]
187    pub file_id: Option<i32>,
188    #[serde(rename = "isArchived")]
189    pub is_archived: Option<bool>,
190    #[serde(rename = "createdAt")]
191    pub created_at: Option<chrono::DateTime<chrono::Utc>>,
192    #[serde(rename = "updatedAt")]
193    pub updated_at: Option<chrono::DateTime<chrono::Utc>>,
194}
195
196#[derive(Fields, Default, Deserialize, Debug)]
197pub struct ValidationForUpdate {
198    pub application: i32,
199    #[serde(rename = "positiveControl")]
200    pub positive_control: Option<String>,
201    #[serde(rename = "negativeControl")]
202    pub negative_control: Option<String>,
203    #[serde(rename = "incubationConditions")]
204    pub incubation_conditions: Option<String>,
205    pub concentration: Option<String>,
206    #[serde(rename = "concentrationUnit")]
207    pub concentration_unit: Option<String>,
208    pub tissue: Option<String>,
209    pub fixation: Option<i32>,
210    #[serde(rename = "fixationNotes")]
211    pub fixation_notes: Option<String>,
212    pub notes: Option<String>,
213    pub status: i32,
214    #[serde(rename = "antigenRetrievalType")]
215    pub antigen_retrieval_type: Option<String>,
216    #[serde(rename = "antigenRetrievalTime")]
217    pub antigen_retrieval_time: Option<String>,
218    #[serde(rename = "antigenRetrievalTemperature")]
219    pub antigen_retrieval_temperature: Option<String>,
220    pub saponin: Option<bool>,
221    #[serde(rename = "saponinConcentration")]
222    pub saponin_concentration: Option<String>,
223    #[serde(rename = "saponinTreatment")]
224    pub methanol_treatment: Option<bool>,
225    #[serde(rename = "methanolTreatmentConcentration")]
226    pub methanol_treatment_concentration: Option<String>,
227    #[serde(rename = "methanolStaining")]
228    pub surface_staining: Option<bool>,
229    #[serde(rename = "surfaceStainingConcentration")]
230    pub surface_staining_concentration: Option<String>,
231    //pub meta: Option<String>,
232    #[serde(rename = "fileId")]
233    pub file_id: Option<i32>,
234    #[serde(rename = "isArchived")]
235    pub is_archived: Option<bool>,
236}
237
238#[derive(FilterNodes, Deserialize, Default, Debug)]
239pub struct ValidationFilter {
240    id: Option<OpValsInt64>,
241    group_id: Option<OpValsInt64>,
242
243    tissue: Option<OpValsString>,
244}
245
246pub struct ValidationBmc;
247
248impl DbBmc for ValidationBmc {
249    const TABLE: &'static str = "validation";
250}
251
252impl ValidationBmc {
253    pub async fn create(
254        ctx: &Ctx,
255        mm: &ModelManager,
256        validation_c: ValidationForCreate,
257    ) -> Result<i32> {
258        base::create::<Self, _>(ctx, mm, validation_c).await
259    }
260    pub async fn create_full(
261        ctx: &Ctx,
262        mm: &ModelManager,
263        validation_c: Validation,
264    ) -> Result<i32> {
265        base::create::<Self, _>(ctx, mm, validation_c).await
266    }
267
268    pub async fn get(ctx: &Ctx, mm: &ModelManager, id: i32) -> Result<Validation> {
269        base::get::<Self, _>(ctx, mm, id).await
270    }
271
272    pub async fn list(
273        ctx: &Ctx,
274        mm: &ModelManager,
275        filters: Option<Vec<ValidationFilter>>,
276        list_options: Option<ListOptions>,
277    ) -> Result<Vec<Validation>> {
278        base::list::<Self, _, _>(ctx, mm, filters, list_options).await
279    }
280
281    pub async fn minlist(
282        ctx: &Ctx,
283        mm: &ModelManager,
284        filters: Option<Vec<ValidationFilter>>,
285        list_options: Option<ListOptions>,
286    ) -> Result<Vec<MinValidation>> {
287        base::list::<Self, _, _>(ctx, mm, filters, list_options).await
288    }
289
290    pub async fn update(
291        ctx: &Ctx,
292        mm: &ModelManager,
293        id: i32,
294        validation_u: ValidationForUpdate,
295    ) -> Result<()> {
296        base::update::<Self, _>(ctx, mm, id, validation_u).await
297    }
298
299    pub async fn delete(ctx: &Ctx, mm: &ModelManager, id: i32) -> Result<()> {
300        base::delete::<Self>(ctx, mm, id).await
301    }
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307    use crate::_dev_utils;
308    use crate::model::Error;
309    use anyhow::Result;
310    use serde_json::json;
311
312    #[ignore]
313    #[tokio::test]
314    async fn test_validation_create_ok() -> Result<()> {
315        let mm = ModelManager::new().await?;
316        let ctx = Ctx::root_ctx();
317        let validation_c = ValidationForCreate {
318            group_id: 1,
319            created_by: Some(261),
320            clone_id: 3124,
321            lot_id: Some(5495),
322            conjugate_id: Some(4291),
323            species_id: Some(44),
324            application: Some(1),
325            positive_control: None,
326            negative_control: None,
327            incubation_conditions: None,
328            concentration: None,
329            concentration_unit: None,
330            tissue: None,
331            fixation: None,
332            fixation_notes: None,
333            notes: None,
334            antigen_retrieval_type: None,
335            antigen_retrieval_time: None,
336            antigen_retrieval_temperature: None,
337            status: Some(1),
338            saponin: Some(false),
339            saponin_concentration: None,
340            methanol_treatment: Some(false),
341            methanol_treatment_concentration: None,
342            surface_staining: Some(false),
343            surface_staining_concentration: None,
344            file_id: Some(2909),
345            is_archived: Some(false),
346            created_at: Some(chrono::offset::Utc::now()),
347            updated_at: Some(chrono::offset::Utc::now()),
348        };
349        let id = ValidationBmc::create(&ctx, &mm, validation_c).await?;
350
351        let validation = ValidationBmc::get(&ctx, &mm, id).await?;
352        assert_eq!(validation.id, 3);
353
354        ValidationBmc::delete(&ctx, &mm, id).await?;
355
356        Ok(())
357    }
358
359    #[ignore]
360    #[tokio::test]
361    async fn test_validation_get_err_not_found() -> Result<()> {
362        let mm = ModelManager::new().await?;
363        let ctx = Ctx::root_ctx();
364        let fx_id = 100;
365
366        let res = ValidationBmc::get(&ctx, &mm, fx_id).await;
367
368        assert!(
369            matches!(
370                res,
371                Err(Error::EntityNotFound {
372                    entity: "validation",
373                    id: 100
374                })
375            ),
376            "EntityNotFound not matching"
377        );
378
379        Ok(())
380    }
381
382    #[ignore]
383    #[tokio::test]
384    async fn test_validation_list_all_ok() -> Result<()> {
385        let mm = ModelManager::new().await?;
386        let ctx = Ctx::root_ctx();
387        let tname = "test_validation_list_all_ok";
388        let seeds = _dev_utils::get_validation_seed(tname);
389        _dev_utils::seed_validations(&ctx, &mm, &seeds).await?;
390
391        let validations = ValidationBmc::list(&ctx, &mm, None, None).await?;
392
393        let validations: Vec<Validation> = validations.into_iter().filter(|t| t.id == 1).collect();
394        assert_eq!(validations.len(), 4, "number of seeded validations.");
395
396        for validation in validations.iter() {
397            ValidationBmc::delete(&ctx, &mm, validation.id).await?;
398        }
399
400        Ok(())
401    }
402
403    #[ignore]
404    #[tokio::test]
405    async fn test_validation_list_by_filter_ok() -> Result<()> {
406        let mm = ModelManager::new().await?;
407        let ctx = Ctx::root_ctx();
408        let tname = "test_validation_list_by_filter_ok";
409        let seeds = _dev_utils::get_validation_seed(tname);
410        _dev_utils::seed_validations(&ctx, &mm, &seeds).await?;
411
412        let filters: Vec<ValidationFilter> = serde_json::from_value(json!([
413            {
414                "name": {
415                    "$endsWith": ".a",
416                    "$containsAny": ["01", "02"]
417                }
418            },
419            {
420                "name": {"$contains": "03"}
421            }
422        ]))?;
423        let list_options = serde_json::from_value(json!({
424            "order_bys": "!id"
425        }))?;
426        let validations = ValidationBmc::list(&ctx, &mm, Some(filters), Some(list_options)).await?;
427
428        assert_eq!(validations.len(), 3);
429        assert!(validations[0].id == 1);
430
431        let validations = ValidationBmc::list(
432            &ctx,
433            &mm,
434            Some(serde_json::from_value(json!([{
435                "name": {"$startsWith": "test_list_by_filter_ok"}
436            }]))?),
437            None,
438        )
439        .await?;
440        assert_eq!(validations.len(), 5);
441        for validation in validations.iter() {
442            ValidationBmc::delete(&ctx, &mm, validation.id).await?;
443        }
444        Ok(())
445    }
446
447    #[ignore]
448    #[tokio::test]
449    async fn test_validation_update_ok() -> Result<()> {
450        let mm = ModelManager::new().await?;
451        let ctx = Ctx::root_ctx();
452        let tname = "test_validation_update_ok";
453        let seeds = _dev_utils::get_validation_seed(tname);
454        let fx_validation = _dev_utils::seed_validations(&ctx, &mm, &seeds)
455            .await?
456            .remove(0);
457
458        ValidationBmc::update(
459            &ctx,
460            &mm,
461            fx_validation.id,
462            ValidationForUpdate {
463                ..Default::default()
464            },
465        )
466        .await?;
467
468        let validation = ValidationBmc::get(&ctx, &mm, fx_validation.id).await?;
469        assert_eq!(validation.id, 1);
470
471        Ok(())
472    }
473
474    #[ignore]
475    #[tokio::test]
476    async fn test_validation_delete_err_not_found() -> Result<()> {
477        let mm = ModelManager::new().await?;
478        let ctx = Ctx::root_ctx();
479        let fx_id = 100;
480
481        let res = ValidationBmc::delete(&ctx, &mm, fx_id).await;
482
483        assert!(
484            matches!(
485                res,
486                Err(Error::EntityNotFound {
487                    entity: "validation",
488                    id: 100
489                })
490            ),
491            "EntityNotFound not matching"
492        );
493
494        Ok(())
495    }
496}