use std::{fmt::Display, ops::Add};
use crate::error::Error;
#[derive(Clone, Copy, Debug, Default)]
pub enum Protocol {
HTTP,
#[default]
HTTPS,
}
impl Display for Protocol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::HTTP => write!(f, "http"),
Self::HTTPS => write!(f, "https"),
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub enum WSEntryPoint {
#[default]
Main,
}
impl Display for WSEntryPoint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Main => write!(f, "data-api.ecb.europa.eu/service"),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum Resource {
#[default]
Data,
Schema,
MetadataDataStructure,
MetadataMetadataStructure,
MetadataCategoryScheme,
MetadataConceptScheme,
MetadataCodeList,
MetadataHierarchicalCodeList,
MetadataOrganisationsScheme,
MetadataAgencyScheme,
MetadataDataProvidersScheme,
MetadataDataConsumerScheme,
MetadataOrganisationUnitScheme,
MetadataDataFlow,
MetadataMetadataFlow,
MetadataReportingTaxonomy,
MetadataProvisionAgreement,
MetadataStructureSet,
MetadataProcess,
MetadataCategorisation,
MetadataContentConstraint,
MetadataAttachmentConstraint,
MetadataStructure,
}
impl Resource {
pub fn all_data_resources() -> Vec<Self> {
vec![Self::Data]
}
pub fn all_schema_resources() -> Vec<Self> {
vec![Self::Schema]
}
pub fn all_metadata_resources() -> Vec<Self> {
vec![
Self::MetadataDataStructure,
Self::MetadataMetadataStructure,
Self::MetadataCategoryScheme,
Self::MetadataConceptScheme,
Self::MetadataCodeList,
Self::MetadataHierarchicalCodeList,
Self::MetadataOrganisationsScheme,
Self::MetadataAgencyScheme,
Self::MetadataDataProvidersScheme,
Self::MetadataDataConsumerScheme,
Self::MetadataOrganisationUnitScheme,
Self::MetadataDataFlow,
Self::MetadataMetadataFlow,
Self::MetadataReportingTaxonomy,
Self::MetadataProvisionAgreement,
Self::MetadataStructureSet,
Self::MetadataProcess,
Self::MetadataCategorisation,
Self::MetadataContentConstraint,
Self::MetadataAttachmentConstraint,
Self::MetadataStructure,
]
}
}
impl Display for Resource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Data => write!(f, "data"),
Self::Schema => write!(f, "schema"),
Self::MetadataDataStructure => write!(f, "datastructure"),
Self::MetadataMetadataStructure => write!(f, "metadatastructure"),
Self::MetadataCategoryScheme => write!(f, "categoryscheme"),
Self::MetadataConceptScheme => write!(f, "Conceptscheme"), Self::MetadataCodeList => write!(f, "codelist"),
Self::MetadataHierarchicalCodeList => write!(f, "hierarchicalcodelist"),
Self::MetadataOrganisationsScheme => write!(f, "organisationsscheme"),
Self::MetadataAgencyScheme => write!(f, "agencyscheme"),
Self::MetadataDataProvidersScheme => write!(f, "dataprovidersscheme"),
Self::MetadataDataConsumerScheme => write!(f, "dataconsumerscheme"),
Self::MetadataOrganisationUnitScheme => write!(f, "organisationunitscheme"),
Self::MetadataDataFlow => write!(f, "dataflow"),
Self::MetadataMetadataFlow => write!(f, "metadataflow"),
Self::MetadataReportingTaxonomy => write!(f, "reportingtaxonomy"),
Self::MetadataProvisionAgreement => write!(f, "provisionagreement"),
Self::MetadataStructureSet => write!(f, "structureset"),
Self::MetadataProcess => write!(f, "process"),
Self::MetadataCategorisation => write!(f, "categorisation"),
Self::MetadataContentConstraint => write!(f, "contentconstraint"),
Self::MetadataAttachmentConstraint => write!(f, "attachmentconstraint"),
Self::MetadataStructure => write!(f, "structure"),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct FlowRef {
pub agency_id: Option<String>,
pub flow_id: String,
pub version: Option<String>,
}
impl Display for FlowRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let agency_id: String = self.agency_id.as_ref().map_or("all".to_owned(), |v| v.clone() );
let flow_id: String = self.flow_id.clone();
let version: String = self.version.as_ref().map_or("latest".to_owned(), |v| v.clone() );
write!(f, "{agency_id},{flow_id},{version}")
}
}
#[derive(Clone, Copy, Debug, Default)]
pub enum Context {
#[default]
DataStructure,
DataFlow,
ProvisionAgreement,
}
impl Display for Context {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DataStructure => write!(f, "datastructure"),
Self::DataFlow => write!(f, "dataflow"),
Self::ProvisionAgreement => write!(f, "provisionagreement"),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Query {
pub protocol: Protocol,
pub ws_entry_point: WSEntryPoint,
pub resource: Resource,
pub agency_id: Option<String>,
pub resource_id: Option<String>,
pub version: Option<String>,
pub flow_ref: Option<FlowRef>,
pub series_key: Option<String>,
pub context: Option<Context>,
}
impl Query {
pub fn new() -> Self {
Self::default()
}
pub fn protocol(mut self, protocol: Protocol) -> Self {
self.protocol = protocol;
self
}
pub fn ws_entry_point(mut self, ws_entry_point: WSEntryPoint) -> Self {
self.ws_entry_point = ws_entry_point;
self
}
pub fn resource(mut self, resource: Resource) -> Self {
self.resource = resource;
self
}
pub fn flow_ref(mut self, flow_ref: FlowRef) -> Self {
self.flow_ref = Some(flow_ref);
self
}
pub fn series_key(mut self, series_key: &str) -> Self {
self.series_key = Some(series_key.to_owned());
self
}
pub fn context(mut self, context: Context) -> Self {
self.context = Some(context);
self
}
pub fn agency_id(mut self, agency_id: &str) -> Self {
self.agency_id = Some(agency_id.to_owned());
self
}
pub fn resource_id(mut self, resource_id: &str) -> Self {
self.resource_id = Some(resource_id.to_owned());
self
}
pub fn version(mut self, version: &str) -> Self {
self.version = Some(version.to_owned());
self
}
pub fn validate_query(&self, permitted_resources: Vec<Resource>) -> Result<(), Error> {
if !permitted_resources.contains(&self.resource) {
return Err(Error::WrongResourceRequested);
}
Ok(())
}
pub fn generate_url(&self) -> Result<String, Error> {
let mut query: String = format!("{}://{}/{}", self.protocol.to_string(), self.ws_entry_point.to_string(), self.resource.to_string());
query = match self.resource {
Resource::Data => {
query
.add("/").add(&self.flow_ref.as_ref().ok_or(Error::MissingQueryAttribute { attribute: "reference to dataflow".to_owned(), })?.to_string())
.add("/").add(&self.series_key.as_ref().ok_or(Error::MissingQueryAttribute { attribute: "series key".to_owned(), })?)
},
Resource::Schema => {
query
.add("/").add(&self.context.as_ref().ok_or(Error::MissingQueryAttribute { attribute: "schema context".to_owned() })?.to_string())
.add("/").add(&self.agency_id.as_ref().ok_or(Error::MissingQueryAttribute { attribute: "agency ID".to_owned() })?)
.add("/").add(&self.resource_id.as_ref().ok_or(Error::MissingQueryAttribute { attribute: "resource ID".to_owned() })?)
.add("/").add(&self.version.as_ref().ok_or(Error::MissingQueryAttribute { attribute: "version number".to_owned() })?)
},
_ => {
query
.add("/").add(&self.agency_id.as_ref().ok_or(Error::MissingQueryAttribute { attribute: "agency ID".to_owned() })?)
.add("/").add(&self.resource_id.as_ref().ok_or(Error::MissingQueryAttribute { attribute: "resource ID".to_owned() })?)
.add("/").add(&self.version.as_ref().ok_or(Error::MissingQueryAttribute { attribute: "version number".to_owned() })?)
},
};
Ok(query)
}
}
#[cfg(test)]
mod tests {
use crate::query::{Resource, FlowRef, Context, Query};
#[test]
fn unit_test_generate_url() -> () {
let query: Query = Query::new()
.flow_ref(FlowRef { agency_id: None, flow_id: String::from("EXR"), version: None, })
.series_key("M.USD.EUR.SP00.A");
assert_eq!(query.generate_url().unwrap(), "https://data-api.ecb.europa.eu/service/data/all,EXR,latest/M.USD.EUR.SP00.A".to_owned());
let query: Query = Query::new()
.resource(Resource::Schema)
.context(Context::DataStructure)
.agency_id("ECB")
.resource_id("ECB_EXR1")
.version("1.0");
assert_eq!(query.generate_url().unwrap(), "https://data-api.ecb.europa.eu/service/schema/datastructure/ECB/ECB_EXR1/1.0".to_owned());
let query: Query = Query::new()
.resource(Resource::MetadataCodeList)
.agency_id("all")
.resource_id("all")
.version("latest");
assert_eq!(query.generate_url().unwrap(), "https://data-api.ecb.europa.eu/service/codelist/all/all/latest".to_owned());
}
}