#![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::LocaleString;
use crate::SnakDataType;
use crate::configuration::Configuration;
use crate::deserialize::ToJson;
use crate::entity_type::EntityType;
use crate::error::WikibaseError;
use crate::from_json;
use crate::item::ItemEntity;
use crate::lexeme::{LexemeEntity, LexemeForm, LexemeSense};
use crate::mediainfo::MediaInfoEntity;
use crate::property::PropertyEntity;
use crate::query::EntityQuery;
use crate::sitelink::SiteLink;
use crate::statement::Statement;
use crate::value::Value;
use serde::Serialize;
use serde::ser::Serializer;
pub trait EntityTrait {
fn to_json(&self) -> serde_json::value::Value;
fn id_mut(&mut self) -> &mut String;
fn missing_mut(&mut self) -> &mut bool;
fn aliases_mut(&mut self) -> &mut Vec<LocaleString>;
fn labels_mut(&mut self) -> &mut Vec<LocaleString>;
fn descriptions_mut(&mut self) -> &mut Vec<LocaleString>;
fn claims_mut(&mut self) -> &mut Vec<Statement>;
fn sitelinks_mut(&mut self) -> &mut Option<Vec<SiteLink>>;
fn entity_type_mut(&mut self) -> &mut EntityType;
fn id(&self) -> &String;
fn missing(&self) -> bool;
fn aliases(&self) -> &Vec<LocaleString>;
fn labels(&self) -> &Vec<LocaleString>;
fn descriptions(&self) -> &Vec<LocaleString>;
fn claims(&self) -> &Vec<Statement>;
fn sitelinks(&self) -> &Option<Vec<SiteLink>>;
fn entity_type(&self) -> &EntityType;
fn label_in_locale(&self, locale: &str) -> Option<&str> {
for label in self.labels().iter() {
if label.language() == locale {
return Some(label.value());
}
}
None
}
fn description_in_locale(&self, locale: &str) -> Option<&str> {
for description in self.descriptions().iter() {
if description.language() == locale {
return Some(description.value());
}
}
None
}
fn set_aliases(&mut self, aliases: Vec<LocaleString>) {
*self.aliases_mut() = aliases;
}
fn set_claims(&mut self, claims: Vec<Statement>) {
*self.claims_mut() = claims;
}
fn add_claim(&mut self, claim: Statement) {
self.claims_mut().push(claim);
}
fn set_descriptions(&mut self, descriptions: Vec<LocaleString>) {
*self.descriptions_mut() = descriptions;
}
fn set_id(&mut self, id: String) {
*self.id_mut() = id;
}
fn set_labels(&mut self, labels: Vec<LocaleString>) {
*self.labels_mut() = labels;
}
fn set_missing(&mut self, missing: bool) {
*self.missing_mut() = missing;
}
fn set_sitelinks(&mut self, sitelinks: Option<Vec<SiteLink>>) {
*self.sitelinks_mut() = sitelinks;
}
fn set_sitelink(&mut self, new_sitelink: SiteLink) {
let current_sitelinks = self.sitelinks_mut();
match current_sitelinks {
None => {
*current_sitelinks = Some(vec![new_sitelink]);
}
Some(sitelinks) => {
for sitelink in sitelinks.iter_mut() {
if sitelink.site() == new_sitelink.site() {
*sitelink = new_sitelink;
return;
}
}
sitelinks.push(new_sitelink);
}
}
}
}
#[derive(Debug, Clone)]
pub enum Entity {
Item(ItemEntity),
Property(PropertyEntity),
MediaInfo(MediaInfoEntity),
Lexeme(LexemeEntity),
}
impl ToJson for Entity {}
impl EntityTrait for Entity {
fn id(&self) -> &String {
self.inner().id()
}
fn missing(&self) -> bool {
self.inner().missing()
}
fn aliases(&self) -> &Vec<LocaleString> {
self.inner().aliases()
}
fn labels(&self) -> &Vec<LocaleString> {
self.inner().labels()
}
fn descriptions(&self) -> &Vec<LocaleString> {
self.inner().descriptions()
}
fn claims(&self) -> &Vec<Statement> {
self.inner().claims()
}
fn sitelinks(&self) -> &Option<Vec<SiteLink>> {
self.inner().sitelinks()
}
fn id_mut(&mut self) -> &mut String {
self.inner_mut().id_mut()
}
fn missing_mut(&mut self) -> &mut bool {
self.inner_mut().missing_mut()
}
fn aliases_mut(&mut self) -> &mut Vec<LocaleString> {
self.inner_mut().aliases_mut()
}
fn labels_mut(&mut self) -> &mut Vec<LocaleString> {
self.inner_mut().labels_mut()
}
fn descriptions_mut(&mut self) -> &mut Vec<LocaleString> {
self.inner_mut().descriptions_mut()
}
fn sitelinks_mut(&mut self) -> &mut Option<Vec<SiteLink>> {
self.inner_mut().sitelinks_mut()
}
fn claims_mut(&mut self) -> &mut Vec<Statement> {
self.inner_mut().claims_mut()
}
fn entity_type(&self) -> &EntityType {
self.inner().entity_type()
}
fn entity_type_mut(&mut self) -> &mut EntityType {
self.inner_mut().entity_type_mut()
}
fn to_json(&self) -> serde_json::value::Value {
self.inner().to_json()
}
}
impl Entity {
pub fn inner(&self) -> &dyn EntityTrait {
match self {
Entity::Item(e) => e,
Entity::Property(e) => e,
Entity::MediaInfo(e) => e,
Entity::Lexeme(e) => e,
}
}
pub fn inner_mut(&mut self) -> &mut dyn EntityTrait {
match self {
Entity::Item(e) => e,
Entity::Property(e) => e,
Entity::MediaInfo(e) => e,
Entity::Lexeme(e) => e,
}
}
pub fn new_from_json(json: &serde_json::value::Value) -> Result<Entity, WikibaseError> {
let id = match json["id"].as_str() {
Some(value) => value.to_string(),
None => return Err(WikibaseError::Serialization("ID missing".to_string())),
};
let entity_type = match EntityType::new_from_id(&id) {
Ok(et) => et,
Err(_) => {
return Err(WikibaseError::Serialization(
"Type can not be inferred from ID".to_string(),
));
}
};
match entity_type {
EntityType::Item => Ok(Entity::Item(ItemEntity::new_from_json(json)?)),
EntityType::Property => Ok(Entity::Property(PropertyEntity::new_from_json(json)?)),
EntityType::MediaInfo => Ok(Entity::MediaInfo(MediaInfoEntity::new_from_json(json)?)),
EntityType::Lexeme => Ok(Entity::Lexeme(LexemeEntity::new_from_json(json)?)),
EntityType::LexemeSense => Err(WikibaseError::Serialization(format!(
"Trying to load a LexemeSense: {}",
&json
))),
EntityType::LexemeForm => Err(WikibaseError::Serialization(format!(
"Trying to load a LexemeForm: {}",
&json
))),
EntityType::EntitySchema => Err(WikibaseError::Serialization(format!(
"Trying to load an EntitySchema: {}",
&json
))),
}
}
pub fn new_empty_item() -> Self {
Entity::Item(ItemEntity::new_empty())
}
pub fn new_empty_property() -> Self {
Entity::Property(PropertyEntity::new_empty())
}
pub fn new_empty_mediainfo() -> Self {
Entity::MediaInfo(MediaInfoEntity::new_empty())
}
pub fn new_empty_lexeme() -> Self {
Entity::Lexeme(LexemeEntity::new_empty())
}
pub fn new_item(
id: String,
labels: Vec<LocaleString>,
descriptions: Vec<LocaleString>,
aliases: Vec<LocaleString>,
claims: Vec<Statement>,
sitelinks: Option<Vec<SiteLink>>,
missing: bool,
) -> Self {
Entity::Item(ItemEntity::new(
id,
labels,
descriptions,
aliases,
claims,
sitelinks,
missing,
))
}
pub fn new_property(
id: String,
labels: Vec<LocaleString>,
descriptions: Vec<LocaleString>,
aliases: Vec<LocaleString>,
claims: Vec<Statement>,
datatype: Option<SnakDataType>,
missing: bool,
) -> Self {
let mut e =
PropertyEntity::new(id, labels, descriptions, aliases, claims, datatype, missing);
e.set_datatype(datatype);
Entity::Property(e)
}
pub fn new_mediainfo(
id: String,
labels: Vec<LocaleString>,
descriptions: Vec<LocaleString>,
claims: Vec<Statement>,
missing: bool,
) -> Self {
Entity::MediaInfo(MediaInfoEntity::new(
id,
labels,
descriptions,
claims,
missing,
))
}
pub fn new_lexeme(
id: String,
labels: Vec<LocaleString>,
claims: Vec<Statement>,
language: String,
lexical_category: Option<String>,
forms: Vec<LexemeForm>,
senses: Vec<LexemeSense>,
missing: bool,
) -> Self {
Entity::Lexeme(LexemeEntity::new(
id,
labels,
claims,
language,
lexical_category,
forms,
senses,
missing,
))
}
pub async fn new_from_id<S: Into<String>>(
id: S,
configuration: &Configuration,
) -> Result<Entity, WikibaseError> {
let ids = vec![id.into()];
let query = EntityQuery::new(ids, "en");
Entity::new_from_query(&query, configuration).await
}
pub async fn new_from_ids(
ids: Vec<String>,
configuration: &Configuration,
) -> Result<Entity, WikibaseError> {
let query = EntityQuery::new(ids, "en");
Entity::new_from_query(&query, configuration).await
}
pub async fn new_from_query(
query: &EntityQuery,
configuration: &Configuration,
) -> Result<Entity, WikibaseError> {
let params = query.params();
let json_response = match configuration.api().get_query_api_json_all(¶ms).await {
Ok(j) => j,
Err(_) => return Err(WikibaseError::Configuration("Query failed".to_string())),
};
let id = &query.ids()[0];
let json_entity = &json_response["entities"][id];
from_json::entity_from_json(json_entity)
}
pub fn set_label(&mut self, new_label: LocaleString) {
let labels = self.labels_mut();
for label in labels.iter_mut() {
if label.language() == new_label.language() {
label.set_value(new_label.value());
return;
}
}
labels.push(new_label);
}
pub fn remove_label<S: Into<String>>(&mut self, language: S) {
let language: String = language.into();
self.labels_mut().retain(|x| *x.language() != language);
}
pub fn remove_description<S: Into<String>>(&mut self, language: S) {
let language: String = language.into();
self.descriptions_mut()
.retain(|x| *x.language() != language);
}
pub fn set_sitelink(&mut self, new_sitelink: SiteLink) {
self.inner_mut().set_sitelink(new_sitelink);
}
pub fn remove_sitelink<S: Into<String>>(&mut self, site: S) {
let sitelinks = self.sitelinks_mut();
let site: String = site.into();
if let Some(sitelinks) = sitelinks {
sitelinks.retain(|x| *x.site() != site)
}
}
pub fn set_description(&mut self, new_description: LocaleString) {
let descriptions = self.descriptions_mut();
for description in descriptions.iter_mut() {
if description.language() == new_description.language() {
description.set_value(new_description.value());
return;
}
}
descriptions.push(new_description);
}
pub fn add_alias(&mut self, new_alias: LocaleString) {
if new_alias.value().is_empty() {
return;
}
let aliases = self.aliases_mut();
if !aliases.contains(&new_alias) {
aliases.push(new_alias);
}
}
pub fn remove_alias(&mut self, alias: LocaleString) {
let aliases = self.aliases_mut();
aliases.retain(|x| *x != alias);
}
pub fn claims_with_property<S: Into<String>>(&self, property_id: S) -> Vec<&Statement> {
let property_id: String = property_id.into();
self.claims()
.iter()
.filter(|claim| claim.main_snak().property() == property_id)
.collect()
}
pub fn values_for_property<S: Into<String>>(&self, property_id: S) -> Vec<Value> {
self.claims_with_property(property_id)
.iter()
.map(|c| c.main_snak().data_value())
.filter(|dv| dv.is_some())
.filter_map(|dv| dv.to_owned())
.map(|dv| dv.to_owned().value().to_owned())
.collect()
}
pub fn has_target_entity<S: Into<String>>(&self, property_id: S, target_entity_id: S) -> bool {
let target_entity_id: String = target_entity_id.into();
self.values_for_property(property_id)
.iter()
.filter(|v| match v {
Value::Entity(target_q) => target_q.id() == target_entity_id,
_ => false,
})
.count()
> 0
}
pub fn claim_with_id<S: Into<String>>(&self, id: S) -> Option<&Statement> {
let id: &str = &id.into();
self.claims().iter().find(|claim| match claim.id() {
Some(x) => x == id,
None => false,
})
}
pub fn has_claims_with_property<S: Into<String>>(&self, property_id: S) -> bool {
let property_id: String = property_id.into();
self.claims()
.iter()
.filter(|claim| claim.main_snak().property() == property_id)
.nth(0)
.is_some()
}
}
impl Serialize for Entity {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self.missing() {
true => serializer.serialize_none(),
false => serializer.serialize_some(&self.to_json()),
}
}
}
#[cfg(test)]
mod tests {
use crate::entity_container::*;
use crate::test_helpers::*;
use mediawiki::api::Api;
use wiremock::matchers::query_param;
use wiremock::{Mock, ResponseTemplate};
#[tokio::test]
async fn entity_serialization() {
let server = start_wikidata_mock().await;
Mock::given(query_param("action", "wbgetentities"))
.respond_with(
ResponseTemplate::new(200)
.set_body_json(wbgetentities_response(vec![q12345_entity()])),
)
.mount(&server)
.await;
let api = Api::new(&server.uri()).await.unwrap();
let ec = EntityContainer::new();
let the_count = ec.load_entity(&api, "Q12345").await.unwrap();
let j = json!(&the_count);
assert_eq!(j["id"].as_str().unwrap(), "Q12345");
assert_eq!(j["type"].as_str().unwrap(), "item");
assert_eq!(
j["labels"]["en"]["value"].as_str().unwrap(),
"Count von Count"
);
assert_eq!(
j["descriptions"]["en"]["value"].as_str().unwrap(),
"character on Sesame Street"
);
assert_eq!(
j["aliases"]["en"][0]["value"].as_str().unwrap(),
"The Count"
);
assert_eq!(
j["sitelinks"]["enwiki"]["title"].as_str().unwrap(),
"Count von Count"
);
assert_eq!(
j["claims"]["P3478"][0]["mainsnak"]["datavalue"]["value"]
.as_str()
.unwrap(),
"850116"
);
}
}