Search
Search documentation
Working with Datasets in Rust
Updated Oct 28, 2026
Dataset operations for managing collections of tabular resources with metadata and schemas โ from Rust.
Available Functions
The dataset API provides utilities for working with datasets:
load_dataset- Load a dataset descriptor from a local path or remote URLinfer_dataset- Automatically fill in missing metadata for every resourcesave_dataset- Save a dataset (and its resources) via pluginsvalidate_dataset- Validate a dataset descriptor and all its resourcesquery_dataset- Run a SQL query across the dataset's tablesvalidate_dataset_foreign_keys- Check foreign-key relationships across resourcesrender_dataset_as- Render a dataset through the render plugins
What is a Dataset?
A dataset is a collection of related data resources (tables) with:
- Metadata describing the dataset (title, description, license, etc.)
- Resource definitions for each table (path, dialect, schema)
- Table Schemas defining the structure of each resource
- Relationships and foreign keys between resources
Datasets are represented in Rust by the Dataset struct and serialised as JSON descriptors (often named dataset.json) following the Fairspec specification. Its metadata follows DataCite, so a title is an entry in titles and a license one in rightsList:
use fairspec::fairspec_metadata::Dataset;
use serde_json::json;
let dataset: Dataset = serde_json::from_value(json!({
"titles": [{"title": "Sales Database Export"}],
"rightsList": [{"rights": "CC-BY-4.0"}],
"resources": [
{"name": "users", "data": "users.csv"},
{"name": "orders", "data": "orders.csv"},
],
}))?;Loading a Dataset
Load a dataset descriptor from a local path or remote URL:
use fairspec::load_dataset;
// Load from local file
let descriptor = load_dataset("dataset.json")?;
// Load from a remote URL
let descriptor = load_dataset("https://example.com/dataset.json")?;load_dataset dispatches to the appropriate plugin based on the source (folder, zip, GitHub, Zenodo, CKAN, โฆ) and returns Option<Descriptor> โ a serde_json::Map โ or None if no plugin recognises the source.
To get a typed Dataset, deserialise the descriptor:
use fairspec::fairspec_metadata::Dataset;
use fairspec::load_dataset;
let descriptor = load_dataset("dataset.json")?.ok_or("unsupported source")?;
let dataset: Dataset = serde_json::from_value(descriptor.into())?;For format-specific loaders, use (from fairspec::fairspec_dataset):
load_dataset_from_folder(path)load_dataset_from_zip(path)load_dataset_from_github(url, api_key)load_dataset_from_zenodo(url, api_key)load_dataset_from_ckan(url, api_key)
Inferring a Dataset
Automatically fill in missing metadata for every resource in a dataset:
use fairspec::fairspec_metadata::Dataset;
use fairspec::fairspec_table::InferTableSchemaOptions;
use fairspec::infer_dataset;
use serde_json::json;
let dataset: Dataset = serde_json::from_value(json!({
"resources": [{"data": "users.csv"}, {"data": "orders.csv"}],
}))?;
let dataset = infer_dataset(&dataset, &InferTableSchemaOptions::default())?;The inference process automatically fills in:
nameโ generated from the file namefileDialectโ detected from the file content (CSV delimiter, JSON pointer, etc.)tableSchemaโ inferred from the data (column types, required fields)dataSchemaโ inferred for JSON resourcesintegrityโ computed hash of the file content
Once inferred, the dataset can be serialised to a descriptor:
println!("{}", serde_json::to_string_pretty(&dataset)?);Example output:
{
"resources": [
{
"name": "users",
"data": "users.csv",
"fileDialect": { "format": "csv", "delimiter": "," },
"tableSchema": {
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"email": { "type": "string" }
},
"required": ["id", "name", "email"]
}
}
]
}Saving a Dataset
Save a dataset to a folder, zip, or other target via plugins:
use fairspec::fairspec_dataset::SaveDatasetOptions;
use fairspec::save_dataset;
// Save to a local folder
save_dataset(&dataset, &SaveDatasetOptions {
target: "./local-dataset".into(),
with_remote: false,
})?;
// Save to a zip archive
save_dataset(&dataset, &SaveDatasetOptions {
target: "./dataset.zip".into(),
with_remote: false,
})?;save_dataset takes SaveDatasetOptions:
targetโ output path (folder or zip file)with_remoteโ also include remote resources (whentrue, they are downloaded; whenfalse, references are preserved)
For format-specific saving, use (from fairspec::fairspec_dataset):
save_dataset_to_folder(&dataset, folder_path, with_remote)save_dataset_to_zip(&dataset, archive_path, with_remote)save_dataset_to_github(&dataset, api_key, repo, org)save_dataset_to_zenodo(&dataset, api_key, sandbox)save_dataset_to_ckan(&dataset, api_key, ckan_url, owner_org, dataset_name)
Validating a Dataset
Validate a dataset descriptor and all its resources:
use fairspec::{ValidateTableOptions, validate_dataset};
// Validate from a path (loads + validates)
let report = validate_dataset("dataset.json", &ValidateTableOptions::default())?;
// Validate a Dataset directly
let report = validate_dataset(dataset, &ValidateTableOptions::default())?;
if report.valid {
println!("Dataset is valid");
} else {
for error in &report.errors {
println!("{}", serde_json::to_string(error)?);
}
}validate_dataset takes a path or URL, a Descriptor or a Dataset, and checks:
- Descriptor validity โ valid JSON conforming to the Fairspec specification
- Resource existence โ all referenced files can be loaded
- Schema validation โ each resource validates against its Table Schema
- Referential integrity โ foreign-key relationships are valid
- Format compliance โ resources match their declared dialects
The returned Report has two fields:
valid: boolโ whether validation passederrors: Vec<FairspecError>โ the list of errors; serialised, each carries atypeand context fields likeresourceName,rowNumber,columnName
Example error inspection:
use fairspec::{ValidateTableOptions, validate_dataset};
let report = validate_dataset("dataset.json", &ValidateTableOptions::default())?;
for error in &report.errors {
let error = serde_json::to_value(error)?;
println!("[{}] {}", error["type"], error["resourceName"]);
}Querying a Dataset
query_dataset(source, sql, options) runs a SQL query across a dataset's tables using Polars' SQL engine. Each table resource is exposed under its name (an unnamed one under the name inferred from its file), so one query can join several resources. Only the resources the query mentions are loaded.
use fairspec::fairspec_table::LoadTableOptions;
use fairspec::query_dataset;
let result = query_dataset(
"dataset.json",
"SELECT users.name, COUNT(*) AS orders FROM orders JOIN users ON orders.user_id = users.id GROUP BY users.name",
&LoadTableOptions::default(),
)?;
println!("{}", result.collect()?);source is a path, a Descriptor or a Dataset. To query a single table as self, load it and use query_table.
Validating Foreign Keys
Validate only the foreign-key relationships between resources (skipping per-resource schema checks):
use fairspec::{ValidateTableOptions, validate_dataset_foreign_keys};
let report = validate_dataset_foreign_keys(&dataset, &ValidateTableOptions::default())?;This is useful when you have already validated each resource independently and only want to confirm cross-resource referential integrity.
Rendering a Dataset
render_dataset_as asks the render plugins for the requested format:
use fairspec::fairspec_metadata::RenderDatasetOptions;
use fairspec::render_dataset_as;
let markdown = render_dataset_as(&dataset, &RenderDatasetOptions {
format: "markdown".into(),
..Default::default()
})?;No Rust plugin renders yet, so the result is None for every format.
Working with Resources
A Dataset exposes its resources via the resources field. Iterate to access each resource's metadata:
for resource in dataset.resources.iter().flatten() {
println!("{:?} {:?}", resource.name, resource.data);
}To load a single resource as a table, pass it to load_table (see Tables):
use fairspec::fairspec_table::LoadTableOptions;
use fairspec::load_table;
let resources = dataset.resources.unwrap_or_default();
let users = load_table(&resources[0], &LoadTableOptions::default())?;To validate a single resource without re-validating the whole dataset, use validate_resource (see Tables for the table-level operations and Files for file-level operations).
Common Workflows
Create a Dataset from Files
use fairspec::fairspec_metadata::Dataset;
use fairspec::fairspec_table::InferTableSchemaOptions;
use fairspec::{ValidateTableOptions, infer_dataset, validate_dataset};
use serde_json::json;
let dataset: Dataset = serde_json::from_value(json!({
"titles": [{"title": "Sales Database Export"}],
"rightsList": [{"rights": "CC-BY-4.0"}],
"resources": [
{"data": "customers.csv"},
{"data": "orders.csv"},
{"data": "products.csv"},
],
}))?;
let dataset = infer_dataset(&dataset, &InferTableSchemaOptions::default())?;
let report = validate_dataset(dataset, &ValidateTableOptions::default())?;
assert!(report.valid, "{:?}", report.errors);Clone a Remote Dataset
use fairspec::fairspec_dataset::SaveDatasetOptions;
use fairspec::fairspec_metadata::Dataset;
use fairspec::{ValidateTableOptions, load_dataset, save_dataset, validate_dataset};
let descriptor = load_dataset("https://example.com/dataset.json")?.ok_or("unsupported source")?;
let dataset: Dataset = serde_json::from_value(descriptor.into())?;
save_dataset(&dataset, &SaveDatasetOptions {
target: "./local-data".into(),
with_remote: true,
})?;
let report = validate_dataset("./local-data/dataset.json", &ValidateTableOptions::default())?;
assert!(report.valid);Dataset Quality Assurance in CI
use fairspec::{ValidateTableOptions, validate_dataset};
let report = validate_dataset("dataset.json", &ValidateTableOptions::default())?;
if report.valid {
println!("Dataset validation passed");
} else {
for error in &report.errors {
println!(" {}", serde_json::to_string(error)?);
}
std::process::exit(1);
}Examples
Create a Multi-Table Dataset with Foreign Keys
use fairspec::fairspec_metadata::Dataset;
use fairspec::{ValidateTableOptions, validate_dataset};
use serde_json::json;
let dataset: Dataset = serde_json::from_value(json!({
"titles": [{"title": "Sales Database Export"}],
"rightsList": [{"rights": "CC-BY-4.0"}],
"resources": [
{
"name": "customers",
"data": "customers.csv",
"tableSchema": {
"properties": {
"id": {"type": "integer"},
"name": {"type": "string"},
},
"primaryKey": ["id"],
},
},
{
"name": "orders",
"data": "orders.csv",
"tableSchema": {
"properties": {
"order_id": {"type": "integer"},
"customer_id": {"type": "integer"},
},
"primaryKey": ["order_id"],
"foreignKeys": [{
"columns": ["customer_id"],
"reference": {"resource": "customers", "columns": ["id"]},
}],
},
},
],
}))?;
let report = validate_dataset(dataset, &ValidateTableOptions::default())?;
assert!(report.valid, "{:?}", report.errors);Download and Validate a Public Dataset
use fairspec::fairspec_dataset::SaveDatasetOptions;
use fairspec::fairspec_metadata::Dataset;
use fairspec::{ValidateTableOptions, load_dataset, save_dataset, validate_dataset};
let descriptor = load_dataset("https://data.example.org/climate/dataset.json")?
.ok_or("unsupported source")?;
let dataset: Dataset = serde_json::from_value(descriptor.into())?;
save_dataset(&dataset, &SaveDatasetOptions {
target: "./climate-data".into(),
with_remote: true,
})?;
let report = validate_dataset("./climate-data/dataset.json", &ValidateTableOptions::default())?;
println!("Valid: {}", report.valid);
let names: Vec<_> = dataset.resources.iter().flatten().map(|r| &r.name).collect();
println!("Resources: {names:?}");Interactive Data Exploration
use fairspec::fairspec_metadata::Dataset;
use fairspec::fairspec_table::LoadTableOptions;
use fairspec::{load_dataset, load_table};
let descriptor = load_dataset("dataset.json")?.ok_or("unsupported source")?;
let dataset: Dataset = serde_json::from_value(descriptor.into())?;
let resources = dataset.resources.unwrap_or_default();
let names: Vec<_> = resources.iter().map(|r| &r.name).collect();
println!("{names:?}");
let users_resource = resources
.iter()
.find(|r| r.name.as_deref() == Some("users"))
.ok_or("no users resource")?;
let users = load_table(users_resource, &LoadTableOptions::default())?.ok_or("unsupported format")?;
println!("{}", users.limit(5).collect()?);Created with โค and Livemark