use crate::models;
use serde::de::DeserializeOwned;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct Configuration {
pub base_path: String,
pub user_agent: Option<String>,
pub client: reqwest::Client,
pub basic_auth: Option<BasicAuth>,
pub oauth_access_token: Option<String>,
pub bearer_access_token: Option<String>,
pub api_key: Option<ApiKey>,
}
pub type BasicAuth = (String, Option<String>);
#[derive(Debug, Clone)]
pub struct ApiKey {
pub prefix: Option<String>,
pub key: String,
}
impl Configuration {
pub fn new() -> Configuration {
Configuration::default()
}
}
impl Default for Configuration {
fn default() -> Self {
Configuration {
base_path: "https://catalog.data.gov/api/3".to_owned(),
user_agent: Some(concat!("data-gov-rs/", env!("CARGO_PKG_VERSION")).to_owned()),
client: reqwest::Client::new(),
basic_auth: None,
oauth_access_token: None,
bearer_access_token: None,
api_key: None,
}
}
}
pub struct CkanClient {
configuration: Arc<Configuration>,
}
impl std::fmt::Debug for CkanClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CkanClient")
.field("base_path", &self.configuration.base_path)
.finish()
}
}
#[derive(Debug)]
pub enum CkanError {
RequestError(Box<dyn std::error::Error + Send + Sync>),
ParseError(serde_json::Error),
ApiError {
status: u16,
message: String,
},
}
impl std::fmt::Display for CkanError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CkanError::RequestError(e) => write!(f, "Request error: {}", e),
CkanError::ParseError(e) => write!(f, "Parse error: {}", e),
CkanError::ApiError { status, message } => {
write!(f, "CKAN API error ({}): {}", status, message)
}
}
}
}
impl std::error::Error for CkanError {}
impl CkanClient {
pub fn new(configuration: Arc<Configuration>) -> Self {
Self { configuration }
}
async fn call_action<T: DeserializeOwned>(
&self,
action: &str,
params: &[(&str, &str)],
) -> Result<T, CkanError> {
let url = format!("{}/action/{}", self.configuration.base_path, action);
let response = self
.configuration
.client
.get(&url)
.query(params)
.send()
.await
.map_err(|e| CkanError::RequestError(Box::new(e)))?;
if !response.status().is_success() {
let status = response.status().as_u16();
let error_text = response
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
return Err(CkanError::ApiError {
status,
message: error_text,
});
}
let wrapper: models::ActionResponse = response
.json()
.await
.map_err(|e| CkanError::RequestError(Box::new(e)))?;
if !wrapper.success {
return Err(CkanError::ApiError {
status: 400,
message: "CKAN API reported failure".to_string(),
});
}
match wrapper.result {
Some(value) => serde_json::from_value(value).map_err(CkanError::ParseError),
None => Err(CkanError::ApiError {
status: 500,
message: "No result data in API response".to_string(),
}),
}
}
pub async fn package_search(
&self,
q: Option<&str>,
rows: Option<i32>,
start: Option<i32>,
fq: Option<&str>,
) -> Result<models::PackageSearchResult, CkanError> {
let rows_str = rows.map(|r| r.to_string());
let start_str = start.map(|s| s.to_string());
let mut params: Vec<(&str, &str)> = Vec::new();
if let Some(q) = q {
params.push(("q", q));
}
if let Some(ref r) = rows_str {
params.push(("rows", r));
}
if let Some(ref s) = start_str {
params.push(("start", s));
}
if let Some(fq) = fq {
params.push(("fq", fq));
}
self.call_action("package_search", ¶ms).await
}
pub async fn package_show(&self, id: &str) -> Result<models::Package, CkanError> {
self.call_action("package_show", &[("id", id)]).await
}
pub async fn organization_list(
&self,
sort: Option<&str>,
limit: Option<i32>,
offset: Option<i32>,
) -> Result<Vec<String>, CkanError> {
let limit_str = limit.map(|l| l.to_string());
let offset_str = offset.map(|o| o.to_string());
let mut params: Vec<(&str, &str)> = Vec::new();
if let Some(sort) = sort {
params.push(("sort", sort));
}
if let Some(ref l) = limit_str {
params.push(("limit", l));
}
if let Some(ref o) = offset_str {
params.push(("offset", o));
}
self.call_action("organization_list", ¶ms).await
}
pub async fn group_list(
&self,
sort: Option<&str>,
limit: Option<i32>,
offset: Option<i32>,
) -> Result<Vec<String>, CkanError> {
let limit_str = limit.map(|l| l.to_string());
let offset_str = offset.map(|o| o.to_string());
let mut params: Vec<(&str, &str)> = Vec::new();
if let Some(sort) = sort {
params.push(("sort", sort));
}
if let Some(ref l) = limit_str {
params.push(("limit", l));
}
if let Some(ref o) = offset_str {
params.push(("offset", o));
}
self.call_action("group_list", ¶ms).await
}
pub async fn dataset_autocomplete(
&self,
incomplete: Option<&str>,
limit: Option<i32>,
) -> Result<Vec<models::DatasetAutocomplete>, CkanError> {
let limit_str = limit.map(|l| l.to_string());
let mut params: Vec<(&str, &str)> = Vec::new();
if let Some(q) = incomplete {
params.push(("q", q));
}
if let Some(ref l) = limit_str {
params.push(("limit", l));
}
self.call_action("package_autocomplete", ¶ms).await
}
pub async fn tag_autocomplete(
&self,
incomplete: Option<&str>,
limit: Option<i32>,
vocabulary_id: Option<&str>,
) -> Result<Vec<String>, CkanError> {
let limit_str = limit.map(|l| l.to_string());
let mut params: Vec<(&str, &str)> = Vec::new();
if let Some(q) = incomplete {
params.push(("q", q));
}
if let Some(ref l) = limit_str {
params.push(("limit", l));
}
if let Some(vid) = vocabulary_id {
params.push(("vocabulary_id", vid));
}
self.call_action("tag_autocomplete", ¶ms).await
}
pub async fn user_autocomplete(
&self,
q: Option<&str>,
limit: Option<i32>,
ignore_self: Option<bool>,
) -> Result<Vec<models::UserAutocomplete>, CkanError> {
let limit_str = limit.map(|l| l.to_string());
let ignore_self_str = ignore_self.map(|b| b.to_string());
let mut params: Vec<(&str, &str)> = Vec::new();
if let Some(q) = q {
params.push(("q", q));
}
if let Some(ref l) = limit_str {
params.push(("limit", l));
}
if let Some(ref i) = ignore_self_str {
params.push(("ignore_self", i));
}
self.call_action("user_autocomplete", ¶ms).await
}
pub async fn group_autocomplete(
&self,
q: Option<&str>,
limit: Option<i32>,
) -> Result<Vec<models::GroupAutocomplete>, CkanError> {
let limit_str = limit.map(|l| l.to_string());
let mut params: Vec<(&str, &str)> = Vec::new();
if let Some(q) = q {
params.push(("q", q));
}
if let Some(ref l) = limit_str {
params.push(("limit", l));
}
self.call_action("group_autocomplete", ¶ms).await
}
pub async fn organization_autocomplete(
&self,
q: Option<&str>,
limit: Option<i32>,
) -> Result<Vec<models::OrganizationAutocomplete>, CkanError> {
let limit_str = limit.map(|l| l.to_string());
let mut params: Vec<(&str, &str)> = Vec::new();
if let Some(q) = q {
params.push(("q", q));
}
if let Some(ref l) = limit_str {
params.push(("limit", l));
}
self.call_action("organization_autocomplete", ¶ms).await
}
pub async fn resource_format_autocomplete(
&self,
incomplete: Option<&str>,
limit: Option<i32>,
) -> Result<Vec<String>, CkanError> {
let limit_str = limit.map(|l| l.to_string());
let mut params: Vec<(&str, &str)> = Vec::new();
if let Some(q) = incomplete {
params.push(("q", q));
}
if let Some(ref l) = limit_str {
params.push(("limit", l));
}
self.call_action("format_autocomplete", ¶ms).await
}
}