feat(papermc_api): add routes for per-version, project and build information

This commit is contained in:
Jef Roosens 2026-04-13 22:31:24 +02:00
parent b4dce4b69a
commit f2c4b97626
Signed by: Jef Roosens
GPG key ID: 21FD3D77D56BAF49
2 changed files with 111 additions and 68 deletions

View file

@ -1,9 +1,8 @@
use serde_json::Value;
use crate::{
error::Error,
models::{Build, BuildCommit, BuildDownload, Java, Project, Version},
};
pub use error::Error;
use crate::models::{Build, BuildCommit, BuildDownload, Java, Project, Version};
mod error;
mod models;
@ -26,33 +25,7 @@ impl Client {
let body_json: Value = res.body_mut().read_json()?;
let projects = body_json["projects"].as_array().ok_or(Error::BadBody)?;
projects
.into_iter()
.map(|p| {
Ok(Project {
id: p["project"]["id"]
.as_str()
.map(|s| s.to_string())
.ok_or(Error::BadBody)?,
name: p["project"]["name"]
.as_str()
.map(|s| s.to_string())
.ok_or(Error::BadBody)?,
// Flatten map of versions into one array
versions: p["versions"]
.as_object()
.ok_or(Error::BadBody)?
.into_iter()
.map(|(_, versions)| versions.as_array().ok_or(Error::BadBody))
// Collect into error to propagate error of any of the versions
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.flatten()
.map(|v| v.as_str().ok_or(Error::BadBody).map(|s| s.to_string()))
.collect::<Result<_, _>>()?,
})
})
.collect()
projects.into_iter().map(parse_project_json).collect()
}
pub fn project<'a>(&'a self, project: &'a str) -> ProjectQuery<'a> {
@ -69,6 +42,16 @@ pub struct ProjectQuery<'a> {
}
impl<'a> ProjectQuery<'a> {
pub fn info(&self) -> Result<Project, Error> {
let mut res = self
.agent
.get(format!("{}/projects/{}", BASE_URL, self.project))
.call()?;
let body_json: Value = res.body_mut().read_json()?;
parse_project_json(&body_json)
}
pub fn versions(&self) -> Result<Vec<Version>, Error> {
let mut res = self
.agent
@ -77,39 +60,7 @@ impl<'a> ProjectQuery<'a> {
let body_json: Value = res.body_mut().read_json()?;
let versions = body_json["versions"].as_array().ok_or(Error::BadBody)?;
versions
.into_iter()
.map(|v| {
Ok(Version {
id: v["version"]["id"]
.as_str()
.map(String::from)
.ok_or(Error::BadBody)?,
support_status: v["version"]["support"]["status"]
.as_str()
.ok_or(Error::BadBody)?
.parse()
.map_err(|_| Error::BadBody)?,
java: Java {
minimum_version: v["version"]["java"]["version"]["minimum"]
.as_u64()
.ok_or(Error::BadBody)?,
recommended_flags: v["version"]["java"]["flags"]["recommended"]
.as_array()
.ok_or(Error::BadBody)?
.into_iter()
.map(|v| v.as_str().map(String::from).ok_or(Error::BadBody))
.collect::<Result<_, _>>()?,
},
builds: v["builds"]
.as_array()
.ok_or(Error::BadBody)?
.into_iter()
.map(|v| v.as_u64().ok_or(Error::BadBody))
.collect::<Result<_, _>>()?,
})
})
.collect()
versions.into_iter().map(parse_version_json).collect()
}
pub fn version(&self, version: &'a str) -> VersionQuery<'a> {
@ -128,17 +79,17 @@ pub struct VersionQuery<'a> {
}
impl<'a> VersionQuery<'a> {
pub fn latest(&self) -> Result<Build, Error> {
pub fn info(&self) -> Result<Version, Error> {
let mut res = self
.agent
.get(format!(
"{}/projects/{}/versions/{}/builds/latest",
"{}/projects/{}/versions/{}",
BASE_URL, self.project, self.version
))
.call()?;
let body_json: Value = res.body_mut().read_json()?;
parse_build_json(&body_json)
parse_version_json(&body_json)
}
pub fn builds(&self) -> Result<Vec<Build>, Error> {
@ -158,6 +109,79 @@ impl<'a> VersionQuery<'a> {
.map(parse_build_json)
.collect()
}
pub fn build(&self, build: &str) -> Result<Build, Error> {
let mut res = self
.agent
.get(format!(
"{}/projects/{}/versions/{}/builds/{}",
BASE_URL, self.project, self.version, build
))
.call()?;
let body_json: Value = res.body_mut().read_json()?;
parse_build_json(&body_json)
}
pub fn latest(&self) -> Result<Build, Error> {
return self.build("latest");
}
}
fn parse_project_json(value: &Value) -> Result<Project, Error> {
Ok(Project {
id: value["project"]["id"]
.as_str()
.map(|s| s.to_string())
.ok_or(Error::BadBody)?,
name: value["project"]["name"]
.as_str()
.map(|s| s.to_string())
.ok_or(Error::BadBody)?,
// Flatten map of versions into one array
versions: value["versions"]
.as_object()
.ok_or(Error::BadBody)?
.into_iter()
.map(|(_, versions)| versions.as_array().ok_or(Error::BadBody))
// Collect into error to propagate error of any of the versions
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.flatten()
.map(|v| v.as_str().ok_or(Error::BadBody).map(|s| s.to_string()))
.collect::<Result<_, _>>()?,
})
}
fn parse_version_json(value: &Value) -> Result<Version, Error> {
Ok(Version {
id: value["version"]["id"]
.as_str()
.map(String::from)
.ok_or(Error::BadBody)?,
support_status: value["version"]["support"]["status"]
.as_str()
.ok_or(Error::BadBody)?
.parse()
.map_err(|_| Error::BadBody)?,
java: Java {
minimum_version: value["version"]["java"]["version"]["minimum"]
.as_u64()
.ok_or(Error::BadBody)?,
recommended_flags: value["version"]["java"]["flags"]["recommended"]
.as_array()
.ok_or(Error::BadBody)?
.into_iter()
.map(|v| v.as_str().map(String::from).ok_or(Error::BadBody))
.collect::<Result<_, _>>()?,
},
builds: value["builds"]
.as_array()
.ok_or(Error::BadBody)?
.into_iter()
.map(|v| v.as_u64().ok_or(Error::BadBody))
.collect::<Result<_, _>>()?,
})
}
fn parse_build_json(value: &Value) -> Result<Build, Error> {

View file

@ -1,4 +1,4 @@
use std::{collections::HashMap, str::FromStr};
use std::{collections::HashMap, fmt::Display, str::FromStr};
use chrono::{DateTime, Utc};
@ -29,6 +29,15 @@ impl FromStr for SupportStatus {
}
}
impl Display for SupportStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Supported => f.write_str("SUPPORTED"),
Self::Unsupported => f.write_str("UNSUPPORTED"),
}
}
}
#[derive(Debug)]
pub struct Java {
pub minimum_version: u64,
@ -74,6 +83,16 @@ impl FromStr for BuildChannel {
}
}
impl Display for BuildChannel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Alpha => f.write_str("ALPHA"),
Self::Beta => f.write_str("BETA"),
Self::Stable => f.write_str("STABLE"),
}
}
}
#[derive(Debug)]
pub struct BuildCommit {
pub sha: String,