use serde::{de::Error, Deserialize};
mod legacy;
mod v2;
mod v3;
#[derive(Debug)]
pub enum VersionedIndex {
V2(v2::Index),
V3(v3::Index),
Unsupported,
}
impl<'de> serde::Deserialize<'de> for VersionedIndex {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let value = serde_json::Value::deserialize(d)?;
let version = value.get("version").and_then(serde_json::Value::as_u64);
match version {
None => Err(D::Error::custom("missing version field in versioned index")),
Some(2) => Ok(VersionedIndex::V2(v2::Index::deserialize(value).unwrap())),
Some(3) => Ok(VersionedIndex::V3(v3::Index::deserialize(value).unwrap())),
_ => Ok(VersionedIndex::Unsupported),
}
}
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum Index {
Versioned(VersionedIndex),
Legacy(legacy::Index),
Unsupported(serde_json::Value),
}
impl Index {
pub fn channels(&self) -> &[Channel] {
match self {
Index::Versioned(VersionedIndex::V2(i)) => i.channels.as_slice(),
Index::Versioned(VersionedIndex::V3(i)) => i.channels.as_slice(),
_ => &[],
}
}
}
#[derive(Debug, Deserialize)]
pub struct Channel {
pub name: ChannelName,
pub version: String,
pub date: Option<String>,
}
impl std::fmt::Display for Channel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.name {
ChannelName::Bleeding | ChannelName::Latest => {
write!(f, "{} ({})", self.name, self.version)
}
ChannelName::Nightly => {
write!(
f,
"{} ({}, {})",
self.name,
self.version,
self.date.as_deref().unwrap_or("unknown")
)
}
ChannelName::Unknown(_) => Ok(()),
}
}
}
#[derive(Debug, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub enum ChannelName {
Bleeding,
Latest,
Nightly,
#[serde(untagged)]
Unknown(String),
}
impl std::fmt::Display for ChannelName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ChannelName::Bleeding => write!(f, "bleeding"),
ChannelName::Latest => write!(f, "latest"),
ChannelName::Nightly => write!(f, "nightly"),
ChannelName::Unknown(c) => write!(f, "{c}"),
}
}
}
#[derive(Debug)]
pub enum VersionedComponentIndex {
V2(v2::ComponentIndex),
Unsupported,
}
impl<'de> serde::Deserialize<'de> for VersionedComponentIndex {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let value = serde_json::Value::deserialize(d)?;
let version = value.get("version").and_then(serde_json::Value::as_u64);
match version {
None => Err(D::Error::custom(
"missing version field in versioned component index",
)),
Some(2) => Ok(VersionedComponentIndex::V2(
v2::ComponentIndex::deserialize(value).unwrap(),
)),
_ => Ok(VersionedComponentIndex::Unsupported),
}
}
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum ComponentIndex {
Versioned(VersionedComponentIndex),
Unsupported(serde_json::Value),
}
#[derive(Debug, Clone, Deserialize)]
pub struct Component {
pub name: String,
pub file: String,
pub sha256: String,
}
impl ComponentIndex {
pub fn components(&self) -> &[Component] {
match self {
ComponentIndex::Versioned(VersionedComponentIndex::V2(i)) => i.components.as_slice(),
ComponentIndex::Unsupported(_) => &[],
_ => &[],
}
}
}
#[derive(Debug)]
pub enum VersionedChannelIndex {
V2(v2::ChannelIndex),
V3(v3::ChannelIndex),
Unsupported,
}
impl<'de> serde::Deserialize<'de> for VersionedChannelIndex {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let value = serde_json::Value::deserialize(d)?;
let version = value.get("version").and_then(serde_json::Value::as_u64);
match version {
None => Err(D::Error::custom(
"missing version field in versioned channel index",
)),
Some(2) => Ok(VersionedChannelIndex::V2(
v2::ChannelIndex::deserialize(value).unwrap(),
)),
Some(3) => Ok(VersionedChannelIndex::V3(
v3::ChannelIndex::deserialize(value).unwrap(),
)),
_ => Ok(VersionedChannelIndex::Unsupported),
}
}
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum ChannelIndex {
Versioned(VersionedChannelIndex),
Unsupported(serde_json::Value),
}
impl ChannelIndex {
pub fn releases(&self) -> &[Release] {
match self {
ChannelIndex::Versioned(VersionedChannelIndex::V2(i)) => i.releases.as_slice(),
ChannelIndex::Versioned(VersionedChannelIndex::V3(i)) => i.releases.as_slice(),
ChannelIndex::Unsupported(_) => &[],
_ => &[],
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Release {
pub version: String,
pub layout_version1: Option<bool>,
pub bundle_source_dir: Option<bool>,
pub date: Option<String>,
pub targets: Option<Vec<Target>>,
}
impl Release {
pub fn is_host_supported(&self) -> bool {
let host = match Target::from_host() {
Ok(h) => h,
Err(_) => {
return false;
}
};
let targets = match &self.targets {
Some(t) => t,
None => {
return true;
}
};
targets.contains(&host)
}
}
#[derive(Debug, Deserialize, Clone, PartialEq)]
#[non_exhaustive]
pub enum Target {
#[serde(rename = "aarch64-apple-darwin")]
Aarch64MacOS,
#[serde(rename = "x86_64-apple-darwin")]
Amd64MacOS,
#[serde(rename = "x86_64-unknown-linux")]
Amd64Linux,
#[serde(rename = "x86_64-pc-windows")]
Amd64Windows,
#[serde(untagged)]
Unknown(String),
}
impl Target {
pub fn from_host() -> miette::Result<Self> {
match std::env::consts::OS {
"macos" => match std::env::consts::ARCH {
"aarch64" => Ok(Target::Aarch64MacOS),
"x86_64" => Ok(Target::Amd64MacOS),
_ => Err(miette::miette!("unsupported architecture")),
},
"linux" => Ok(Target::Amd64Linux),
"windows" => Ok(Target::Amd64Windows),
_ => Err(miette::miette!("unsupported platform")),
}
}
}
impl std::fmt::Display for Target {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Target::Aarch64MacOS => write!(f, "aarch64-apple-darwin"),
Target::Amd64MacOS => write!(f, "x86_64-apple-darwin"),
Target::Amd64Linux => write!(f, "x86_64-unknown-linux"),
Target::Amd64Windows => write!(f, "x86_64-pc-windows"),
Target::Unknown(t) => write!(f, "{t}"),
}
}
}