#![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::entity_type::EntityType;
use crate::error::WikibaseError;
use crate::Coordinate;
use crate::DataValue;
use crate::DataValueType;
use crate::EntityValue;
use crate::MonoLingualText;
use crate::QuantityValue;
use crate::TimeValue;
use crate::Value;
use serde::ser::{SerializeStruct, Serializer};
use serde::Serialize;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum SnakType {
NoValue,
UnknownValue,
Value,
}
impl SnakType {
pub fn string_mapping(&self) -> &str {
match *self {
SnakType::Value => "Value",
SnakType::NoValue => "No Value",
SnakType::UnknownValue => "Some Value",
}
}
pub fn from_str(s: &str) -> Result<Self, WikibaseError> {
match s {
"value" => Ok(SnakType::Value),
"novalue" => Ok(SnakType::NoValue),
"somevalue" => Ok(SnakType::UnknownValue),
_ => Err(WikibaseError::Serialization(format!(
"SnakType parsing error: {}",
s
))),
}
}
}
impl Serialize for SnakType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
SnakType::Value => serializer.serialize_str("value"),
SnakType::NoValue => serializer.serialize_str("novalue"),
SnakType::UnknownValue => serializer.serialize_str("somevalue"),
}
}
}
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum SnakDataType {
Time,
WikibaseItem,
WikibaseProperty,
WikibaseLexeme,
WikibaseSense,
WikibaseForm,
String,
ExternalId,
GlobeCoordinate,
MonolingualText,
Quantity,
Url,
CommonsMedia,
Math,
TabularData,
MusicalNotation,
GeoShape,
EntitySchema,
NotSet,
NoValue,
SomeValue,
}
impl FromStr for SnakDataType {
type Err = WikibaseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"time" => Ok(SnakDataType::Time),
"wikibase-item" => Ok(SnakDataType::WikibaseItem),
"wikibase-property" => Ok(SnakDataType::WikibaseProperty),
"wikibase-lexeme" => Ok(SnakDataType::WikibaseLexeme),
"wikibase-sense" => Ok(SnakDataType::WikibaseSense),
"wikibase-form" => Ok(SnakDataType::WikibaseForm),
"external-id" => Ok(SnakDataType::ExternalId),
"string" => Ok(SnakDataType::String),
"globe-coordinate" => Ok(SnakDataType::GlobeCoordinate),
"monolingualtext" => Ok(SnakDataType::MonolingualText),
"quantity" => Ok(SnakDataType::Quantity),
"commonsMedia" => Ok(SnakDataType::CommonsMedia),
"url" => Ok(SnakDataType::Url),
"math" => Ok(SnakDataType::Math),
"tabular-data" => Ok(SnakDataType::TabularData),
"musical-notation" => Ok(SnakDataType::MusicalNotation),
"geo-shape" => Ok(SnakDataType::GeoShape),
"entity-schema" => Ok(SnakDataType::EntitySchema),
"" => Ok(SnakDataType::NotSet),
"novalue" => Ok(SnakDataType::NoValue),
"somevalue" => Ok(SnakDataType::SomeValue),
_ => Err(WikibaseError::Serialization(format!(
"SnakDataType parsing error: {}",
s
))),
}
}
}
impl Serialize for SnakDataType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
SnakDataType::Time => serializer.serialize_str("time"),
SnakDataType::WikibaseItem => serializer.serialize_str("wikibase-item"),
SnakDataType::WikibaseProperty => serializer.serialize_str("wikibase-property"),
SnakDataType::WikibaseLexeme => serializer.serialize_str("wikibase-lexeme"),
SnakDataType::WikibaseSense => serializer.serialize_str("wikibase-sense"),
SnakDataType::WikibaseForm => serializer.serialize_str("wikibase-form"),
SnakDataType::ExternalId => serializer.serialize_str("external-id"),
SnakDataType::String => serializer.serialize_str("string"),
SnakDataType::GlobeCoordinate => serializer.serialize_str("globe-coordinate"),
SnakDataType::MonolingualText => serializer.serialize_str("monolingualtext"),
SnakDataType::Quantity => serializer.serialize_str("quantity"),
SnakDataType::CommonsMedia => serializer.serialize_str("commonsMedia"),
SnakDataType::Url => serializer.serialize_str("url"),
SnakDataType::Math => serializer.serialize_str("math"),
SnakDataType::TabularData => serializer.serialize_str("tabular-data"),
SnakDataType::MusicalNotation => serializer.serialize_str("musical-notation"),
SnakDataType::GeoShape => serializer.serialize_str("geo-shape"),
SnakDataType::EntitySchema => serializer.serialize_str("entity-schema"),
SnakDataType::NotSet => serializer.serialize_str(""), SnakDataType::NoValue => serializer.serialize_str("novalue"),
SnakDataType::SomeValue => serializer.serialize_str("somevalue"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Snak {
datatype: SnakDataType,
property: String,
snak_type: SnakType,
data_value: Option<DataValue>,
}
impl Snak {
pub fn new<S: Into<String>>(
datatype: SnakDataType,
property: S,
snak_type: SnakType,
data_value: Option<DataValue>,
) -> Snak {
Self {
datatype,
property: property.into(),
snak_type,
data_value,
}
}
pub fn new_time<S: Into<String>>(property: S, value: S, precision: u64) -> Snak {
Self::new(
SnakDataType::Time,
property,
SnakType::Value,
Some(DataValue::new(
DataValueType::Time,
Value::Time(TimeValue::new(
0,
0,
"http://www.wikidata.org/entity/Q1985727",
precision,
&value.into(),
0,
)),
)),
)
}
pub fn new_item<S: Into<String>>(property: S, entity_id: S) -> Snak {
Self::new(
SnakDataType::WikibaseItem,
property,
SnakType::Value,
Some(DataValue::new(
DataValueType::EntityId,
Value::Entity(EntityValue::new(EntityType::Item, entity_id.into())),
)),
)
}
pub fn new_string<S: Into<String>>(property: S, value: S) -> Snak {
Self::new(
SnakDataType::String,
property,
SnakType::Value,
Some(DataValue::new(
DataValueType::StringType,
Value::StringValue(value.into()),
)),
)
}
pub fn new_url<S: Into<String>>(property: S, value: S) -> Snak {
Self::new(
SnakDataType::Url,
property,
SnakType::Value,
Some(DataValue::new(
DataValueType::StringType,
Value::StringValue(value.into()),
)),
)
}
pub fn new_external_id<S: Into<String>>(property: S, value: S) -> Snak {
Self::new(
SnakDataType::ExternalId,
property,
SnakType::Value,
Some(DataValue::new(
DataValueType::StringType,
Value::StringValue(value.into()),
)),
)
}
pub fn new_coordinate<S: Into<String>>(property: S, latitude: f64, longitude: f64) -> Snak {
Self::new(
SnakDataType::GlobeCoordinate,
property,
SnakType::Value,
Some(DataValue::new(
DataValueType::GlobeCoordinate,
Value::Coordinate(Coordinate::new(
None,
"http://www.wikidata.org/entity/Q2".to_string(),
latitude,
longitude,
None,
)),
)),
)
}
pub fn new_monolingual_text<S: Into<String>>(property: S, language: S, value: S) -> Snak {
Self::new(
SnakDataType::MonolingualText,
property,
SnakType::Value,
Some(DataValue::new(
DataValueType::MonoLingualText,
Value::MonoLingual(MonoLingualText::new(language, value)),
)),
)
}
pub fn new_quantity<S: Into<String>>(property: S, amount: f64) -> Snak {
Self::new(
SnakDataType::Quantity,
property,
SnakType::Value,
Some(DataValue::new(
DataValueType::Quantity,
Value::Quantity(QuantityValue::new(amount, None, "1", None)),
)),
)
}
pub fn new_no_value<S: Into<String>>(property: S, datatype: SnakDataType) -> Snak {
Self::new(datatype, property.into(), SnakType::NoValue, None)
}
pub fn new_unknown_value<S: Into<String>>(property: S, datatype: SnakDataType) -> Snak {
Self::new(datatype, property.into(), SnakType::UnknownValue, None)
}
pub fn new_from_json(
snak_json: &serde_json::Map<String, serde_json::Value>,
) -> Result<Snak, WikibaseError> {
let data_type_string = match snak_json.get("datatype") {
Some(value) => match value.as_str() {
Some(s) => s,
None => return Err(WikibaseError::Serialization("Datatype".to_string())),
},
None => "", };
let property = snak_json["property"].as_str().unwrap_or("");
let snak_type_string = snak_json["snaktype"].as_str().unwrap_or("");
let data_type = SnakDataType::from_str(data_type_string)?;
let snak_type = SnakType::from_str(snak_type_string)?;
if snak_type == SnakType::UnknownValue {
let snak = Snak::new(data_type, property, SnakType::UnknownValue, None);
return Ok(snak);
}
if snak_type == SnakType::NoValue {
let snak = Snak::new(data_type, property, SnakType::NoValue, None);
return Ok(snak);
}
let data_value = match snak_json["datavalue"].as_object() {
Some(value) => value,
None => {
return Err(WikibaseError::Serialization(
"Data value not found".to_string(),
));
}
};
let data_value_type = match data_value["type"].as_str() {
Some(value) => DataValueType::new_from_str(value)?,
None => {
return Err(WikibaseError::Serialization(
"Not type in data value".to_string(),
));
}
};
match data_value_type {
DataValueType::EntitySchemaId => {
let value_object = match data_value["value"].as_object() {
Some(value) => value,
None => {
return Err(WikibaseError::Serialization(
"Error serializing value object".to_string(),
));
}
};
let entity_value = EntityValue::new_from_object(value_object)?;
let statement_value = Value::EntitySchema(entity_value);
let data_value = DataValue::new(data_value_type, statement_value);
Ok(Snak::new(
data_type,
property,
SnakType::Value,
Some(data_value),
))
}
DataValueType::EntityId => {
let value_object = match data_value["value"].as_object() {
Some(value) => value,
None => {
return Err(WikibaseError::Serialization(
"Error serializing value object".to_string(),
));
}
};
let entity_value = EntityValue::new_from_object(value_object)?;
let statement_value = Value::Entity(entity_value);
let data_value = DataValue::new(data_value_type, statement_value);
Ok(Snak::new(
data_type,
property,
SnakType::Value,
Some(data_value),
))
}
DataValueType::Quantity => {
let value_object = match data_value["value"].as_object() {
Some(value) => value,
None => {
return Err(WikibaseError::Serialization(
"Error serializing value object".to_string(),
));
}
};
let quantity_value = QuantityValue::new_from_object(value_object)?;
let statement_value = Value::Quantity(quantity_value);
let data_value = DataValue::new(data_value_type, statement_value);
Ok(Snak::new(
data_type,
property,
SnakType::Value,
Some(data_value),
))
}
DataValueType::GlobeCoordinate => {
let value_object = match data_value["value"].as_object() {
Some(value) => value,
None => {
return Err(WikibaseError::Serialization(
"Error serializing value object".to_string(),
));
}
};
let coordinate = Coordinate::new_from_json(value_object)?;
let coordinate_statement = Value::Coordinate(coordinate);
let data_value = DataValue::new(data_value_type, coordinate_statement);
Ok(Snak::new(
data_type,
property,
SnakType::Value,
Some(data_value),
))
}
DataValueType::Time => {
let value_object = match data_value["value"].as_object() {
Some(value) => value,
None => {
return Err(WikibaseError::Serialization(
"Error serializing value object".to_string(),
));
}
};
let time_value_struct = TimeValue::new_from_object(value_object);
let time_value = Value::Time(time_value_struct);
let data_value = DataValue::new(data_value_type, time_value);
Ok(Snak::new(
data_type,
property,
SnakType::Value,
Some(data_value),
))
}
DataValueType::MonoLingualText => {
let value_object = match data_value["value"].as_object() {
Some(value) => value,
None => {
return Err(WikibaseError::Serialization(
"Error serializing value object".to_string(),
));
}
};
let mono_lingual_text = MonoLingualText::new_from_json(value_object)?;
let mono_lingual_text_value = Value::MonoLingual(mono_lingual_text);
let data_value = DataValue::new(data_value_type, mono_lingual_text_value);
Ok(Snak::new(
data_type,
property,
SnakType::Value,
Some(data_value),
))
}
DataValueType::StringType => {
let value_string = match data_value["value"].as_str() {
Some(value) => value,
None => {
return Err(WikibaseError::Serialization(
"Error serializing value string".to_string(),
));
}
};
let statement_value = Value::StringValue(value_string.to_string());
let data_value = DataValue::new(data_value_type, statement_value);
Ok(Snak::new(
data_type,
property,
SnakType::Value,
Some(data_value),
))
}
}
}
pub fn datatype(&self) -> &SnakDataType {
&self.datatype
}
pub fn data_value(&self) -> &Option<DataValue> {
&self.data_value
}
pub fn property(&self) -> &str {
&self.property
}
pub fn set_datatype(&mut self, datatype: SnakDataType) {
self.datatype = datatype;
}
pub fn set_data_value(&mut self, data_value: Option<DataValue>) {
self.data_value = data_value;
}
pub fn set_property(&mut self, property: &str) {
self.property = property.to_string();
}
pub fn set_snak_type(&mut self, snak_type: SnakType) {
self.snak_type = snak_type;
}
pub fn snak_type(&self) -> &SnakType {
&self.snak_type
}
}
impl Serialize for Snak {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut num = 3;
if self.data_value.is_some() {
num += 1;
}
let mut state = serializer.serialize_struct("Snak", num)?;
state.serialize_field("snaktype", &self.snak_type)?;
state.serialize_field("property", &self.property)?;
state.serialize_field("datatype", &self.datatype)?;
if let Some(dv) = &self.data_value {
state.serialize_field("datavalue", &dv)?;
}
state.end()
}
}
#[cfg(test)]
mod tests {
use crate::entity_container::EntityContainer;
use crate::test_helpers::*;
use mediawiki::api::Api;
use std::collections::HashMap;
use wiremock::matchers::{path, query_param};
use wiremock::{Mock, ResponseTemplate};
#[tokio::test]
async fn test_datatypes() {
let server = start_wikidata_mock().await;
let conceptbaseuri = format!("{}/entity/", server.uri());
let sparql_result = serde_json::json!({
"results": {
"bindings": [
{
"prop": {"type": "uri", "value": format!("{}P1", conceptbaseuri)},
"wbtype": {"type": "uri", "value": "http://wikiba.se/ontology#String"}
},
{
"prop": {"type": "uri", "value": format!("{}P2", conceptbaseuri)},
"wbtype": {"type": "uri", "value": "http://wikiba.se/ontology#WikibaseItem"}
}
]
}
});
Mock::given(path("/sparql"))
.respond_with(ResponseTemplate::new(200).set_body_json(sparql_result))
.mount(&server)
.await;
Mock::given(query_param("action", "wbgetentities"))
.respond_with(
ResponseTemplate::new(200).set_body_json(wbgetentities_response(vec![
minimal_property("P1"),
minimal_property("P2"),
])),
)
.mount(&server)
.await;
let api = Api::new(&server.uri()).await.unwrap();
let sparql = "SELECT (max(?property) as ?prop ) ?wbtype WHERE { ?property rdf:type wikibase:Property. ?property wikibase:propertyType ?wbtype} GROUP BY ?wbtype";
let mut prop2type: HashMap<String, String> = HashMap::new();
let sparql_result = api.sparql_query(sparql).await.unwrap();
for b in sparql_result["results"]["bindings"].as_array().unwrap() {
let prop = b["prop"]["value"].as_str().unwrap();
let prop = api.extract_entity_from_uri(prop).unwrap();
let wbtype = b["wbtype"]["value"].as_str().unwrap();
prop2type.insert(prop, wbtype.to_string());
}
let props: Vec<String> = prop2type.keys().map(|prop| prop.to_string()).collect();
let ec = EntityContainer::new();
ec.load_entities(&api, &props).await.unwrap();
for prop in &props {
match ec.get_entity(prop.to_owned()) {
Some(_i) => {}
None => panic!(
"Could not get property {} for datatype {}",
&prop, &prop2type[prop]
),
}
}
}
}