Skip to main content

CkanClient

Struct CkanClient 

Source
pub struct CkanClient { /* private fields */ }
Expand description

Async CKAN client focused on data.gov’s read APIs.

CkanClient wraps the generated request glue and exposes ergonomic async methods for popular CKAN endpoints such as package_search, package_show, organization listings, and autocomplete helpers. The client is cheap to clone and share across tasks because it only holds an Arc<Configuration>.

use data_gov_ckan::{CkanClient, Configuration};
use std::sync::Arc;

let client = CkanClient::new(Arc::new(Configuration::default()));
let results = client.package_search(Some("climate"), Some(5), None, None).await?;
println!("{} datasets", results.count.unwrap_or(0));

Implementations§

Source§

impl CkanClient

Source

pub fn new(configuration: Arc<Configuration>) -> Self

Create a new CKAN client instance

Creates a client configured to work with a specific CKAN instance. For data.gov, use the base URL: https://catalog.data.gov/api/3

§Arguments
  • configuration - API configuration including base URL, user agent, and credentials
§Examples

// Basic client for read-only operations
let config = Arc::new(Configuration {
    base_path: "https://catalog.data.gov/api/3".to_string(),
    user_agent: Some("my-rust-app/1.0".to_string()),
    client: reqwest::Client::new(),
    basic_auth: None,
    oauth_access_token: None,
    bearer_access_token: None,
    api_key: None,
});

let client = CkanClient::new(config);

// Client with API key for write operations
let authenticated_config = Arc::new(Configuration {
    base_path: "https://catalog.data.gov/api/3".to_string(),
    user_agent: Some("my-rust-app/1.0".to_string()),
    client: reqwest::Client::new(),
    basic_auth: None,
    oauth_access_token: None,
    bearer_access_token: None,
    api_key: Some(ApiKey {
        prefix: None,
        key: "your-api-key-here".to_string(),
    }),
});

let auth_client = CkanClient::new(authenticated_config);

Search for datasets (packages) with advanced filtering and faceting

This is the primary method for discovering datasets in the CKAN catalog. It provides powerful search capabilities with full-text search, filtering, faceting, and pagination.

§Arguments
  • q - Search query string (searches title, description, tags, etc.)
  • rows - Maximum number of results to return (default: 10, max typically: 1000)
  • start - Starting offset for pagination (0-based)
  • fq - Additional filter queries in Solr format
§Returns

Returns search results with datasets, facets, and pagination information.

§Examples

// Basic text search
let results = client.package_search(
    Some("climate change"),
    Some(20),
    Some(0),
    None
).await?;

println!("Found {} total datasets", results.count.unwrap_or(0));
for package in results.results.unwrap_or_default() {
    println!("Title: {}", package.title.unwrap_or_default());
    println!("Organization: {}", package.organization.as_ref()
        .and_then(|org| org.title.as_deref())
        .unwrap_or("Unknown"));
}

// Search with organization filter
let epa_datasets = client.package_search(
    Some("water quality"),
    Some(10),
    Some(0),
    Some("organization:epa-gov")
).await?;

// Search with multiple filters
let recent_climate_data = client.package_search(
    Some("climate"),
    Some(15),
    Some(0),
    Some("res_format:CSV AND metadata_modified:[2020-01-01T00:00:00Z TO NOW]")
).await?;
§Pagination
// Paginate through all results
let mut start = 0;
let page_size = 50;

loop {
    let results = client.package_search(
        Some("energy"),
        Some(page_size),
        Some(start),
        None
    ).await?;

    let packages = results.results.unwrap_or_default();
    if packages.is_empty() {
        break;
    }

    // Process this page of results
    for package in packages {
        println!("Processing: {}", package.name);
    }

    start += page_size;
}
§Advanced Filtering

The fq parameter supports Solr query syntax for advanced filtering. The q and fq parameters are passed through to CKAN’s Solr-backed package_search endpoint, so you can use familiar Solr constructs such as:

  • organization:epa-gov - Filter by organization
  • res_format:CSV - Filter by resource format
  • tags:healthcare - Filter by tags
  • metadata_modified:[2020-01-01T00:00:00Z TO NOW] - Date ranges
  • Combine with AND, OR, NOT operators

Examples:

  • Text search with wildcard: q=climat*
  • Phrase search: q="air quality"
  • Complex filter: fq=organization:epa-gov AND res_format:CSV
Source

pub async fn package_show(&self, id: &str) -> Result<Package, CkanError>

Retrieve a specific dataset by its ID or name

Fetches complete metadata for a single dataset, including all resources, tags, organization details, and custom metadata fields.

§Arguments
  • id - Dataset ID (UUID) or name (URL-friendly slug)
§Returns

Returns the complete dataset record with all metadata and resources.

§Examples

// Get dataset by name
let dataset = client.package_show("consumer-complaint-database").await?;

println!("Dataset: {}", dataset.title.unwrap_or_default());
println!("Description: {}", dataset.notes.unwrap_or_default());

// List all resources in the dataset
println!("Resources:");
for resource in dataset.resources.unwrap_or_default() {
    println!("  - {} ({})",
        resource.name.as_deref().unwrap_or("Unnamed"),
        resource.format.as_deref().unwrap_or("Unknown format")
    );

    if let Some(url) = resource.url {
        println!("    URL: {}", url);
    }

    if let Some(size) = resource.size {
        println!("    Size: {} bytes", size);
    }
}

// Show dataset tags
if let Some(tags) = dataset.tags {
    let tag_names: Vec<String> = tags.into_iter()
        .filter_map(|tag| tag.display_name)
        .collect();
    println!("Tags: {}", tag_names.join(", "));
}

// Show organization
if let Some(org) = dataset.organization {
    println!("Organization: {}", org.title.unwrap_or_default());
}

// Get dataset by UUID
let dataset_by_id = client.package_show("a1b2c3d4-e5f6-7890-abcd-ef1234567890").await?;
§Error Handling
match client.package_show("nonexistent-dataset").await {
    Ok(dataset) => {
        println!("Found dataset: {}", dataset.title.unwrap_or_default());
    },
    Err(CkanError::ApiError { status: 404, .. }) => {
        println!("Dataset not found");
    },
    Err(e) => {
        println!("Other error: {}", e);
    }
}
§Dataset Metadata

The returned dataset includes rich metadata:

  • Basic Info: Title, description, notes, license
  • Resources: Files, APIs, documentation associated with dataset
  • Organization: Publishing agency/department information
  • Tags: Subject tags and keywords for discovery
  • Temporal: Creation date, modification date, temporal coverage
  • Spatial: Geographic coverage and bounding boxes
  • Custom Fields: Agency-specific metadata extensions
Source

pub async fn organization_list( &self, sort: Option<&str>, limit: Option<i32>, offset: Option<i32>, ) -> Result<Vec<String>, CkanError>

List all organizations in the CKAN instance

Source

pub async fn group_list( &self, sort: Option<&str>, limit: Option<i32>, offset: Option<i32>, ) -> Result<Vec<String>, CkanError>

List all groups in the CKAN instance

Source

pub async fn dataset_autocomplete( &self, incomplete: Option<&str>, limit: Option<i32>, ) -> Result<Vec<DatasetAutocomplete>, CkanError>

Get dataset autocomplete suggestions for type-ahead functionality

Provides quick dataset name/title suggestions as the user types, perfect for implementing search boxes with autocomplete dropdowns.

§Arguments
  • incomplete - Partial dataset name or title to search for (e.g., “climat”)
  • limit - Maximum number of suggestions to return (default: 10, reasonable max: 20)
§Returns

Returns suggestions containing dataset names and titles that match the input.

§Examples

// Get suggestions as user types "elect"
let suggestions = client.dataset_autocomplete(Some("elect"), Some(5)).await?;

for suggestion in &suggestions {
    println!("Dataset: {} - {}",
        suggestion.name.as_deref().unwrap_or("Unknown"),
        suggestion.title.as_deref().unwrap_or("No title"));
}
§UI Integration

This is designed for real-time search suggestions:

// In your web frontend or CLI app
async fn on_search_input_change(input: &str, client: &CkanClient) -> Result<(), Box<dyn std::error::Error>> {
    if input.len() >= 2 { // Start suggesting after 2+ characters
        let suggestions = client.dataset_autocomplete(Some(input), Some(10)).await?;
        // Display suggestions in dropdown/list
        for suggestion in &suggestions {
            println!("Suggestion: {}", suggestion.title.as_deref().unwrap_or("Unknown"));
        }
    }
    Ok(())
}
Source

pub async fn tag_autocomplete( &self, incomplete: Option<&str>, limit: Option<i32>, vocabulary_id: Option<&str>, ) -> Result<Vec<String>, CkanError>

Get tag autocomplete suggestions

Source

pub async fn user_autocomplete( &self, q: Option<&str>, limit: Option<i32>, ignore_self: Option<bool>, ) -> Result<Vec<UserAutocomplete>, CkanError>

Get user autocomplete suggestions

Source

pub async fn group_autocomplete( &self, q: Option<&str>, limit: Option<i32>, ) -> Result<Vec<GroupAutocomplete>, CkanError>

Get group autocomplete suggestions for filtering and organization

Groups in CKAN represent thematic collections of datasets. This endpoint provides autocomplete functionality for group names and titles.

§Arguments
  • q - Partial group name or title to search for (e.g., “agri”)
  • limit - Maximum number of suggestions to return (default: 10)
§Returns

Returns group suggestions with names, display names, and metadata.

§Examples

// Find agriculture-related groups
let groups = client.group_autocomplete(Some("agri"), Some(5)).await?;

println!("Found {} groups", groups.len());
for group in groups {
    println!("Group: {} ({})",
        group.title.as_deref().unwrap_or("Unknown"),
        group.name.as_deref().unwrap_or("Unknown"));
}
§Common Use Cases
// Building category filters for search UI
let science_groups = client.group_autocomplete(Some("science"), Some(10)).await?;

// Finding groups for dataset categorization
let energy_groups = client.group_autocomplete(Some("energy"), Some(5)).await?;
Source

pub async fn organization_autocomplete( &self, q: Option<&str>, limit: Option<i32>, ) -> Result<Vec<OrganizationAutocomplete>, CkanError>

Get organization autocomplete suggestions

Source

pub async fn resource_format_autocomplete( &self, incomplete: Option<&str>, limit: Option<i32>, ) -> Result<Vec<String>, CkanError>

Get resource format autocomplete suggestions

Trait Implementations§

Source§

impl Debug for CkanClient

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more