moon's haunted
This commit is contained in:
commit
dc4b909c5a
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
target/
|
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[submodule "crates/subresource_integrity"]
|
||||||
|
path = crates/subresource_integrity
|
||||||
|
url = https://fem.mint.lgbt/kitsunecafe/subresource-integrity.git
|
||||||
|
[submodule "crates/maybe_owned"]
|
||||||
|
path = crates/maybe_owned
|
||||||
|
url = https://fem.mint.lgbt/kitsunecafe/maybe-owned.git
|
1461
Cargo.lock
generated
Normal file
1461
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
19
Cargo.toml
Normal file
19
Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "unity_release_api"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
derive = "1.0.0"
|
||||||
|
features = "0.10.0"
|
||||||
|
futures = "0.3.30"
|
||||||
|
maybe_owned = { version = "0.1.0", path = "crates/maybe_owned" }
|
||||||
|
reqwest = "0.12.8"
|
||||||
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
|
serde_json = "1.0.128"
|
||||||
|
subresource_integrity = { version = "0.1.0", path = "crates/subresource_integrity", features = ["serde", "md5"] }
|
||||||
|
time = { version = "0.3.36", features = ["serde"] }
|
||||||
|
url = "2.5.2"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tokio = { version = "1.40.0", features = ["rt", "macros"] }
|
1
crates/maybe_owned
Submodule
1
crates/maybe_owned
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 7c9bd9c81af7f5badcde8a88f710c453db9e413b
|
1
crates/subresource_integrity
Submodule
1
crates/subresource_integrity
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 05f62a89aa7058616b104a2b5fbabb63fff023c2
|
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
90
src/common.rs
Normal file
90
src/common.rs
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub enum APIVersion {
|
||||||
|
#[default]
|
||||||
|
V1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for APIVersion {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::V1 => write!(f, "v1"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, serde::Deserialize)]
|
||||||
|
pub enum Order {
|
||||||
|
Ascending,
|
||||||
|
#[default]
|
||||||
|
Descending,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Order {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Ascending => write!(f, "RELEASE_DATE_ASC"),
|
||||||
|
Self::Descending => write!(f, "RELEASE_DATE_DESC"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum Stream {
|
||||||
|
#[serde(rename = "LTS")]
|
||||||
|
LTS,
|
||||||
|
Beta,
|
||||||
|
Alpha,
|
||||||
|
Tech,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Stream {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::LTS => write!(f, "LTS"),
|
||||||
|
Self::Beta => write!(f, "BETA"),
|
||||||
|
Self::Alpha => write!(f, "ALPHA"),
|
||||||
|
Self::Tech => write!(f, "TECH"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum Platform {
|
||||||
|
Windows,
|
||||||
|
Linux,
|
||||||
|
#[serde(rename = "MAC_OS")]
|
||||||
|
MacOS,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Platform {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Windows => write!(f, "WINDOWS"),
|
||||||
|
Self::Linux => write!(f, "LINUX"),
|
||||||
|
Self::MacOS => write!(f, "MAC_OS"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum Architecture {
|
||||||
|
X86_64,
|
||||||
|
Arm64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Architecture {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Arm64 => write!(f, "ARM64"),
|
||||||
|
Self::X86_64 => write!(f, "X86_64"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub struct Version(pub String);
|
4
src/lib.rs
Normal file
4
src/lib.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
#![feature(result_flattening)]
|
||||||
|
pub mod common;
|
||||||
|
pub mod request;
|
||||||
|
pub mod response;
|
246
src/request.rs
Normal file
246
src/request.rs
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use maybe_owned::MaybeOwned;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
common::*,
|
||||||
|
response::{self, OffsetConnection},
|
||||||
|
};
|
||||||
|
|
||||||
|
type Parameter<'a> = (&'a str, &'a str);
|
||||||
|
|
||||||
|
pub struct RequestBuilder<'a> {
|
||||||
|
client: Option<&'a reqwest::Client>,
|
||||||
|
api_version: APIVersion,
|
||||||
|
limit: u8,
|
||||||
|
offset: usize,
|
||||||
|
order: Order,
|
||||||
|
stream: Option<Stream>,
|
||||||
|
platform: Option<Platform>,
|
||||||
|
architecture: Option<Architecture>,
|
||||||
|
version: Option<Version>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RequestBuilder<'_> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
limit: 10,
|
||||||
|
client: Default::default(),
|
||||||
|
api_version: Default::default(),
|
||||||
|
offset: Default::default(),
|
||||||
|
order: Default::default(),
|
||||||
|
stream: Default::default(),
|
||||||
|
platform: Default::default(),
|
||||||
|
architecture: Default::default(),
|
||||||
|
version: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RequestBuilder<'a> {
|
||||||
|
pub fn new(client: &'a reqwest::Client) -> Self {
|
||||||
|
Self {
|
||||||
|
client: Some(client),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_limit(self, limit: u8) -> Self {
|
||||||
|
Self { limit, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_offset(self, offset: usize) -> Self {
|
||||||
|
Self { offset, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_order(self, order: Order) -> Self {
|
||||||
|
Self { order, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_stream(self, stream: Stream) -> Self {
|
||||||
|
Self {
|
||||||
|
stream: Some(stream),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_platform(self, platform: Platform) -> Self {
|
||||||
|
Self {
|
||||||
|
platform: Some(platform),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_architecture(self, architecture: Architecture) -> Self {
|
||||||
|
Self {
|
||||||
|
architecture: Some(architecture),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_version(self, version: String) -> Self {
|
||||||
|
Self {
|
||||||
|
version: Some(Version(version)),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send(self) -> Result<OffsetConnection, response::Error> {
|
||||||
|
Into::<Request>::into(self).send().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<RequestBuilder<'a>> for Request<'a> {
|
||||||
|
fn from(val: RequestBuilder<'a>) -> Self {
|
||||||
|
Request {
|
||||||
|
client: Cow::Borrowed(val.client.unwrap()),
|
||||||
|
api_version: val.api_version.to_string(),
|
||||||
|
limit: val.limit.to_string(),
|
||||||
|
offset: val.offset.to_string(),
|
||||||
|
order: val.order.to_string(),
|
||||||
|
stream: val.stream.map_or_else(String::default, |s| s.to_string()),
|
||||||
|
|
||||||
|
platform: val
|
||||||
|
.platform
|
||||||
|
.map_or_else(String::default, |p| p.to_string()),
|
||||||
|
|
||||||
|
architecture: val
|
||||||
|
.architecture
|
||||||
|
.map_or_else(String::default, |a| a.to_string()),
|
||||||
|
|
||||||
|
version: val
|
||||||
|
.version
|
||||||
|
.map_or_else(String::default, |v| v.0.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Request<'a> {
|
||||||
|
client: Cow<'a, reqwest::Client>,
|
||||||
|
api_version: String,
|
||||||
|
limit: String,
|
||||||
|
offset: String,
|
||||||
|
order: String,
|
||||||
|
stream: String,
|
||||||
|
platform: String,
|
||||||
|
architecture: String,
|
||||||
|
version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Request<'_> {
|
||||||
|
const API_URI: &'static str = "https://services.api.unity.com/unity/editor/release/";
|
||||||
|
|
||||||
|
fn as_parameters(&self) -> impl Iterator<Item = Parameter> {
|
||||||
|
[
|
||||||
|
("limit", self.limit.as_str()),
|
||||||
|
("offset", self.offset.as_str()),
|
||||||
|
("order", self.order.as_str()),
|
||||||
|
("stream", self.stream.as_str()),
|
||||||
|
("platform", self.platform.as_str()),
|
||||||
|
("architecture", self.architecture.as_str()),
|
||||||
|
("version", self.version.as_str()),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.filter(|p| !p.1.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_dir(value: &str) -> String {
|
||||||
|
format!("{value}/")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn api_root(&self) -> Result<Url, url::ParseError> {
|
||||||
|
Url::parse(Self::API_URI)
|
||||||
|
.and_then(|url| url.join(&Self::as_dir(&self.api_version)))
|
||||||
|
.and_then(|url| url.join("releases"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_uri(&self) -> Result<Url, url::ParseError> {
|
||||||
|
self.api_root()
|
||||||
|
.and_then(|url| Url::parse_with_params(url.as_str(), self.as_parameters()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send(&self) -> Result<OffsetConnection, response::Error> {
|
||||||
|
let response = self.client.get(self.build_uri().unwrap()).send().await?;
|
||||||
|
response::parse(response).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct UnityReleaseClient<'a> {
|
||||||
|
client: MaybeOwned<'a, reqwest::Client>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> UnityReleaseClient<'a> {
|
||||||
|
pub fn new(client: impl Into<MaybeOwned<'a, reqwest::Client>>) -> Self {
|
||||||
|
Self {
|
||||||
|
client: client.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request(&self) -> RequestBuilder {
|
||||||
|
match &self.client {
|
||||||
|
MaybeOwned::Owned(x) => RequestBuilder::new(x),
|
||||||
|
MaybeOwned::Borrowed(x) => RequestBuilder::new(x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send(
|
||||||
|
&self,
|
||||||
|
request: impl Into<Request<'a>>,
|
||||||
|
) -> Result<OffsetConnection, response::Error> {
|
||||||
|
request.into().send().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::request::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn defaut_request_uri() {
|
||||||
|
let expected = "https://services.api.unity.com/unity/editor/release/v1/releases?limit=10&offset=0&order=RELEASE_DATE_DESC";
|
||||||
|
let client = UnityReleaseClient::default();
|
||||||
|
let request: Request = client.request().into();
|
||||||
|
assert_eq!(request.build_uri().unwrap().as_str(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_request_uri() {
|
||||||
|
let expected = "https://services.api.unity.com/unity/editor/release/v1/releases?limit=5&offset=20&order=RELEASE_DATE_ASC&stream=LTS&platform=LINUX&architecture=ARM64&version=2020.3.44f1";
|
||||||
|
|
||||||
|
let client = UnityReleaseClient::default();
|
||||||
|
let request: Request = client
|
||||||
|
.request()
|
||||||
|
.with_order(Order::Ascending)
|
||||||
|
.with_limit(5)
|
||||||
|
.with_offset(20)
|
||||||
|
.with_stream(Stream::LTS)
|
||||||
|
.with_platform(Platform::Linux)
|
||||||
|
.with_architecture(Architecture::Arm64)
|
||||||
|
.with_version("2020.3.44f1".to_string())
|
||||||
|
.into();
|
||||||
|
|
||||||
|
assert_eq!(request.build_uri().unwrap().as_str(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn borrowed_client() {
|
||||||
|
let expected = "https://services.api.unity.com/unity/editor/release/v1/releases?limit=10&offset=0&order=RELEASE_DATE_ASC";
|
||||||
|
let request_client = reqwest::Client::default();
|
||||||
|
let client = UnityReleaseClient::new(request_client);
|
||||||
|
let request: Request = client.request().with_order(Order::Ascending).into();
|
||||||
|
|
||||||
|
assert_eq!(request.build_uri().unwrap().as_str(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[tokio::test]
|
||||||
|
// async fn test_connection() {
|
||||||
|
// let client = UnityReleaseClient::default();
|
||||||
|
// let request = client.request().with_offset(25).with_limit(25);
|
||||||
|
// let req: Request = request.into();
|
||||||
|
// // println!("{}", req.build_uri().unwrap());
|
||||||
|
// let response = req.send().await;
|
||||||
|
// print!("{response:?}");
|
||||||
|
// }
|
||||||
|
}
|
222
src/response.rs
Normal file
222
src/response.rs
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use reqwest::Response;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use subresource_integrity::Integrity;
|
||||||
|
use time::Date;
|
||||||
|
|
||||||
|
use crate::common::*;
|
||||||
|
|
||||||
|
fn handle_fractional_numbers<'de, D>(deserializer: D) -> Result<usize, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let value: f64 = Deserialize::deserialize(deserializer)?;
|
||||||
|
let value = value.ceil();
|
||||||
|
Ok(value as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum UnitKind {
|
||||||
|
Byte,
|
||||||
|
Kilobyte,
|
||||||
|
Megabyte,
|
||||||
|
Gigabyte,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub struct DigitalValue {
|
||||||
|
#[serde(deserialize_with = "handle_fractional_numbers")]
|
||||||
|
pub value: usize,
|
||||||
|
pub unit: UnitKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub struct ReleaseNotes {
|
||||||
|
pub url: PathBuf,
|
||||||
|
pub integrity: Option<Integrity>,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub kind: FileKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ThirdPartyNotice {
|
||||||
|
#[serde(rename = "originalFileName")]
|
||||||
|
pub original_filename: String,
|
||||||
|
pub url: PathBuf,
|
||||||
|
pub integrity: Option<Integrity>,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub kind: FileKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
#[serde(rename_all = "UPPERCASE")]
|
||||||
|
pub enum FileKind {
|
||||||
|
Text,
|
||||||
|
#[serde(rename = "TAR_GZ")]
|
||||||
|
TarGZ,
|
||||||
|
#[serde(rename = "TAR_XZ")]
|
||||||
|
TarXZ,
|
||||||
|
ZIP,
|
||||||
|
PKG,
|
||||||
|
EXE,
|
||||||
|
PO,
|
||||||
|
DMG,
|
||||||
|
LZMA,
|
||||||
|
LZ4,
|
||||||
|
MD,
|
||||||
|
PDF,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum ModuleCategory {
|
||||||
|
Documentation,
|
||||||
|
Platform,
|
||||||
|
LanguagePack,
|
||||||
|
DevTool,
|
||||||
|
Plugin,
|
||||||
|
Component,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum SKUFamily {
|
||||||
|
DOTS,
|
||||||
|
Classic,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub struct ExtractedPathRename {
|
||||||
|
pub from: PathBuf,
|
||||||
|
pub to: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub struct Eula {
|
||||||
|
pub url: PathBuf,
|
||||||
|
pub integrity: Option<Integrity>,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub kind: FileKind,
|
||||||
|
pub label: String,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Module {
|
||||||
|
pub id: String,
|
||||||
|
pub slug: String,
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub category: ModuleCategory,
|
||||||
|
pub url: PathBuf,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub kind: FileKind,
|
||||||
|
pub download_size: DigitalValue,
|
||||||
|
pub installed_size: DigitalValue,
|
||||||
|
pub sub_modules: Vec<Module>,
|
||||||
|
pub required: bool,
|
||||||
|
pub hidden: bool,
|
||||||
|
pub extracted_path_rename: Option<ExtractedPathRename>,
|
||||||
|
#[serde(rename = "preSelected")]
|
||||||
|
pub preselected: bool,
|
||||||
|
pub destination: Option<String>,
|
||||||
|
pub eula: Option<Vec<Eula>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Download {
|
||||||
|
pub url: PathBuf,
|
||||||
|
pub integrity: Option<Integrity>,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub kind: FileKind,
|
||||||
|
pub platform: Platform,
|
||||||
|
pub architecture: Architecture,
|
||||||
|
pub download_size: DigitalValue,
|
||||||
|
pub installed_size: DigitalValue,
|
||||||
|
pub modules: Vec<Module>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Release {
|
||||||
|
pub version: Version,
|
||||||
|
pub date: Option<Date>,
|
||||||
|
pub notes: Option<ReleaseNotes>,
|
||||||
|
pub stream: Stream,
|
||||||
|
pub downloads: Vec<Download>,
|
||||||
|
pub sku_family: SKUFamily,
|
||||||
|
pub recommended: bool,
|
||||||
|
pub unity_hub_link: Option<String>,
|
||||||
|
pub short_revision: String,
|
||||||
|
pub third_party_notices: Vec<ThirdPartyNotice>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub struct OffsetConnection {
|
||||||
|
pub offset: usize,
|
||||||
|
pub limit: u8,
|
||||||
|
pub total: usize,
|
||||||
|
pub results: Vec<Release>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn parse(response: Response) -> Result<OffsetConnection, Error> {
|
||||||
|
let body = response.text().await?;
|
||||||
|
println!("{body}");
|
||||||
|
serde_json::from_str::<ResponseKind>(body.as_str())
|
||||||
|
.map(Result::from)
|
||||||
|
.map_err(Error::from)
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum ResponseKind {
|
||||||
|
Ok(OffsetConnection),
|
||||||
|
Err(APIError),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
API(APIError),
|
||||||
|
Parse(serde_json::Error),
|
||||||
|
Request(reqwest::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ResponseKind> for Result<OffsetConnection, Error> {
|
||||||
|
fn from(value: ResponseKind) -> Self {
|
||||||
|
match value {
|
||||||
|
ResponseKind::Ok(v) => Ok(v),
|
||||||
|
ResponseKind::Err(e) => Err(Error::from(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<APIError> for Error {
|
||||||
|
fn from(value: APIError) -> Self {
|
||||||
|
Self::API(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<serde_json::Error> for Error {
|
||||||
|
fn from(value: serde_json::Error) -> Self {
|
||||||
|
Self::Parse(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<reqwest::Error> for Error {
|
||||||
|
fn from(value: reqwest::Error) -> Self {
|
||||||
|
Self::Request(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub struct APIError {
|
||||||
|
pub title: String,
|
||||||
|
pub status: u16,
|
||||||
|
pub detail: String,
|
||||||
|
}
|
0
src/utils.rs
Normal file
0
src/utils.rs
Normal file
Loading…
Reference in a new issue