extern crate curl;
extern crate regex;
extern crate serde_json;
pub mod from_json;
pub mod query;
pub mod requests;
pub mod validate;
use query::{EntityQuery, SearchQuery};
use std::{error::Error, fmt};
const USER_AGENT_BASE: &'static str = "Wikibase-RS/0.3.0";
#[derive(Debug)]
pub enum WikibaseError {
Configuration(String),
Request(String),
Serialization(String),
Validation(String),
}
impl Error for WikibaseError {}
impl fmt::Display for WikibaseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Debug)]
pub struct Configuration {
api_url: String,
user_agent_prefix: String,
}
impl Configuration {
pub fn new(user_agent_prefix: &str) -> Result<Configuration, WikibaseError> {
let valid_user_agent_prefix = validate::validate_user_agent(&user_agent_prefix)?;
Ok(Self {
api_url: "https://www.wikidata.org/w/api.php".to_string(),
user_agent_prefix: valid_user_agent_prefix.to_string(),
})
}
pub fn api_url(&self) -> &str {
&self.api_url
}
pub fn set_api_url<S: Into<String>>(&mut self, api_url: S) {
self.api_url = api_url.into();
}
pub fn user_agent(&self) -> String {
format!("{} {}", &self.user_agent_prefix, USER_AGENT_BASE)
}
pub fn user_agent_prefix(&self) -> &str {
&self.user_agent_prefix
}
}
#[derive(Debug)]
pub enum Value {
Coordinate(Coordinate),
MonoLingual(MonoLingualText),
Entity(EntityValue),
Quantity(QuantityValue),
StringValue(String),
Time(TimeValue),
}
#[derive(Debug)]
pub enum DataValueType {
EntityId,
GlobeCoordinate,
MonoLingualText,
Quantity,
StringType,
Time,
}
impl DataValueType {
pub fn new_from_str(string_value: &str) -> Result<DataValueType, WikibaseError> {
match string_value {
"globecoordinate" => Ok(DataValueType::GlobeCoordinate),
"monolingualtext" => Ok(DataValueType::MonoLingualText),
"quantity" => Ok(DataValueType::Quantity),
"string" => Ok(DataValueType::StringType),
"wikibase-entityid" => Ok(DataValueType::EntityId),
"time" => Ok(DataValueType::Time),
_ => Err(WikibaseError::Serialization(
"Data value type could not be matched".to_string(),
)),
}
}
pub fn string_value(&self) -> String {
match *self {
DataValueType::EntityId => "wikibase-entityid".to_string(),
DataValueType::GlobeCoordinate => "globecoordinate".to_string(),
DataValueType::MonoLingualText => "monolingualtext".to_string(),
DataValueType::Quantity => "quantity".to_string(),
DataValueType::StringType => "string".to_string(),
DataValueType::Time => "time".to_string(),
}
}
}
#[derive(Debug)]
pub struct Coordinate {
altitude: Option<f64>,
globe: String,
latitude: f64,
longitude: f64,
precision: Option<f64>,
}
impl Coordinate {
fn new_from_json(
object: &serde_json::Map<std::string::String, serde_json::Value>,
) -> Result<Coordinate, WikibaseError> {
let mut altitude = None;
let mut precision = None;
if object["altitude"].is_null() == false {
let altitude_string = match object["altitude"].as_str() {
Some(value) => value,
None => return Err(WikibaseError::Serialization("Altitude".to_string())),
};
let altitude_number: f64 = match altitude_string.parse() {
Ok(value) => value,
Err(error) => {
return Err(WikibaseError::Serialization(
error.description().to_string(),
));
}
};
altitude = Some(altitude_number);
}
let latitude = match object["latitude"].as_f64() {
Some(value) => value,
None => return Err(WikibaseError::Serialization("Latitude".to_string())),
};
let longitude = match object["longitude"].as_f64() {
Some(value) => value,
None => return Err(WikibaseError::Serialization("Longitude".to_string())),
};
if object["precision"].is_null() == false {
precision = match object["precision"].as_f64() {
Some(value) => Some(value),
None => return Err(WikibaseError::Serialization("Precision".to_string())),
};
}
let globe = match object["globe"].as_str() {
Some(value) => value,
None => return Err(WikibaseError::Serialization("Globe".to_string())),
};
Ok(Self {
altitude,
globe: globe.to_string(),
latitude,
longitude,
precision,
})
}
pub fn new(
altitude: Option<f64>,
globe: String,
latitude: f64,
longitude: f64,
precision: Option<f64>,
) -> Coordinate {
Self {
altitude,
globe,
latitude,
longitude,
precision,
}
}
pub fn altitude(&self) -> &Option<f64> {
&self.altitude
}
pub fn globe(&self) -> &str {
&self.globe
}
pub fn latitude(&self) -> &f64 {
&self.latitude
}
pub fn longitude(&self) -> &f64 {
&self.longitude
}
pub fn precision(&self) -> &Option<f64> {
&self.precision
}
pub fn set_altitude(&mut self, altitude: Option<f64>) {
self.altitude = altitude;
}
pub fn set_globe<S: Into<String>>(&mut self, globe: S) {
self.globe = globe.into();
}
pub fn set_latitude(&mut self, latitude: f64) {
self.latitude = latitude;
}
pub fn set_longitude(&mut self, longitude: f64) {
self.longitude = longitude;
}
pub fn set_precision(&mut self, precision: Option<f64>) {
self.precision = precision;
}
}
#[derive(Debug)]
pub struct MonoLingualText {
language: String,
text: String,
}
impl MonoLingualText {
fn new_from_json(
object: &serde_json::Map<std::string::String, serde_json::Value>,
) -> Result<MonoLingualText, WikibaseError> {
let language = match object["language"].as_str() {
Some(value) => value,
None => return Err(WikibaseError::Serialization("Language".to_string())),
};
let text = match object["text"].as_str() {
Some(value) => value,
None => return Err(WikibaseError::Serialization("Text".to_string())),
};
Ok(Self {
language: language.to_string(),
text: text.to_string(),
})
}
pub fn new<S: Into<String>>(text: S, language: S) -> MonoLingualText {
Self {
text: text.into(),
language: language.into(),
}
}
pub fn language(&self) -> &str {
&self.language
}
pub fn set_language<S: Into<String>>(&mut self, language: S) {
self.language = language.into();
}
pub fn set_text<S: Into<String>>(&mut self, text: S) {
self.text = text.into();
}
pub fn text(&self) -> &str {
&self.text
}
}
#[derive(Debug)]
pub enum EntityType {
Item,
Property,
}
impl EntityType {
pub fn new_from_str(type_string: &str) -> Option<EntityType> {
match type_string {
"item" => Some(EntityType::Item),
"property" => Some(EntityType::Property),
_ => None,
}
}
pub fn new_from_id(id_string: &str) -> Result<EntityType, WikibaseError> {
let first_char = match id_string.chars().nth(0) {
Some(value) => value,
None => {
return Err(WikibaseError::Validation(
"Error getting first character of string".to_string(),
));
}
};
match first_char {
'P' => Ok(EntityType::Property),
'Q' => Ok(EntityType::Item),
_ => Err(WikibaseError::Serialization(
"Error matching entity type".to_string(),
)),
}
}
pub fn string_value(&self) -> String {
match *self {
EntityType::Item => "item".to_string(),
EntityType::Property => "property".to_string(),
}
}
}
#[derive(Debug)]
pub enum StatementRank {
Deprecated,
Normal,
Preferred,
}
#[derive(Debug)]
pub struct Statement {
claim_type: String,
rank: StatementRank,
main_snak: Snak,
qualifiers: Vec<Snak>,
references: Vec<Reference>,
}
#[derive(Debug)]
pub struct DataValue {
value: Value,
value_type: DataValueType,
}
#[derive(Debug)]
pub struct LocaleString {
language: String,
value: String,
}
#[derive(Debug)]
pub struct Entity {
id: String,
labels: Vec<LocaleString>,
descriptions: Vec<LocaleString>,
aliases: Vec<LocaleString>,
claims: Vec<Statement>,
sitelinks: Option<Vec<SiteLink>>,
missing: bool,
}
#[derive(Debug)]
pub struct SiteLink {
badges: Vec<String>,
site: String,
title: String,
}
impl SiteLink {
pub fn new<S: Into<String>>(site: S, title: S, badges: Vec<String>) -> SiteLink {
Self {
badges,
site: site.into(),
title: title.into(),
}
}
}
#[derive(Debug)]
pub struct QuantityValue {
amount: f64,
lower_bound: Option<f64>,
unit: String,
upper_bound: Option<f64>,
}
#[derive(Debug)]
pub struct Reference {
snaks: Vec<Snak>,
}
#[derive(Debug)]
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",
}
}
}
#[derive(Debug)]
pub struct SearchResultEntity {
id: String,
entity_type: EntityType,
label: LocaleString,
description: Option<LocaleString>,
aliases: Vec<LocaleString>,
}
impl SearchResultEntity {
pub fn new<S: Into<String>>(
id: S,
entity_type: EntityType,
label: LocaleString,
description: Option<LocaleString>,
aliases: Vec<LocaleString>,
) -> SearchResultEntity {
Self {
id: id.into(),
entity_type,
label,
description,
aliases,
}
}
pub fn aliases(&self) -> &Vec<LocaleString> {
&self.aliases
}
pub fn description(&self) -> &Option<LocaleString> {
&self.description
}
pub fn id(&self) -> &str {
&self.id
}
pub fn label(&self) -> &LocaleString {
&self.label
}
pub fn set_aliases(&mut self, aliases: Vec<LocaleString>) {
self.aliases = aliases;
}
pub fn set_descriptions(&mut self, description: Option<LocaleString>) {
self.description = description;
}
pub fn set_labels(&mut self, label: LocaleString) {
self.label = label;
}
}
#[derive(Debug)]
pub struct SearchResults {
results: Vec<SearchResultEntity>,
}
impl SearchResults {
pub fn new(results: Vec<SearchResultEntity>) -> SearchResults {
Self { results }
}
pub fn new_from_query(
query: &SearchQuery,
configuration: &Configuration,
) -> Result<SearchResults, WikibaseError> {
let request_result = requests::wikibase_request(&query.url(&configuration), &configuration);
let json_response = match request_result {
Ok(value) => value,
Err(error) => return Err(error),
};
from_json::search_result_entities_from_json(&json_response, &query)
}
pub fn results(&self) -> &Vec<SearchResultEntity> {
&self.results
}
}
#[derive(Debug)]
pub struct Snak {
datatype: String,
property: String,
snak_type: SnakType,
data_value: Option<DataValue>,
}
#[derive(Debug)]
pub struct EntityValue {
entity_type: EntityType,
id: String,
}
#[derive(Debug)]
pub struct TimeValue {
after: u64,
before: u64,
calendarmodel: String,
precision: u64,
time: String,
timezone: u64,
}
impl LocaleString {
fn new<S: Into<String>>(language: S, value: S) -> LocaleString {
Self {
language: language.into(),
value: value.into(),
}
}
pub fn language(&self) -> &str {
&self.language
}
pub fn value(&self) -> &str {
&self.value
}
}
impl QuantityValue {
fn new_from_object(
value: &serde_json::Map<std::string::String, serde_json::Value>,
) -> Result<QuantityValue, WikibaseError> {
let amount = match from_json::float_from_json(&value, "amount") {
Some(value) => value,
None => return Err(WikibaseError::Serialization("Amount".to_string())),
};
let lower_bound = from_json::float_from_json(&value, "lowerBound");
let upper_bound = from_json::float_from_json(&value, "upperBound");
Ok(Self {
amount,
lower_bound,
unit: value["unit"].as_str().unwrap_or_else(|| "").to_string(),
upper_bound,
})
}
pub fn new<S: Into<String>>(
amount: f64,
lower_bound: Option<f64>,
unit: S,
upper_bound: Option<f64>,
) -> QuantityValue {
Self {
amount,
lower_bound,
unit: unit.into(),
upper_bound,
}
}
pub fn amount(&self) -> &f64 {
&self.amount
}
pub fn lower_bound(&self) -> &Option<f64> {
&self.lower_bound
}
pub fn set_amount(&mut self, amount: f64) {
self.amount = amount;
}
pub fn set_lower_bound(&mut self, lower_bound: Option<f64>) {
self.lower_bound = lower_bound;
}
pub fn set_unit<S: Into<String>>(&mut self, unit: S) {
self.unit = unit.into();
}
pub fn set_upper_bound(&mut self, upper_bound: Option<f64>) {
self.upper_bound = upper_bound;
}
pub fn unit(&self) -> &str {
&self.unit
}
pub fn upper_bound(&self) -> &Option<f64> {
&self.upper_bound
}
}
fn entity_from_query(
query: &EntityQuery,
configuration: &Configuration,
) -> Result<Entity, WikibaseError> {
let json_response = requests::wikibase_request(&query.url(&configuration), &configuration)?;
let id = &query.ids()[0];
let json_entity = &json_response["entities"][id];
from_json::entity_from_json(json_entity)
}
impl Entity {
pub fn new(
id: String,
labels: Vec<LocaleString>,
descriptions: Vec<LocaleString>,
aliases: Vec<LocaleString>,
claims: Vec<Statement>,
sitelinks: Option<Vec<SiteLink>>,
missing: bool,
) -> Entity {
Self {
id,
labels,
descriptions,
aliases,
claims,
sitelinks,
missing,
}
}
fn new_empty() -> Entity {
Self {
id: "".to_string(),
labels: vec![],
descriptions: vec![],
aliases: vec![],
claims: vec![],
sitelinks: None,
missing: false,
}
}
pub 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_from_query(&query, &configuration)
}
pub fn new_from_ids(
ids: Vec<String>,
configuration: &Configuration,
) -> Result<Entity, WikibaseError> {
let query = EntityQuery::new(ids, "en");
entity_from_query(&query, &configuration)
}
pub fn new_from_query(
query: &EntityQuery,
configuration: &Configuration,
) -> Result<Entity, WikibaseError> {
entity_from_query(&query, &configuration)
}
pub fn aliases(&self) -> &Vec<LocaleString> {
&self.aliases
}
pub fn label_in_locale(&self, locale: &str) -> Option<&str> {
for label in &self.labels {
if label.language() == locale {
return Some(label.value());
}
}
None
}
pub fn description_in_locale(&self, locale: &str) -> Option<&str> {
for description in &self.descriptions {
if description.language() == locale {
return Some(description.value());
}
}
None
}
fn set_aliases(&mut self, aliases: Vec<LocaleString>) {
self.aliases = aliases;
}
fn set_claims(&mut self, claims: Vec<Statement>) {
self.claims = claims;
}
fn set_descriptions(&mut self, descriptions: Vec<LocaleString>) {
self.descriptions = descriptions;
}
fn set_id(&mut self, id: String) {
self.id = id;
}
fn set_labels(&mut self, labels: Vec<LocaleString>) {
self.labels = labels;
}
fn set_missing(&mut self, missing: bool) {
self.missing = missing;
}
pub fn set_sitelinks(&mut self, sitelinks: Option<Vec<SiteLink>>) {
self.sitelinks = sitelinks;
}
pub fn sitelinks(&self) -> &Option<Vec<SiteLink>> {
&self.sitelinks
}
pub fn claims(&self) -> &Vec<Statement> {
&self.claims
}
pub fn id(&self) -> &str {
&self.id
}
pub fn missing(&self) -> &bool {
&self.missing
}
}
impl TimeValue {
fn new_from_object(
value: &serde_json::Map<std::string::String, serde_json::Value>,
) -> TimeValue {
Self {
after: value["after"].as_u64().unwrap_or_else(|| 0),
before: value["before"].as_u64().unwrap_or_else(|| 0),
calendarmodel: value["calendarmodel"]
.as_str()
.unwrap_or_else(|| "")
.to_string(),
precision: value["precision"].as_u64().unwrap_or_else(|| 0),
time: value["time"].as_str().unwrap_or_else(|| "").to_string(),
timezone: value["timezone"].as_u64().unwrap_or_else(|| 0),
}
}
pub fn new<S: Into<String>>(
after: u64,
before: u64,
calendarmodel: S,
precision: u64,
time: S,
timezone: u64,
) -> TimeValue {
Self {
after,
before,
calendarmodel: calendarmodel.into(),
precision,
time: time.into(),
timezone,
}
}
pub fn after(&self) -> &u64 {
&self.after
}
pub fn before(&self) -> &u64 {
&self.before
}
pub fn calendarmodel(&self) -> &str {
&self.calendarmodel
}
pub fn precision(&self) -> &u64 {
&self.precision
}
pub fn set_after(&mut self, after: u64) {
self.after = after;
}
pub fn set_before(&mut self, before: u64) {
self.before = before;
}
pub fn set_calendarmodel<S: Into<String>>(&mut self, calendarmodel: S) {
self.calendarmodel = calendarmodel.into();
}
pub fn set_precision(&mut self, precision: u64) {
self.precision = precision;
}
pub fn set_time<S: Into<String>>(&mut self, time: S) {
self.time = time.into();
}
pub fn set_timezone(&mut self, timezone: u64) {
self.timezone = timezone;
}
pub fn time(&self) -> &str {
&self.time
}
pub fn timezone(&self) -> &u64 {
&self.timezone
}
}
impl EntityValue {
pub fn new<S: Into<String>>(entity_type: EntityType, id: S) -> EntityValue {
Self {
entity_type,
id: id.into(),
}
}
pub fn new_from_object(
value: &serde_json::Map<std::string::String, serde_json::Value>,
) -> Result<EntityValue, WikibaseError> {
let entity_type_string = match value["entity-type"].as_str() {
Some(value) => value,
None => {
return Err(WikibaseError::Serialization(
"Entity type is not a string".to_string(),
));
}
};
let entity_type = match EntityType::new_from_str(entity_type_string) {
Some(value) => value,
None => {
return Err(WikibaseError::Serialization(
"Entity type did not match".to_string(),
));
}
};
let id = match value["id"].as_str() {
Some(value) => value,
None => {
return Err(WikibaseError::Serialization(
"Id is not a string".to_string(),
));
}
};
Ok(Self {
entity_type,
id: id.to_string(),
})
}
pub fn entity_type(&self) -> &EntityType {
&self.entity_type
}
pub fn id(&self) -> &str {
&self.id
}
pub fn set_entity_type(&mut self, entity_type: EntityType) {
self.entity_type = entity_type;
}
pub fn set_id<S: Into<String>>(&mut self, id: S) {
self.id = id.into();
}
}
impl Reference {
pub fn new(snaks: Vec<Snak>) -> Reference {
Self { snaks }
}
pub fn set_snaks(&mut self, snaks: Vec<Snak>) {
self.snaks = snaks;
}
}
impl DataValue {
pub fn new(value_type: DataValueType, value: Value) -> DataValue {
Self { value_type, value }
}
pub fn set_value(&mut self, value: Value) {
self.value = value;
}
pub fn set_value_type(&mut self, value_type: DataValueType) {
self.value_type = value_type;
}
pub fn value(&self) -> &Value {
&self.value
}
pub fn value_type(&self) -> &DataValueType {
&self.value_type
}
}
impl Snak {
pub fn new<S: Into<String>>(
datatype: S,
property: S,
snak_type: SnakType,
data_value: Option<DataValue>,
) -> Snak {
Self {
datatype: datatype.into(),
property: property.into(),
snak_type,
data_value,
}
}
pub fn datatype(&self) -> &str {
&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: &str) {
self.datatype = datatype.to_string();
}
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 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,
}
}
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: &str) {
self.main_snak.datatype = datatype.to_string();
}
pub fn property(&self) -> &str {
&self.main_snak.property
}
pub fn set_property(&mut self, property: &str) {
self.main_snak.property = property.to_string();
}
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
}
}