wikibase 0.7.6

A library to access Wikibase
Documentation
#![deny(
//    missing_docs,
    missing_debug_implementations,
    missing_copy_implementations,
    trivial_casts,
    trivial_numeric_casts,
    unsafe_code,
    unstable_features,
    unused_import_braces,
    unused_qualifications
)]

use crate::Reference;
use crate::Snak;
use crate::SnakDataType;
use crate::error::WikibaseError;
use serde::Serialize;
use serde::ser::{SerializeStruct, Serializer};

/// Statement Rank
///
/// The rank each statement has on Wikidata. Default is Normal.
/// https://www.wikidata.org/wiki/Help:Ranking
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum StatementRank {
    Deprecated,
    Normal,
    Preferred,
}

impl StatementRank {
    pub fn as_str(&self) -> &str {
        match self {
            StatementRank::Deprecated => "deprecated",
            StatementRank::Normal => "normal",
            StatementRank::Preferred => "preferred",
        }
    }
}

/// Statement
///
/// A Wikidata statement has a main statement (the `main_snak`),
/// and qualifiers and references. In Wikidata terminology
/// only a claim with references is a statement.
#[derive(Debug, Clone, PartialEq)]
pub struct Statement {
    claim_type: String,
    rank: StatementRank,
    main_snak: Snak,
    qualifiers: Vec<Snak>,
    references: Vec<Reference>,
    id: Option<String>,
}

impl Statement {
    pub fn new<S: Into<String>>(
        claim_type: S,
        rank: StatementRank,
        main_snak: Snak,
        qualifiers: Vec<Snak>,
        references: Vec<Reference>,
    ) -> Statement {
        Self {
            claim_type: claim_type.into(),
            rank,
            main_snak,
            qualifiers,
            references,
            id: None,
        }
    }

    pub fn new_normal(
        main_snak: Snak,
        qualifiers: Vec<Snak>,
        references: Vec<Reference>,
    ) -> Statement {
        Self::new(
            "statement",
            StatementRank::Normal,
            main_snak,
            qualifiers,
            references,
        )
    }

    /// Serializes a Wikidata claim
    ///
    /// Takes a json object and returns a result.
    ///
    /// # Errors
    ///
    /// An error is returned when the json can't be interpreted as an object,
    /// or the main snak is missing.
    pub fn new_from_json(json_claim: &serde_json::Value) -> Result<Statement, WikibaseError> {
        let claim_object = match json_claim.as_object() {
            Some(value) => value,
            None => return Err(WikibaseError::Serialization("Statement".to_string())),
        };

        let claim_type = match claim_object["type"].as_str() {
            Some(value) => value,
            None => {
                return Err(WikibaseError::Serialization(
                    "Statement type error".to_string(),
                ));
            }
        };

        let rank_string = match claim_object["rank"].as_str() {
            Some(value) => value,
            None => {
                return Err(WikibaseError::Serialization(
                    "Statement rank error".to_string(),
                ));
            }
        };

        let rank = match rank_string {
            "deprecated" => StatementRank::Deprecated,
            "normal" => StatementRank::Normal,
            "preferred" => StatementRank::Preferred,
            _ => {
                return Err(WikibaseError::Serialization(
                    "Statement rank error".to_string(),
                ));
            }
        };

        let main_snak = match claim_object["mainsnak"].as_object() {
            Some(snak_json) => Snak::new_from_json(snak_json)?,
            None => return Err(WikibaseError::Serialization("Main snak".to_string())),
        };

        let qualifiers = match claim_object.get("qualifiers") {
            // Qualifiers are an object with property keys that contain
            // an array of snaks. {"P817": [], ...}
            Some(qualifiers) => Statement::snaks_object_from_json(qualifiers)?,
            None => vec![],
        };

        let references = match claim_object.get("references") {
            Some(references_array_json) => {
                Statement::reference_array_from_json(references_array_json)?
            }
            None => vec![],
        };

        let mut statement = Statement::new(claim_type, rank, main_snak, qualifiers, references);

        // Set statement ID, if present

        if let Some(id) = claim_object.get("id")
            && let Some(id) = id.as_str()
        {
            statement.set_id(id.to_string())
        }

        Ok(statement)
    }

    /// Serializes an object of snaks from JSON
    ///
    /// Wikibase uses this structure for qualfiers and the references.
    ///
    /// # JSON Documentation
    ///
    /// Serializes a json that looks like this:
    ///
    /// `
    /// {"qualifiers": {"P1": [Snak1, Snak2], ... ]}}
    /// `
    pub fn snaks_object_from_json(
        snaks_object_json: &serde_json::Value,
    ) -> Result<Vec<Snak>, WikibaseError> {
        let mut snaks = vec![];

        let snaks_object = match snaks_object_json.as_object() {
            Some(value) => value,
            None => {
                return Err(WikibaseError::Serialization(
                    "Can't deserialize snaks object".to_string(),
                ));
            }
        };

        // Loop over the property keys e.g. {"P817": [], ...}
        for (_, property_snaks) in snaks_object.iter() {
            let snak_array = match property_snaks.as_array() {
                Some(value) => value,
                None => {
                    return Err(WikibaseError::Serialization(
                        "Can't deserialize snak array".to_string(),
                    ));
                }
            };

            // Loop over the qualifiers of one property e.g. {"P817": [Snak-A, Snak-B, ...]}
            for snak_json in snak_array.iter() {
                let snak_object = match snak_json.as_object() {
                    Some(value) => value,
                    None => {
                        return Err(WikibaseError::Serialization(
                            "Can't deserialize snak object".to_string(),
                        ));
                    }
                };

                snaks.push(Snak::new_from_json(snak_object)?);
            }
        }

        Ok(snaks)
    }

    /// Serializes the references of a claim
    ///
    /// Takes a serde-JSON value an returns a vector of references or an error
    /// inside of a result. The JSON looks like this:
    /// {"references": ["0": {"snaks": {"P1": [Snak1, Snak2]}, ... }]}
    pub fn reference_array_from_json(
        references_array_json: &serde_json::Value,
    ) -> Result<Vec<Reference>, WikibaseError> {
        let mut references = vec![];

        let reference_array = match references_array_json.as_array() {
            Some(value) => value,
            None => return Err(WikibaseError::Serialization("Reference array".to_string())),
        };

        for references_object_json in reference_array.iter() {
            let reference_object = match references_object_json.as_object() {
                Some(value) => value,
                None => return Err(WikibaseError::Serialization("Reference object".to_string())),
            };

            let snaks_array = match reference_object.get("snaks") {
                Some(value) => value,
                None => {
                    return Err(WikibaseError::Serialization(
                        "Reference snak object".to_string(),
                    ));
                }
            };

            references.push(Reference::new(Self::snaks_object_from_json(snaks_array)?));
        }

        Ok(references)
    }

    /// Returns serde_json object, but with ID, hashes etc. removed
    pub fn as_stripped_json(&self) -> serde_json::Value {
        let mut ret = json!(self);
        // TODO strip hashes etc.
        if let Some(x) = ret.as_object_mut() {
            let _ = x.remove("id");
        }
        ret
    }

    pub fn set_id<S: Into<String>>(&mut self, id: S) {
        self.id = Some(id.into());
    }

    pub fn remove_id(&mut self) {
        self.id = None;
    }

    pub fn id(&self) -> Option<String> {
        self.id.clone()
    }

    pub fn set_claim_type(&mut self, claim_type: &str) {
        self.claim_type = claim_type.to_string();
    }

    pub fn set_rank(&mut self, rank: StatementRank) {
        self.rank = rank;
    }

    pub fn set_datatype(&mut self, datatype: SnakDataType) {
        self.main_snak.set_datatype(datatype);
    }

    pub fn property(&self) -> &str {
        self.main_snak.property()
    }

    pub fn set_property(&mut self, property: &str) {
        self.main_snak.set_property(property);
    }

    pub fn set_main_snak(&mut self, snak: Snak) {
        self.main_snak = snak;
    }

    pub fn add_qualifier_snak(&mut self, snak: Snak) {
        self.qualifiers.push(snak);
    }

    pub fn set_qualifier_snaks(&mut self, snaks: Vec<Snak>) {
        self.qualifiers = snaks;
    }

    pub fn set_references(&mut self, references: Vec<Reference>) {
        self.references = references;
    }

    pub fn references(&self) -> &Vec<Reference> {
        &self.references
    }

    pub fn qualifiers(&self) -> &Vec<Snak> {
        &self.qualifiers
    }

    pub fn main_snak(&self) -> &Snak {
        &self.main_snak
    }

    pub fn claim_type(&self) -> &String {
        &self.claim_type
    }

    pub fn rank(&self) -> &StatementRank {
        &self.rank
    }
}

impl Serialize for Statement {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut num = 3;
        if self.id.is_some() {
            num += 1;
        }
        if !self.qualifiers.is_empty() {
            num += 1;
        }
        if !self.references.is_empty() {
            num += 1;
        }
        let mut state = serializer.serialize_struct("Statement", num)?;

        state.serialize_field("type", &self.claim_type)?;
        state.serialize_field("rank", &self.rank.as_str())?;
        state.serialize_field("mainsnak", &self.main_snak)?;

        if let Some(id) = &self.id {
            state.serialize_field("id", &id)?
        };

        if !self.qualifiers.is_empty() {
            let mut qualifiers = json!({});
            for snak in &self.qualifiers {
                if !qualifiers[snak.property()].is_object() {
                    qualifiers[snak.property()] = json!([]);
                }
                if let Some(qualifiers) = qualifiers[snak.property()].as_array_mut() {
                    qualifiers.push(json!(snak))
                }
            }
            state.serialize_field("qualifiers", &qualifiers)?;
        }

        if !self.references.is_empty() {
            let mut references = json!([]);
            for reference in &self.references {
                let mut rj = json!({"snaks":{}});
                for snak in reference.snaks() {
                    if !rj["snaks"][snak.property()].is_array() {
                        rj["snaks"][snak.property()] = json!([]);
                    }
                    if let Some(snaks) = rj["snaks"][snak.property()].as_array_mut() {
                        snaks.push(json!(snak))
                    }
                }
                // TODO: "snaks-order"?
                if let Some(references) = references.as_array_mut() {
                    references.push(rj);
                }
            }
            state.serialize_field("references", &references)?;
        }

        state.end()
    }
}