feat(papermc_api): implement wrapper for PaperMC builds API
This commit is contained in:
parent
f2a0b6230f
commit
b4dce4b69a
7 changed files with 952 additions and 24 deletions
10
papermc-api/Cargo.toml
Normal file
10
papermc-api/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "papermc-api"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
chrono.workspace = true
|
||||
serde_json = "1.0.149"
|
||||
ureq = { version = "3.3.0", features = ["json"] }
|
||||
19
papermc-api/examples/routes.rs
Normal file
19
papermc-api/examples/routes.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
fn main() {
|
||||
let client = papermc_api::Client::new();
|
||||
let projects = client.projects().unwrap();
|
||||
|
||||
for project in projects {
|
||||
println!("project: {:?}", project);
|
||||
}
|
||||
|
||||
let versions = client.project("paper").versions().unwrap();
|
||||
for version in versions {
|
||||
println!("version: {:?}", version);
|
||||
}
|
||||
|
||||
let latest = client.project("paper").version("1.21.1").latest().unwrap();
|
||||
println!("latest: {:?}", latest);
|
||||
|
||||
let builds = client.project("paper").version("1.21.10").builds().unwrap();
|
||||
println!("number of builds: {}", builds.len());
|
||||
}
|
||||
22
papermc-api/src/error.rs
Normal file
22
papermc-api/src/error.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Ureq(ureq::Error),
|
||||
BadBody,
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl From<ureq::Error> for Error {
|
||||
fn from(value: ureq::Error) -> Self {
|
||||
Self::Ureq(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Ureq(err) => err.fmt(f),
|
||||
Self::BadBody => f.write_str("bad response body"),
|
||||
}
|
||||
}
|
||||
}
|
||||
217
papermc-api/src/lib.rs
Normal file
217
papermc-api/src/lib.rs
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
models::{Build, BuildCommit, BuildDownload, Java, Project, Version},
|
||||
};
|
||||
|
||||
mod error;
|
||||
mod models;
|
||||
|
||||
pub struct Client {
|
||||
agent: ureq::Agent,
|
||||
}
|
||||
|
||||
pub const BASE_URL: &str = "https://fill.papermc.io/v3";
|
||||
|
||||
impl Client {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
agent: ureq::agent(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn projects(&self) -> Result<Vec<Project>, Error> {
|
||||
let mut res = self.agent.get(format!("{}/projects", BASE_URL)).call()?;
|
||||
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()
|
||||
}
|
||||
|
||||
pub fn project<'a>(&'a self, project: &'a str) -> ProjectQuery<'a> {
|
||||
return ProjectQuery {
|
||||
agent: &self.agent,
|
||||
project,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProjectQuery<'a> {
|
||||
agent: &'a ureq::Agent,
|
||||
project: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> ProjectQuery<'a> {
|
||||
pub fn versions(&self) -> Result<Vec<Version>, Error> {
|
||||
let mut res = self
|
||||
.agent
|
||||
.get(format!("{}/projects/{}/versions", BASE_URL, self.project))
|
||||
.call()?;
|
||||
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()
|
||||
}
|
||||
|
||||
pub fn version(&self, version: &'a str) -> VersionQuery<'a> {
|
||||
VersionQuery {
|
||||
agent: self.agent,
|
||||
project: self.project,
|
||||
version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VersionQuery<'a> {
|
||||
agent: &'a ureq::Agent,
|
||||
project: &'a str,
|
||||
version: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> VersionQuery<'a> {
|
||||
pub fn latest(&self) -> Result<Build, Error> {
|
||||
let mut res = self
|
||||
.agent
|
||||
.get(format!(
|
||||
"{}/projects/{}/versions/{}/builds/latest",
|
||||
BASE_URL, self.project, self.version
|
||||
))
|
||||
.call()?;
|
||||
let body_json: Value = res.body_mut().read_json()?;
|
||||
|
||||
parse_build_json(&body_json)
|
||||
}
|
||||
|
||||
pub fn builds(&self) -> Result<Vec<Build>, Error> {
|
||||
let mut res = self
|
||||
.agent
|
||||
.get(format!(
|
||||
"{}/projects/{}/versions/{}/builds",
|
||||
BASE_URL, self.project, self.version
|
||||
))
|
||||
.call()?;
|
||||
let body_json: Value = res.body_mut().read_json()?;
|
||||
|
||||
body_json
|
||||
.as_array()
|
||||
.ok_or(Error::BadBody)?
|
||||
.into_iter()
|
||||
.map(parse_build_json)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_build_json(value: &Value) -> Result<Build, Error> {
|
||||
Ok(Build {
|
||||
id: value["id"].as_u64().ok_or(Error::BadBody)?,
|
||||
time: chrono::DateTime::parse_from_rfc3339(value["time"].as_str().ok_or(Error::BadBody)?)
|
||||
.map_err(|_| Error::BadBody)?
|
||||
.into(),
|
||||
channel: value["channel"]
|
||||
.as_str()
|
||||
.ok_or(Error::BadBody)?
|
||||
.parse()
|
||||
.map_err(|_| Error::BadBody)?,
|
||||
commits: value["commits"]
|
||||
.as_array()
|
||||
.ok_or(Error::BadBody)?
|
||||
.into_iter()
|
||||
.map(|build| {
|
||||
Ok(BuildCommit {
|
||||
sha: build["sha"].as_str().ok_or(Error::BadBody)?.to_string(),
|
||||
time: chrono::DateTime::parse_from_rfc3339(
|
||||
build["time"].as_str().ok_or(Error::BadBody)?,
|
||||
)
|
||||
.map_err(|_| Error::BadBody)?
|
||||
.into(),
|
||||
message: build["message"].as_str().ok_or(Error::BadBody)?.to_string(),
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, Error>>()?,
|
||||
downloads: value["downloads"]
|
||||
.as_object()
|
||||
.ok_or(Error::BadBody)?
|
||||
.into_iter()
|
||||
.map(|(key, build)| {
|
||||
Ok((
|
||||
key.to_string(),
|
||||
BuildDownload {
|
||||
name: build["name"].as_str().ok_or(Error::BadBody)?.to_string(),
|
||||
size: build["size"].as_u64().ok_or(Error::BadBody)?,
|
||||
url: build["url"].as_str().ok_or(Error::BadBody)?.to_string(),
|
||||
checksums: build["checksums"]
|
||||
.as_object()
|
||||
.ok_or(Error::BadBody)?
|
||||
.into_iter()
|
||||
.map(|(key, value)| {
|
||||
Ok((
|
||||
key.to_string(),
|
||||
value.as_str().ok_or(Error::BadBody)?.to_string(),
|
||||
))
|
||||
})
|
||||
.collect::<Result<_, Error>>()?,
|
||||
},
|
||||
))
|
||||
})
|
||||
.collect::<Result<_, Error>>()?,
|
||||
})
|
||||
}
|
||||
90
papermc-api/src/models.rs
Normal file
90
papermc-api/src/models.rs
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Project {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub versions: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SupportStatus {
|
||||
Supported,
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
pub struct SupportStatusParseError;
|
||||
|
||||
impl FromStr for SupportStatus {
|
||||
type Err = SupportStatusParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"SUPPORTED" => Ok(Self::Supported),
|
||||
"UNSUPPORTED" => Ok(Self::Unsupported),
|
||||
_ => Err(SupportStatusParseError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Java {
|
||||
pub minimum_version: u64,
|
||||
pub recommended_flags: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Version {
|
||||
pub id: String,
|
||||
pub support_status: SupportStatus,
|
||||
pub java: Java,
|
||||
pub builds: Vec<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Build {
|
||||
pub id: u64,
|
||||
pub time: DateTime<Utc>,
|
||||
pub channel: BuildChannel,
|
||||
pub commits: Vec<BuildCommit>,
|
||||
pub downloads: HashMap<String, BuildDownload>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BuildChannel {
|
||||
Alpha,
|
||||
Beta,
|
||||
Stable,
|
||||
}
|
||||
|
||||
pub struct BuildChannelParseError;
|
||||
|
||||
impl FromStr for BuildChannel {
|
||||
type Err = BuildChannelParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"STABLE" => Ok(Self::Stable),
|
||||
"BETA" => Ok(Self::Beta),
|
||||
"ALPHA" => Ok(Self::Alpha),
|
||||
_ => Err(BuildChannelParseError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BuildCommit {
|
||||
pub sha: String,
|
||||
pub time: DateTime<Utc>,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BuildDownload {
|
||||
pub name: String,
|
||||
pub checksums: HashMap<String, String>,
|
||||
pub size: u64,
|
||||
pub url: String,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue