use std::collections::HashMap;
use opys_bundle::{blob_id, blob_id_of, BlobSource, Blobs};
use opys_core::{encode_short_ruleset, Artifact, MojangRuleset, Source, Val, ValDefs, Valset};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(try_from = "Map<String, Value>", into = "Map<String, Value>")]
pub struct BuildArtifact {
fields: Map<String, Value>,
source: BuildSource,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum BuildSource {
Named(Source),
Carried(BlobSource),
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum BuildArtifactError {
#[error("an artifact has no `source`")]
NoSource,
#[error(
"an artifact's source is `{{ url }}`, `{{ blob }}`, `{{ file }}` or `{{ bytes }}`: {0}"
)]
Source(String),
#[error("cannot read {path}: {reason}")]
File { path: String, reason: String },
#[error("{0}")]
Artifact(String),
}
impl TryFrom<Map<String, Value>> for BuildArtifact {
type Error = BuildArtifactError;
fn try_from(mut fields: Map<String, Value>) -> Result<Self, Self::Error> {
let source = fields
.remove("source")
.ok_or(BuildArtifactError::NoSource)?;
let carried = source
.as_object()
.is_some_and(|s| s.contains_key("file") || s.contains_key("bytes"));
let wrong = |e: serde_json::Error| BuildArtifactError::Source(e.to_string());
let source = if carried {
BuildSource::Carried(serde_json::from_value(source).map_err(wrong)?)
} else {
BuildSource::Named(serde_json::from_value(source).map_err(wrong)?)
};
Ok(BuildArtifact { fields, source })
}
}
impl From<BuildArtifact> for Map<String, Value> {
fn from(artifact: BuildArtifact) -> Self {
let source = match &artifact.source {
BuildSource::Named(source) => serde_json::to_value(source),
BuildSource::Carried(source) => serde_json::to_value(source),
};
let mut fields = artifact.fields;
fields.insert("source".to_owned(), source.unwrap_or(Value::Null));
fields
}
}
impl From<Artifact> for BuildArtifact {
fn from(artifact: Artifact) -> Self {
let source = artifact.source.clone();
let mut fields = match serde_json::to_value(artifact) {
Ok(Value::Object(fields)) => fields,
_ => Map::new(),
};
fields.remove("source");
BuildArtifact {
fields,
source: BuildSource::Named(source),
}
}
}
impl BuildArtifact {
fn carried_at(path: &str, source: BlobSource) -> Self {
let mut fields = Map::new();
fields.insert("path".to_owned(), Value::String(path.to_owned()));
BuildArtifact {
fields,
source: BuildSource::Carried(source),
}
}
pub fn file(path: &str, file: impl Into<std::path::PathBuf>) -> Self {
Self::carried_at(path, BlobSource::File(file.into()))
}
pub fn bytes(path: &str, bytes: Vec<u8>) -> Self {
Self::carried_at(path, BlobSource::Bytes(bytes))
}
pub fn with_rules(mut self, rules: &MojangRuleset) -> Self {
if !rules.is_empty() {
if let Ok(rules) = serde_json::to_value(encode_short_ruleset(rules)) {
self.fields.insert("rules".to_owned(), rules);
}
}
self
}
pub fn path(&self) -> Option<&str> {
self.fields.get("path").and_then(Value::as_str)
}
pub fn carrying(artifacts: Vec<Artifact>, blobs: &Blobs) -> Vec<BuildArtifact> {
artifacts
.into_iter()
.map(|artifact| {
let held = artifact.blob_id().and_then(|id| blobs.get(id)).cloned();
let mut built = BuildArtifact::from(artifact);
if let Some(source) = held {
built.source = BuildSource::Carried(source);
}
built
})
.collect()
}
pub fn resolve(self) -> Result<(Artifact, Option<(String, BlobSource)>), BuildArtifactError> {
let mut fields = self.fields;
let (source, held) = match self.source {
BuildSource::Named(source) => (source, None),
BuildSource::Carried(carried) => {
let (id, size) = match &carried {
BlobSource::Bytes(bytes) => (blob_id(bytes), bytes.len() as u64),
BlobSource::File(path) => {
let read = |e: std::io::Error| BuildArtifactError::File {
path: path.display().to_string(),
reason: e.to_string(),
};
blob_id_of(std::fs::File::open(path).map_err(read)?).map_err(read)?
}
};
fields.insert("size".to_owned(), Value::from(size));
(Source::Blob { blob: id.clone() }, Some((id, carried)))
}
};
let source = serde_json::to_value(source)
.map_err(|e| BuildArtifactError::Artifact(e.to_string()))?;
fields.insert("source".to_owned(), source);
let artifact = serde_json::from_value(Value::Object(fields))
.map_err(|e| BuildArtifactError::Artifact(e.to_string()))?;
Ok((artifact, held))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum LaunchFragment {
Text(String),
One(Val),
Many(Valset),
}
pub type LaunchGroups = HashMap<String, LaunchFragment>;
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct Contribution {
pub artifacts: Vec<BuildArtifact>,
pub vars: ValDefs,
pub launch: LaunchGroups,
pub envs: ValDefs,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PluginOutput {
pub name: String,
#[serde(default)]
pub contribution: Contribution,
}