use crate::*;
use brec_scheme::{
SchemeBlock, SchemeBlockField, SchemeConfig, SchemeContext, SchemeFieldType, SchemeFile,
SchemePayload, SchemePayloadField, SchemePayloadVariant, SchemeType,
};
use std::{
env, fs,
path::{Path, PathBuf},
};
pub struct Scheme<'a> {
collector: &'a Collector,
cfg: &'a Config,
package: String,
}
impl<'a> Scheme<'a> {
pub fn generate(collector: &'a Collector, cfg: &'a Config) -> Result<(), E> {
let scheme = Self {
collector,
cfg,
package: get_pkg_name(),
};
scheme.write()
}
fn write(&self) -> Result<(), E> {
let output = SchemeFile {
version: brec_scheme::VERSION.to_owned(),
package: self.package.clone(),
config: self.config(),
blocks: self.blocks()?,
payloads: self.payloads()?,
contexts: self.contexts()?,
types: self.types()?,
};
let content = serde_json::to_vec_pretty(&output).map_err(std::io::Error::other)?;
let path = self.resolve_target_path()?;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(path, content)?;
Ok(())
}
fn config(&self) -> SchemeConfig {
let default_payloads = if self.cfg.is_no_default_payloads() {
Vec::new()
} else {
vec!["Bytes".to_owned(), "String".to_owned()]
};
SchemeConfig {
no_default_payloads: self.cfg.is_no_default_payloads(),
default_payloads,
}
}
fn blocks(&self) -> Result<Vec<SchemeBlock>, E> {
let package = self.package.clone();
let mut blocks = self
.collector
.blocks
.get(&package)
.into_iter()
.flat_map(|blocks| blocks.values())
.map(|block| {
Ok(SchemeBlock {
name: block.name.clone(),
fullname: block.fullname()?.to_string(),
fullpath: block.fullpath()?.to_string(),
visibility: block.vis.clone(),
no_crc: block.attrs.is_no_crc(),
fields: block
.fields
.iter()
.filter(|field| !field.injected)
.map(Self::field_to_scheme)
.collect(),
})
})
.collect::<Result<Vec<_>, E>>()?;
blocks.sort_by(|a, b| a.fullname.cmp(&b.fullname));
Ok(blocks)
}
fn payloads(&self) -> Result<Vec<SchemePayload>, E> {
let package = self.package.clone();
let mut payloads = self
.collector
.payloads
.get(&package)
.into_iter()
.flat_map(|payloads| payloads.values())
.filter(|payload| !payload.attrs.is_include())
.map(|payload| {
Ok(SchemePayload {
name: payload.name.clone(),
fullname: payload.fullname()?.to_string(),
fullpath: payload.fullpath()?.to_string(),
is_bincode: payload.attrs.is_bincode(),
is_crypt: payload.attrs.is_crypt(),
no_crc: payload.attrs.is_no_crc(),
no_auto_crc: payload.attrs.is_no_auto_crc(),
no_default_sig: payload.attrs.no_default_sig(),
hooks: payload.attrs.hooks(),
fields: Self::payload_fields(&payload.kind),
variants: Self::payload_variants(&payload.kind),
})
})
.collect::<Result<Vec<_>, E>>()?;
payloads.sort_by(|a, b| a.fullname.cmp(&b.fullname));
Ok(payloads)
}
fn contexts(&self) -> Result<Vec<SchemeContext>, E> {
let package = self.package.clone();
let mut contexts = self
.collector
.contexts
.get(&package)
.into_iter()
.flat_map(|contexts| contexts.values())
.map(|context| {
Ok(SchemeContext {
name: context.name.clone(),
fullname: context.fullname()?.to_string(),
fullpath: context.fullpath()?.to_string(),
fields: Self::payload_fields(&context.kind),
variants: Self::payload_variants(&context.kind),
})
})
.collect::<Result<Vec<_>, E>>()?;
contexts.sort_by(|a, b| a.fullname.cmp(&b.fullname));
Ok(contexts)
}
fn types(&self) -> Result<Vec<SchemeType>, E> {
let package = self.package.clone();
let mut types = self
.collector
.payloads
.get(&package)
.into_iter()
.flat_map(|payloads| payloads.values())
.filter(|payload| payload.attrs.is_include())
.map(|payload| {
Ok(SchemeType {
name: payload.name.clone(),
fullname: payload.fullname()?.to_string(),
fullpath: payload.fullpath()?.to_string(),
fields: Self::payload_fields(&payload.kind),
variants: Self::payload_variants(&payload.kind),
})
})
.collect::<Result<Vec<_>, E>>()?;
types.sort_by(|a, b| a.fullname.cmp(&b.fullname));
Ok(types)
}
fn field_to_scheme(field: &BlockField) -> SchemeBlockField {
SchemeBlockField {
name: field.name.clone(),
visibility: field.vis.clone(),
ty: Self::ty_to_scheme(&field.ty),
}
}
fn payload_fields(kind: &PayloadKind) -> Vec<SchemePayloadField> {
match kind {
PayloadKind::Struct(fields) => Self::payload_fields_group(fields),
PayloadKind::Enum(_) => Vec::new(),
}
}
fn payload_variants(kind: &PayloadKind) -> Vec<SchemePayloadVariant> {
match kind {
PayloadKind::Struct(_) => Vec::new(),
PayloadKind::Enum(variants) => variants
.iter()
.map(|variant| SchemePayloadVariant {
name: variant.name.clone(),
fields: Self::payload_fields_group(&variant.fields),
})
.collect(),
}
}
fn payload_fields_group(fields: &PayloadFields) -> Vec<SchemePayloadField> {
match fields {
PayloadFields::Named(fields) => fields
.iter()
.map(|field| SchemePayloadField {
name: Some(field.name.clone()),
visibility: Some(field.vis.clone()),
ty: Self::payload_ty_to_scheme(&field.ty),
})
.collect(),
PayloadFields::Unnamed(fields) => fields
.iter()
.map(|ty| SchemePayloadField {
name: None,
visibility: None,
ty: Self::payload_ty_to_scheme(ty),
})
.collect(),
PayloadFields::Unit => Vec::new(),
}
}
fn ty_to_scheme(ty: &BlockTy) -> SchemeFieldType {
SchemeFieldType::Block(ty.clone())
}
fn payload_ty_to_scheme(ty: &PayloadTy) -> SchemeFieldType {
SchemeFieldType::Payload(ty.clone())
}
fn resolve_target_path(&self) -> Result<PathBuf, E> {
if let Ok(out_dir) = env::var("OUT_DIR") {
let out_dir = PathBuf::from(out_dir);
if let Some(target_dir) = self.find_target_dir(&out_dir) {
return Ok(target_dir.join("brec.scheme.json"));
}
return Ok(out_dir.join("brec.scheme.json"));
}
if let Ok(target_dir) = env::var("CARGO_TARGET_DIR") {
return Ok(PathBuf::from(target_dir).join("brec.scheme.json"));
}
Ok(PathBuf::from(env::var("CARGO_MANIFEST_DIR")?).join("target/brec.scheme.json"))
}
fn find_target_dir<'b>(&self, path: &'b Path) -> Option<&'b Path> {
path.ancestors()
.find(|ancestor| ancestor.file_name().is_some_and(|name| name == "target"))
}
}