rdml-qpcr 0.1.1

Read, write, and validate RDML (Real-time PCR Data Markup Language) qPCR data files
Documentation
//! Experimenters, shared documentation blocks, and the file-level id.

use serde::{Deserialize, Serialize};

use crate::types::{DocumentationRef, ExperimenterRef, Id};

/// Contact details of an experimenter (root-level master element
/// `experimenter`).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Experimenter {
    /// Unique id this experimenter is referenced by. Ids are short
    /// human-readable names shown in software; see [`Id`].
    pub id: Id,
    /// First (given) name.
    #[serde(rename = "firstName")]
    pub first_name: String,
    /// Last (family) name.
    #[serde(rename = "lastName")]
    pub last_name: String,
    /// E-mail address.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub email: Option<String>,
    /// Name of the laboratory.
    #[serde(rename = "labName", skip_serializing_if = "Option::is_none", default)]
    pub lab_name: Option<String>,
    /// Address of the laboratory.
    #[serde(
        rename = "labAddress",
        skip_serializing_if = "Option::is_none",
        default
    )]
    pub lab_address: Option<String>,
}

impl Experimenter {
    /// Creates an experimenter with the required fields; the optional
    /// contact details start out empty.
    pub fn new(id: Id, first_name: impl Into<String>, last_name: impl Into<String>) -> Self {
        Self {
            id,
            first_name: first_name.into(),
            last_name: last_name.into(),
            email: None,
            lab_name: None,
            lab_address: None,
        }
    }

    /// A typed reference to this experimenter, for use in runs and
    /// thermal cycling conditions.
    #[must_use]
    pub fn reference(&self) -> ExperimenterRef {
        self.id.clone().into()
    }
}

/// A shared description text (root-level master element `documentation`).
///
/// Use one of these when the same free-text description applies to many
/// samples, targets, or experiments; the other elements then reference it
/// by id instead of repeating the text.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Documentation {
    /// Unique id this documentation block is referenced by.
    pub id: Id,
    /// The shared description text.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub text: Option<String>,
}

impl Documentation {
    /// Creates a documentation block with the given id and no text.
    #[must_use]
    pub fn new(id: Id) -> Self {
        Self { id, text: None }
    }

    /// A typed reference to this documentation block.
    #[must_use]
    pub fn reference(&self) -> DocumentationRef {
        self.id.clone().into()
    }
}

/// A publisher-assigned identity for the RDML *file itself* (root element
/// `id`, schema `rdmlIdType`) — unrelated to the id attributes of master
/// elements.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RdmlId {
    /// Who assigned this serial number.
    pub publisher: String,
    /// The serial number assigned by the publisher.
    #[serde(rename = "serialNumber")]
    pub serial_number: String,
    /// An MD5 hash calculated over the complete file after removing all
    /// `rdmlId` elements and all whitespace between elements.
    #[serde(rename = "MD5Hash", skip_serializing_if = "Option::is_none", default)]
    pub md5_hash: Option<String>,
}

impl RdmlId {
    /// Creates a file id with the required fields and no hash.
    pub fn new(publisher: impl Into<String>, serial_number: impl Into<String>) -> Self {
        Self {
            publisher: publisher.into(),
            serial_number: serial_number.into(),
            md5_hash: None,
        }
    }
}