use crate::error::{MetadataError, Result};
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Catalog {
pub title: Vec<LiteralString>,
pub description: Vec<LiteralString>,
pub publisher: Agent,
pub dataset: Vec<Dataset>,
pub homepage: Option<Url>,
pub language: Vec<String>,
pub license: Option<Url>,
pub issued: Option<chrono::DateTime<chrono::Utc>>,
pub modified: Option<chrono::DateTime<chrono::Utc>>,
pub spatial: Vec<Location>,
pub temporal: Vec<PeriodOfTime>,
pub themes: Vec<String>,
pub record: Vec<CatalogRecord>,
pub service: Vec<DataService>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Dataset {
pub title: Vec<LiteralString>,
pub description: Vec<LiteralString>,
pub identifier: Vec<String>,
pub keyword: Vec<String>,
pub theme: Vec<String>,
pub contact_point: Vec<ContactPoint>,
pub publisher: Option<Agent>,
pub creator: Option<Agent>,
pub issued: Option<chrono::DateTime<chrono::Utc>>,
pub modified: Option<chrono::DateTime<chrono::Utc>>,
pub language: Vec<String>,
pub landing_page: Vec<Url>,
pub access_rights: Option<String>,
pub conforms_to: Vec<String>,
pub distribution: Vec<Distribution>,
pub accrual_periodicity: Option<String>,
pub spatial: Vec<Location>,
pub temporal: Vec<PeriodOfTime>,
pub version: Option<String>,
pub version_notes: Vec<String>,
pub qualified_relation: Vec<Relationship>,
}
pub struct DatasetBuilder {
title: Vec<LiteralString>,
description: Vec<LiteralString>,
identifier: Vec<String>,
keyword: Vec<String>,
theme: Vec<String>,
contact_point: Vec<ContactPoint>,
publisher: Option<Agent>,
creator: Option<Agent>,
issued: Option<chrono::DateTime<chrono::Utc>>,
modified: Option<chrono::DateTime<chrono::Utc>>,
language: Vec<String>,
landing_page: Vec<Url>,
access_rights: Option<String>,
conforms_to: Vec<String>,
distribution: Vec<Distribution>,
accrual_periodicity: Option<String>,
spatial: Vec<Location>,
temporal: Vec<PeriodOfTime>,
version: Option<String>,
version_notes: Vec<String>,
qualified_relation: Vec<Relationship>,
}
impl Dataset {
pub fn builder() -> DatasetBuilder {
DatasetBuilder {
title: Vec::new(),
description: Vec::new(),
identifier: Vec::new(),
keyword: Vec::new(),
theme: Vec::new(),
contact_point: Vec::new(),
publisher: None,
creator: None,
issued: None,
modified: None,
language: Vec::new(),
landing_page: Vec::new(),
access_rights: None,
conforms_to: Vec::new(),
distribution: Vec::new(),
accrual_periodicity: None,
spatial: Vec::new(),
temporal: Vec::new(),
version: None,
version_notes: Vec::new(),
qualified_relation: Vec::new(),
}
}
}
impl DatasetBuilder {
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title.push(LiteralString {
value: title.into(),
language: None,
});
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description.push(LiteralString {
value: description.into(),
language: None,
});
self
}
pub fn keyword(mut self, keyword: impl Into<String>) -> Self {
self.keyword.push(keyword.into());
self
}
pub fn theme(mut self, theme: impl Into<String>) -> Self {
self.theme.push(theme.into());
self
}
pub fn publisher(mut self, publisher: Agent) -> Self {
self.publisher = Some(publisher);
self
}
pub fn distribution(mut self, distribution: Distribution) -> Self {
self.distribution.push(distribution);
self
}
pub fn build(self) -> Result<Dataset> {
if self.title.is_empty() {
return Err(MetadataError::MissingField("title".to_string()));
}
if self.description.is_empty() {
return Err(MetadataError::MissingField("description".to_string()));
}
Ok(Dataset {
title: self.title,
description: self.description,
identifier: self.identifier,
keyword: self.keyword,
theme: self.theme,
contact_point: self.contact_point,
publisher: self.publisher,
creator: self.creator,
issued: self.issued,
modified: self.modified,
language: self.language,
landing_page: self.landing_page,
access_rights: self.access_rights,
conforms_to: self.conforms_to,
distribution: self.distribution,
accrual_periodicity: self.accrual_periodicity,
spatial: self.spatial,
temporal: self.temporal,
version: self.version,
version_notes: self.version_notes,
qualified_relation: self.qualified_relation,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Distribution {
pub access_url: Url,
pub download_url: Option<Url>,
pub title: Vec<LiteralString>,
pub description: Vec<LiteralString>,
pub format: Option<String>,
pub media_type: Option<String>,
pub byte_size: Option<u64>,
pub checksum: Option<Checksum>,
pub compression_format: Option<String>,
pub package_format: Option<String>,
pub issued: Option<chrono::DateTime<chrono::Utc>>,
pub modified: Option<chrono::DateTime<chrono::Utc>>,
pub license: Option<Url>,
pub rights: Option<String>,
pub access_service: Vec<DataService>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DataService {
pub title: Vec<LiteralString>,
pub endpoint_url: Vec<Url>,
pub endpoint_description: Vec<Url>,
pub description: Vec<LiteralString>,
pub service_type: Option<String>,
pub serves_dataset: Vec<Dataset>,
pub contact_point: Vec<ContactPoint>,
pub license: Option<Url>,
pub access_rights: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CatalogRecord {
pub title: Vec<LiteralString>,
pub description: Vec<LiteralString>,
pub issued: Option<chrono::DateTime<chrono::Utc>>,
pub modified: chrono::DateTime<chrono::Utc>,
pub primary_topic: Option<Dataset>,
pub conforms_to: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Agent {
pub name: String,
pub agent_type: Option<AgentType>,
}
impl Agent {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
agent_type: None,
}
}
pub fn organization(name: impl Into<String>) -> Self {
Self {
name: name.into(),
agent_type: Some(AgentType::Organization),
}
}
pub fn person(name: impl Into<String>) -> Self {
Self {
name: name.into(),
agent_type: Some(AgentType::Person),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum AgentType {
Organization,
Person,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContactPoint {
pub name: Option<String>,
pub email: Option<String>,
pub url: Option<Url>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LiteralString {
pub value: String,
pub language: Option<String>,
}
impl LiteralString {
pub fn new(value: impl Into<String>) -> Self {
Self {
value: value.into(),
language: None,
}
}
pub fn with_language(value: impl Into<String>, language: impl Into<String>) -> Self {
Self {
value: value.into(),
language: Some(language.into()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Location {
pub geometry: Option<String>,
pub bbox: Option<BoundingBox>,
pub centroid: Option<Point>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct BoundingBox {
pub west: f64,
pub east: f64,
pub south: f64,
pub north: f64,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Point {
pub lon: f64,
pub lat: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PeriodOfTime {
pub start_date: Option<chrono::DateTime<chrono::Utc>>,
pub end_date: Option<chrono::DateTime<chrono::Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Checksum {
pub algorithm: String,
pub checksum_value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Relationship {
pub relation: Url,
pub had_role: Option<String>,
}
impl Dataset {
pub fn to_jsonld(&self) -> Result<String> {
let mut map = serde_json::json!({
"@context": "https://www.w3.org/ns/dcat",
"@type": "Dataset",
});
let dataset_json =
serde_json::to_value(self).map_err(|e| MetadataError::JsonError(e.to_string()))?;
if let serde_json::Value::Object(obj) = dataset_json {
if let serde_json::Value::Object(ref mut context_map) = map {
context_map.extend(obj);
}
}
serde_json::to_string_pretty(&map).map_err(|e| MetadataError::JsonError(e.to_string()))
}
pub fn to_json(&self) -> Result<String> {
serde_json::to_string_pretty(&self).map_err(|e| MetadataError::JsonError(e.to_string()))
}
pub fn from_json(json: &str) -> Result<Self> {
serde_json::from_str(json).map_err(|e| MetadataError::JsonError(e.to_string()))
}
}
impl Catalog {
pub fn to_jsonld(&self) -> Result<String> {
let mut map = serde_json::json!({
"@context": "https://www.w3.org/ns/dcat",
"@type": "Catalog",
});
let catalog_json =
serde_json::to_value(self).map_err(|e| MetadataError::JsonError(e.to_string()))?;
if let serde_json::Value::Object(obj) = catalog_json {
if let serde_json::Value::Object(ref mut context_map) = map {
context_map.extend(obj);
}
}
serde_json::to_string_pretty(&map).map_err(|e| MetadataError::JsonError(e.to_string()))
}
pub fn to_json(&self) -> Result<String> {
serde_json::to_string_pretty(&self).map_err(|e| MetadataError::JsonError(e.to_string()))
}
pub fn from_json(json: &str) -> Result<Self> {
serde_json::from_str(json).map_err(|e| MetadataError::JsonError(e.to_string()))
}
}