mindat_rs/
lib.rs

1//! # mindat-rs
2//!
3//! A Rust client library for the [Mindat API](https://api.mindat.org/).
4//!
5//! Mindat is the world's largest open database of minerals, rocks, meteorites, and
6//! the localities where they come from. This crate provides a type-safe, async interface
7//! to access mineralogical data.
8//!
9//! ## Features
10//!
11//! - Full coverage of the Mindat API endpoints
12//! - Strongly-typed request builders and response models
13//! - Async/await support using tokio
14//! - Pagination helpers
15//! - Comprehensive error handling
16//!
17//! ## Quick Start
18//!
19//! ```no_run
20//! use mindat_rs::{MindatClient, GeomaterialsQuery, Result};
21//!
22//! #[tokio::main]
23//! async fn main() -> Result<()> {
24//!     // Create a client with your API token
25//!     let client = MindatClient::new("your-api-token");
26//!
27//!     // Search for quartz
28//!     let query = GeomaterialsQuery::new()
29//!         .name("quartz")
30//!         .ima_approved(true);
31//!
32//!     let minerals = client.geomaterials(query).await?;
33//!
34//!     for mineral in minerals.results {
35//!         println!("{}: {:?}", mineral.id, mineral.name);
36//!     }
37//!
38//!     Ok(())
39//! }
40//! ```
41//!
42//! ## Authentication
43//!
44//! Most API endpoints require authentication with a Mindat API token.
45//! You can obtain a token from your [Mindat account settings](https://www.mindat.org/).
46//!
47//! Some endpoints (like `minerals_ima`) can be accessed without authentication:
48//!
49//! ```no_run
50//! use mindat_rs::{MindatClient, ImaMineralsQuery};
51//!
52//! # async fn example() -> mindat_rs::Result<()> {
53//! let client = MindatClient::anonymous();
54//! let minerals = client.minerals_ima(ImaMineralsQuery::new()).await?;
55//! # Ok(())
56//! # }
57//! ```
58//!
59//! ## Pagination
60//!
61//! Most list endpoints return paginated results. Use the pagination helpers:
62//!
63//! ```no_run
64//! use mindat_rs::{MindatClient, GeomaterialsQuery};
65//!
66//! # async fn example() -> mindat_rs::Result<()> {
67//! let client = MindatClient::new("token");
68//!
69//! // Get first page
70//! let query = GeomaterialsQuery::new().page(1).page_size(100);
71//! let page1 = client.geomaterials(query).await?;
72//!
73//! // Check if there are more pages
74//! if page1.has_next() {
75//!     let query = GeomaterialsQuery::new().page(2).page_size(100);
76//!     let page2 = client.geomaterials(query).await?;
77//! }
78//! # Ok(())
79//! # }
80//! ```
81//!
82//! ## Available Endpoints
83//!
84//! - **Countries**: List and retrieve country information
85//! - **Geomaterials**: Search minerals, rocks, varieties, synonyms, and more
86//! - **Localities**: Search mineral localities worldwide
87//! - **IMA Minerals**: Access IMA-approved mineral species
88//! - **Classification**: Dana 8th ed. and Nickel-Strunz 10th ed. systems
89//! - **Locality Metadata**: Ages, statuses, types, and geographic regions
90
91pub mod client;
92pub mod error;
93pub mod models;
94
95pub use client::{DEFAULT_BASE_URL, MindatClient, MindatClientBuilder};
96pub use error::{MindatError, Result};
97pub use models::*;
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_client_creation() {
105        let client = MindatClient::new("test-token");
106        assert_eq!(client.base_url().as_str(), "https://api.mindat.org/v1/");
107    }
108
109    #[test]
110    fn test_anonymous_client() {
111        let client = MindatClient::anonymous();
112        assert_eq!(client.base_url().as_str(), "https://api.mindat.org/v1/");
113    }
114
115    #[test]
116    fn test_geomaterials_query_builder() {
117        let query = GeomaterialsQuery::new()
118            .name("quartz")
119            .ima_approved(true)
120            .with_elements("Si,O")
121            .hardness_range(6.0, 7.0)
122            .page(1)
123            .page_size(50);
124
125        assert_eq!(query.name, Some("quartz".to_string()));
126        assert_eq!(query.ima, Some(true));
127        assert_eq!(query.elements_inc, Some("Si,O".to_string()));
128        assert_eq!(query.hardness_min, Some(6.0));
129        assert_eq!(query.hardness_max, Some(7.0));
130        assert_eq!(query.page, Some(1));
131        assert_eq!(query.page_size, Some(50));
132    }
133
134    #[test]
135    fn test_localities_query_builder() {
136        let query = LocalitiesQuery::new()
137            .country("Brazil")
138            .with_elements("Au")
139            .select_fields("id,txt,country");
140
141        assert_eq!(query.country, Some("Brazil".to_string()));
142        assert_eq!(query.elements_inc, Some("Au".to_string()));
143        assert_eq!(query.fields, Some("id,txt,country".to_string()));
144    }
145
146    #[test]
147    fn test_paginated_response() {
148        let response: PaginatedResponse<i32> = PaginatedResponse {
149            count: Some(150),
150            next: Some("https://api.mindat.org/geomaterials/?page=2".to_string()),
151            previous: None,
152            results: vec![1, 2, 3],
153        };
154
155        assert!(response.has_next());
156        assert!(!response.has_previous());
157        assert_eq!(response.total_pages(50), Some(3));
158    }
159
160    #[test]
161    fn test_entry_type_from_u8() {
162        assert_eq!(EntryType::from(0), EntryType::Mineral);
163        assert_eq!(EntryType::from(1), EntryType::Synonym);
164        assert_eq!(EntryType::from(2), EntryType::Variety);
165        assert_eq!(EntryType::from(7), EntryType::Rock);
166        assert_eq!(EntryType::from(99), EntryType::Mineral); // Unknown defaults to Mineral
167    }
168
169    #[test]
170    fn test_geomaterials_ordering_display() {
171        assert_eq!(GeomaterialsOrdering::Id.to_string(), "id");
172        assert_eq!(GeomaterialsOrdering::IdDesc.to_string(), "-id");
173        assert_eq!(GeomaterialsOrdering::Name.to_string(), "name");
174    }
175}