Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "modrinth-api"
version = "0.1.0"
version = "0.1.1"
edition = "2024"

[dependencies]
Expand Down
11 changes: 5 additions & 6 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@

## structs\project.rs:

- [ ] - Implement custom struct for `client_side` and `server_side` values
- [ ] - Implement custom struct for `status` value
- [ ] - Implement custom struct for `requested_status` value (w/ Option)
- [ ] - implement custom deserializer for URL links
- [ ] - implement custom struct for donation links
- [ ] - implement custom struct for license
- [x] - Implement custom struct for `client_side` and `server_side` values
- [x] - Implement custom struct for `status` value
- [x] - Implement custom struct for `requested_status` value (w/ Option)
- [x] - implement custom struct for donation links
- [x] - implement custom struct for license
- [x] - implement custom `ProjectStatus` struct
57 changes: 56 additions & 1 deletion src/api/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,65 @@ mod tests {
)
.await?;

let title = response.hits.first().unwrap().slug.as_ref();
let response = response.hits.first().unwrap();
let title = response.slug.as_ref();

assert!(title.is_some());
assert_eq!(title.ok_or(0), Ok(&String::from("xaeros-minimap")));
Ok(())
}

#[tokio::test]
async fn test_fetching_project_with_mut() -> Result<()> {
let api = ModrinthAPI::default();
let response = api
.extended_search(
"xaeros",
&Sort::Downloads,
None,
Some(ExtendedSearch {
offset: None,
facets: vec![vec![Facet::ProjectType(ProjectType::Mod)]],
}),
)
.await?;

let response = response.hits.first().unwrap();
let title = response.slug.as_ref();

assert!(title.is_some());
assert_eq!(title.ok_or(0), Ok(&String::from("xaeros-minimap")));

let hit = response.to_owned().fetch_project(&api).await?;

assert_eq!(hit.slug.as_ref().unwrap(), &String::from("xaeros-minimap"));
Ok(())
}

#[tokio::test]
async fn test_fetching_project_without_mut() -> Result<()> {
let api = ModrinthAPI::default();
let response = api
.extended_search(
"xaeros",
&Sort::Downloads,
None,
Some(ExtendedSearch {
offset: None,
facets: vec![vec![Facet::ProjectType(ProjectType::Mod)]],
}),
)
.await?;

let response = response.hits.first().unwrap();
let title = response.slug.as_ref();

assert!(title.is_some());
assert_eq!(title.ok_or(0), Ok(&String::from("xaeros-minimap")));

let hit = response.get_full_project(&api).await?;

assert_eq!(hit.slug, String::from("xaeros-minimap"));
Ok(())
}
}
22 changes: 1 addition & 21 deletions src/structs/mod.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,8 @@
pub mod projects;
pub mod search;

use crate::{ModrinthAPI, Result, structs::projects::Project};
use serde::{Deserialize, Serialize};
use url::Url;

pub type Date = chrono::DateTime<chrono::Utc>;

fn deserialise_optional_url<'de, D: serde::Deserializer<'de>>(
de: D,
) -> Result<Option<Url>, D::Error> {
use serde::de::{Error, Unexpected};
use std::borrow::Cow;

let intermediate = <Option<Cow<'de, str>>>::deserialize(de)?;
match intermediate.as_deref() {
None | Some("") => Ok(None),
Some(s) => Url::parse(s).map_or_else(
|err| {
Err(Error::invalid_value(
Unexpected::Str(s),
&err.to_string().as_str(),
))
},
|ok| Ok(Some(ok)),
),
}
}
95 changes: 68 additions & 27 deletions src/structs/projects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
//!
//! [documentation](https://docs.modrinth.com/api/operations/tags/projects/)

use super::Date;
use super::*;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Project {
/// The slug of a project, used for vanity URLs
pub slug: String,
Expand All @@ -16,45 +16,30 @@ pub struct Project {
/// A list of the categories that the project has
pub categories: Vec<String>,
/// The client side support of the project
pub client_side: String, // TODO: read #1 in TOOD.md file (structs\projects section)
pub client_side: ProjectSupportRange,
/// The server side support of the project
pub server_side: String, // TODO: read #1 in TODO.md file (structs\projects section)
pub server_side: ProjectSupportRange,
/// A long form description of the project
pub body: String,
/// The status of the project
///
/// TODO: read #2 in TODO.md file (structs\projects section)
pub status: String,
pub status: ProjectStatus,
/// The requested status when submitting for review or scheduling the project for release
///
/// TODO: read #3 in TODO.md file (structs\projects section)
pub requested_status: Option<String>,
pub requested_status: Option<RequestedStatus>,
/// A list of categories which are searchable but non-primary
pub additional_categories: Vec<String>,
/// An optional link to where to submit bugs or issues with the project
///
/// TODO: read #4 in TODO.md file (structs\projects section)
pub issues_url: Option<String>,
/// An optional link to the source code of the project
///
/// TODO: read #4 in TODO.md file (structs\projects section)
pub source_url: Option<String>,
/// An optional link to the project’s wiki page or other relevant information
///
/// TODO: read #4 in TODO.md file (structs\projects section)
pub wiki_url: Option<String>,
/// An optional invite link to the project’s discord
///
/// TODO: read #4 in TODO.md file (structs\projects section)
pub discord_url: Option<String>,
/// Donation links
///
/// TODO: read #5 in TODO.md file (structs\projects section)
#[serde(skip)]
pub donation_links: Option<Vec<String>>,
/// Donation links / urls
pub donation_urls: Vec<DonationLink>,
pub project_type: String,
pub downloads: usize,
/// TODO: read #4 in TODO.md file (structs\projects section)
/// Project icon URL
pub icon_url: Option<String>,
/// The RGB color of the project, automatically generated from the project icon
pub color: Option<usize>,
Expand All @@ -70,9 +55,7 @@ pub struct Project {
pub approved: Option<Date>,
pub queued: Option<Date>,
pub followers: usize,
/// TODO: read #6 in TODO.md file (structs\projects section)
#[serde(skip)]
pub license: Option<Vec<String>>, // placeholder
pub license: License,
/// A list of the version IDs of the project (will never be empty unless draft status)
pub versions: Vec<String>,
/// A list of all the game versions supported by the project
Expand All @@ -81,6 +64,16 @@ pub struct Project {
pub loaders: Vec<String>,
}

impl Project {
/// Returns a read-only reference to the project's donation URLs.
///
/// This method serves as an alias/getter for the [`Project::donation_urls`] field,
/// providing direct access to the same underlying data.
pub fn donation_links(&self) -> &Vec<DonationLink> {
self.donation_urls.as_ref()
}
}

#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ProjectType {
Expand All @@ -101,3 +94,51 @@ pub enum MonetizationStatus {
Demonetized,
ForceDemonetized,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct License {
pub id: String,
pub name: String,
pub url: Option<String>,
}

#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ProjectSupportRange {
Required,
Optional,
Unsupported,
Unknown,
}

#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ProjectStatus {
Approved,
Archived,
Rejected,
Draft,
Unlisted,
Processing,
Withheld,
Scheduled,
Private,
Unknown,
}

#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum RequestedStatus {
Approved,
Archived,
Unlisted,
Private,
Draft,
}

#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
pub struct DonationLink {
pub id: String,
pub platform: String,
pub url: String,
}
Loading