use crate::config::{AsyncConf, ErrorConf, ErrorConfField, TracingConf};
use anyhow::{anyhow, Error};
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use std::collections::HashMap;
use std::rc::Rc;
use witx::{Document, Id, InterfaceFunc, Module, NamedType, TypeRef};
pub use crate::config::Asyncness;
pub struct CodegenSettings {
pub errors: ErrorTransform,
pub async_: AsyncConf,
pub wasmtime: bool,
pub tracing: TracingConf,
pub mutable: bool,
}
impl CodegenSettings {
pub fn new(
error_conf: &ErrorConf,
async_: &AsyncConf,
doc: &Document,
wasmtime: bool,
tracing: &TracingConf,
mutable: bool,
) -> Result<Self, Error> {
let errors = ErrorTransform::new(error_conf, doc)?;
Ok(Self {
errors,
async_: async_.clone(),
wasmtime,
tracing: tracing.clone(),
mutable,
})
}
pub fn get_async(&self, module: &Module, func: &InterfaceFunc) -> Asyncness {
self.async_.get(module.name.as_str(), func.name.as_str())
}
}
pub struct ErrorTransform {
m: Vec<ErrorType>,
}
impl ErrorTransform {
pub fn empty() -> Self {
Self { m: Vec::new() }
}
pub fn new(conf: &ErrorConf, doc: &Document) -> Result<Self, Error> {
let mut richtype_identifiers = HashMap::new();
let m = conf.iter().map(|(ident, field)|
match field {
ErrorConfField::Trappable(field) => if let Some(abi_type) = doc.typename(&Id::new(ident.to_string())) {
Ok(ErrorType::Generated(TrappableErrorType { abi_type, rich_type: field.rich_error.clone() }))
} else {
Err(anyhow!("No witx typename \"{}\" found", ident.to_string()))
},
ErrorConfField::User(field) => if let Some(abi_type) = doc.typename(&Id::new(ident.to_string())) {
if let Some(ident) = field.rich_error.get_ident() {
if let Some(prior_def) = richtype_identifiers.insert(ident.clone(), field.err_loc.clone())
{
return Err(anyhow!(
"duplicate rich type identifier of {:?} not allowed. prior definition at {:?}",
ident, prior_def
));
}
Ok(ErrorType::User(UserErrorType {
abi_type,
rich_type: field.rich_error.clone(),
method_fragment: ident.to_string()
}))
} else {
return Err(anyhow!(
"rich error type must be identifier for now - TODO add ability to provide a corresponding identifier: {:?}",
field.err_loc
))
}
}
else { Err(anyhow!("No witx typename \"{}\" found", ident.to_string())) }
}
).collect::<Result<Vec<_>, Error>>()?;
Ok(Self { m })
}
pub fn iter(&self) -> impl Iterator<Item = &ErrorType> {
self.m.iter()
}
pub fn for_abi_error(&self, tref: &TypeRef) -> Option<&ErrorType> {
match tref {
TypeRef::Name(nt) => self.for_name(nt),
TypeRef::Value { .. } => None,
}
}
pub fn for_name(&self, nt: &NamedType) -> Option<&ErrorType> {
self.m.iter().find(|e| e.abi_type().name == nt.name)
}
}
pub enum ErrorType {
User(UserErrorType),
Generated(TrappableErrorType),
}
impl ErrorType {
pub fn abi_type(&self) -> &NamedType {
match self {
Self::User(u) => &u.abi_type,
Self::Generated(r) => &r.abi_type,
}
}
}
pub struct TrappableErrorType {
abi_type: Rc<NamedType>,
rich_type: Ident,
}
impl TrappableErrorType {
pub fn abi_type(&self) -> TypeRef {
TypeRef::Name(self.abi_type.clone())
}
pub fn typename(&self) -> TokenStream {
let richtype = &self.rich_type;
quote!(#richtype)
}
}
pub struct UserErrorType {
abi_type: Rc<NamedType>,
rich_type: syn::Path,
method_fragment: String,
}
impl UserErrorType {
pub fn abi_type(&self) -> TypeRef {
TypeRef::Name(self.abi_type.clone())
}
pub fn typename(&self) -> TokenStream {
let t = &self.rich_type;
quote!(#t)
}
pub fn method_fragment(&self) -> &str {
&self.method_fragment
}
}