use std::{collections::HashMap, path::PathBuf};
use anyhow::bail;
use autoschematic_macros::FieldTypes;
use documented::{Documented, DocumentedFields};
use serde::{Deserialize, Serialize};
use crate::macros::FieldTypes;
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Documented, DocumentedFields, FieldTypes)]
#[serde(deny_unknown_fields)]
pub struct AutoschematicConfig {
pub prefixes: HashMap<String, Prefix>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Documented, DocumentedFields, FieldTypes)]
#[serde(deny_unknown_fields)]
pub struct Prefix {
pub connectors: Vec<Connector>,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub resource_group: Option<String>,
#[serde(default)]
pub tasks: Vec<AuxTask>,
#[serde(default)]
pub env_file: Option<String>,
#[serde(default)]
pub env: HashMap<String, String>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Documented, DocumentedFields, FieldTypes)]
#[serde(deny_unknown_fields)]
pub struct AuxTask {
pub name: String,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub env: HashMap<String, String>,
#[serde(default)]
pub env_file: Option<String>,
}
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Documented)]
pub enum Protocol {
#[default]
Tarpc,
Grpc,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[serde(deny_unknown_fields)]
pub enum Spec {
Binary {
path: PathBuf,
#[serde(default)]
protocol: Protocol,
},
Cargo {
name: String,
#[serde(default)]
version: Option<String>,
#[serde(default)]
binary: Option<String>,
#[serde(default)]
git: Option<String>,
#[serde(default)]
features: Option<Vec<String>>,
#[serde(default)]
protocol: Protocol,
},
CargoLocal {
path: PathBuf,
#[serde(default)]
binary: Option<String>,
#[serde(default)]
features: Option<Vec<String>>,
#[serde(default)]
protocol: Protocol,
},
TypescriptLocal {
path: PathBuf,
},
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
pub struct SpecCommand {
pub binary: PathBuf,
pub args: Vec<String>,
}
impl Spec {
pub fn protocol(&self) -> Protocol {
match self {
Spec::Binary { protocol, .. } => protocol.clone(),
Spec::Cargo { protocol, .. } => protocol.clone(),
Spec::CargoLocal { protocol, .. } => protocol.clone(),
Spec::TypescriptLocal { .. } => Protocol::Grpc,
}
}
pub fn pre_command(&self) -> anyhow::Result<Option<SpecCommand>> {
match self {
Spec::CargoLocal {
path, binary, features, ..
} => {
let manifest_path = path.join("Cargo.toml");
if !manifest_path.is_file() {
bail!("Spec::pre_command: No Cargo.toml under {}", path.display())
}
let mut args: Vec<String> = vec!["build", "--release", "--manifest-path", manifest_path.to_str().unwrap()]
.into_iter()
.map(String::from)
.collect();
if let Some(binary) = binary {
args.append(&mut vec![String::from("--bin"), binary.to_string()]);
}
if let Some(features) = features.to_owned()
&& !features.is_empty()
{
args.append(&mut vec![String::from("--features"), features.join(",").to_string()]);
}
Ok(Some(SpecCommand {
binary: "cargo".into(),
args,
}))
}
_ => Ok(None),
}
}
pub fn command(&self) -> anyhow::Result<SpecCommand> {
match self {
Spec::Binary { path, .. } => {
let mut binary_path = path.clone();
if !binary_path.is_file() {
binary_path = which::which(binary_path)?;
}
if !binary_path.is_file() {
bail!("launch_server_binary: {}: not found", binary_path.display())
}
Ok(SpecCommand {
binary: binary_path,
args: Vec::new(),
})
}
Spec::Cargo { name, .. } => {
let cargo_home = match std::env::var("CARGO_HOME") {
Ok(p) => PathBuf::from(p),
Err(_) => {
let Ok(home) = std::env::var("HOME") else {
bail!("$HOME not set!");
};
PathBuf::from(home).join(".cargo")
}
};
let binary_path = cargo_home.join("bin").join(name);
if !binary_path.is_file() {
bail!("launch_server_binary: {}: not found", binary_path.display())
}
Ok(SpecCommand {
binary: binary_path,
args: Vec::new(),
})
}
Spec::CargoLocal {
path, binary, features, ..
} => {
let manifest_path = path.join("Cargo.toml");
if !manifest_path.is_file() {
bail!("Spec::pre_command: No Cargo.toml under {}", path.display())
}
let mut args: Vec<String> = vec!["run", "--release", "--manifest-path", manifest_path.to_str().unwrap()]
.into_iter()
.map(String::from)
.collect();
if let Some(binary) = binary {
args.append(&mut vec![String::from("--bin"), binary.to_string()]);
}
if let Some(features) = features.to_owned()
&& !features.is_empty()
{
args.append(&mut vec![String::from("--features"), features.join(",").to_string()]);
}
Ok(SpecCommand {
binary: "cargo".into(),
args,
})
}
Spec::TypescriptLocal { path } => {
if !path.is_file() {
bail!("launch_server_binary: {}: not found", path.display())
}
let args = vec![
path.to_string_lossy().to_string(),
];
Ok(SpecCommand {
binary: "tsx".into(),
args,
})
}
}
}
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Documented, DocumentedFields, FieldTypes)]
#[serde(deny_unknown_fields)]
pub struct Connector {
pub shortname: String,
pub spec: Spec,
#[serde(default)]
pub env: HashMap<String, String>,
#[serde(default)]
pub env_file: Option<String>,
}
impl AutoschematicConfig {
pub fn resource_group_map(&self) -> HashMap<String, Vec<PathBuf>> {
let mut res = HashMap::new();
for (prefix_name, prefix) in &self.prefixes {
if let Some(resource_group) = &prefix.resource_group {
if !res.contains_key(resource_group) {
res.insert(resource_group.to_string(), Vec::new());
}
if let Some(prefixes) = res.get_mut(resource_group) {
prefixes.push(PathBuf::from(prefix_name));
}
}
}
tracing::debug!("Resource group map: {:?}", res);
res
}
}