Overview
Platforms are managed through:- Platform Trait (
src/feed.rs:124-174) - Interface all platforms must implement - Platform Registry (
src/feed/platforms.rs) - Central registry of all platforms - Individual Implementations - One file per platform (e.g.,
anilist_platform.rs)
Platform Trait
All platforms must implement this trait fromsrc/feed.rs:124-137:
Key Data Structures
Step-by-Step: Creating a New Platform
use std::hash::{Hash, Hasher};
use async_trait::async_trait;
use chrono::DateTime;
use crate::feed::{
BasePlatform, FeedItem, FeedSource, Platform, PlatformInfo,
error::FeedError,
};
/// MyPlatform integration.
pub struct MyPlatformPlatform {
pub base: BasePlatform,
client: wreq::Client,
}
impl MyPlatformPlatform {
/// Creates a new MyPlatform platform.
pub fn new() -> Self {
let info = PlatformInfo {
name: "MyPlatform".to_string(),
feed_item_name: "Chapter".to_string(),
api_hostname: "api.myplatform.com".to_string(),
api_domain: "myplatform.com".to_string(),
api_url: "https://api.myplatform.com".to_string(),
copyright_notice: "© MyPlatform 2025".to_string(),
logo_url: "https://myplatform.com/logo.png".to_string(),
tags: "series".to_string(),
};
let client = wreq::Client::builder()
.emulation(wreq_util::Emulation::Chrome137)
.build()
.unwrap();
Self {
base: BasePlatform::new(info),
client,
}
}
}
#[async_trait]
impl Platform for MyPlatformPlatform {
async fn fetch_latest(&self, items_id: &str) -> Result<FeedItem, FeedError> {
// Make API request to get latest item
let url = format!("{}/items/{}", self.base.info.api_url, items_id);
let response = self.client.get(&url).send().await?;
let body = response.text().await?;
let json: serde_json::Value = serde_json::from_str(&body)?;
// Parse response
let id = json["id"].as_str()
.ok_or_else(|| FeedError::MissingField {
field: "id".to_string()
})?
.to_string();
let title = json["title"].as_str()
.ok_or_else(|| FeedError::MissingField {
field: "title".to_string()
})?
.to_string();
let timestamp = json["published_at"].as_i64()
.ok_or_else(|| FeedError::MissingField {
field: "published_at".to_string()
})?;
let published = DateTime::from_timestamp(timestamp, 0)
.ok_or_else(|| FeedError::InvalidTimestamp { timestamp })?;
Ok(FeedItem { id, title, published })
}
async fn fetch_source(&self, source_id: &str) -> Result<FeedSource, FeedError> {
// Make API request to get source info
let url = format!("{}/sources/{}", self.base.info.api_url, source_id);
let response = self.client.get(&url).send().await?;
let body = response.text().await?;
let json: serde_json::Value = serde_json::from_str(&body)?;
// Parse response
Ok(FeedSource {
id: source_id.to_string(),
items_id: source_id.to_string(),
name: json["name"].as_str().unwrap_or("").to_string(),
description: json["description"].as_str().unwrap_or("").to_string(),
source_url: self.get_source_url_from_id(source_id),
image_url: json["image"].as_str().map(String::from),
})
}
fn get_id_from_source_url<'a>(&self, url: &'a str) -> Result<&'a str, FeedError> {
// Extract ID from URL path
// Example: https://myplatform.com/series/123 -> "123"
self.base.get_nth_path_from_url(url, 1)
}
fn get_source_url_from_id(&self, id: &str) -> String {
format!("https://{}/series/{}", self.base.info.api_domain, id)
}
fn get_base(&self) -> &BasePlatform {
&self.base
}
}
// Required for platform registry
impl PartialEq for MyPlatformPlatform {
fn eq(&self, other: &Self) -> bool {
self.base.info.api_url == other.base.info.api_url
}
}
impl Eq for MyPlatformPlatform {}
impl Hash for MyPlatformPlatform {
fn hash<H: Hasher>(&self, state: &mut H) {
self.base.info.api_url.hash(state);
}
}
pub mod anilist_platform;
pub mod comick_platform;
pub mod mangadex_platform;
pub mod myplatform_platform; // Add this line
pub mod platforms;
Real-World Example: AniList Platform
Here’s how AniList implements GraphQL queries (src/feed/anilist_platform.rs:208-239):
Rate Limiting
Many APIs require rate limiting. Use thegovernor crate:
src/feed/anilist_platform.rs:27-31
Error Handling
Use the predefined error types fromFeedError:
Testing Your Platform
Create tests intests/mock_platforms_test.rs using httpmock:
Store mock API responses in
tests/responses/ directory for better organization.Related Documentation
- Testing Guidelines - Learn how to write comprehensive platform tests
- Architecture Overview - Learn about error handling patterns