use heck::{ToKebabCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
use serde::Deserialize;
use std::collections::{HashMap, HashSet};
use std::fs;
use std::hash::{Hash, Hasher};
use std::path::Path;
#[derive(Debug, Deserialize)]
pub struct DawnApi {
#[serde(rename = "_comment")]
pub comment: Option<Vec<String>>,
#[serde(rename = "_doc")]
pub doc: Option<String>,
#[serde(rename = "_metadata")]
pub metadata: ApiMetadata,
#[serde(flatten)]
pub definitions: HashMap<String, Definition>,
}
impl DawnApi {
pub fn filter_by_tags(&self, enabled_tags: &[String]) -> DawnApi {
let mut filtered_definitions = HashMap::new();
for (name, def) in &self.definitions {
if Self::should_include_definition(def, enabled_tags) {
filtered_definitions.insert(name.clone(), (*def).clone());
}
}
DawnApi {
comment: self.comment.clone(),
doc: self.doc.clone(),
metadata: self.metadata.clone(),
definitions: filtered_definitions,
}
}
fn should_include_definition(def: &Definition, enabled_tags: &[String]) -> bool {
let def_tags = match def {
Definition::Native(d) => &d.tags,
Definition::Typedef(d) => &d.tags,
Definition::Enum(d) => &d.tags,
Definition::Bitmask(d) => &d.tags,
Definition::FunctionPointer(d) => &d.tags,
Definition::Structure(d) => &d.tags,
Definition::Object(d) => &d.tags,
Definition::Constant(d) => &d.tags,
Definition::Function(d) => &d.tags,
Definition::Callback(d) => &d.tags,
Definition::CallbackFunction(d) => &d.tags,
Definition::CallbackInfo(d) => &d.tags,
};
if def_tags.is_empty() {
return true;
}
def_tags.iter().any(|tag| enabled_tags.contains(tag))
}
pub fn enums(&self) -> Vec<(&String, &EnumDef)> {
self.definitions
.iter()
.filter_map(|(name, def)| match def {
Definition::Enum(enum_def) => Some((name, enum_def)),
_ => None,
})
.collect()
}
pub fn bitmasks(&self) -> Vec<(&String, &BitmaskDef)> {
self.definitions
.iter()
.filter_map(|(name, def)| match def {
Definition::Bitmask(bitmask_def) => Some((name, bitmask_def)),
_ => None,
})
.collect()
}
pub fn structures(&self) -> Vec<(&String, &StructureDef)> {
self.definitions
.iter()
.filter_map(|(name, def)| match def {
Definition::Structure(struct_def) => Some((name, struct_def)),
_ => None,
})
.collect()
}
pub fn extensions(&self) -> HashMap<&String, HashSet<Extension<'_>>> {
let mut extensions = HashMap::new();
for (name, def) in self.structures() {
for chain_root in &def.chain_roots {
extensions
.entry(chain_root)
.or_insert(HashSet::new())
.insert(Extension {
ty: name,
tags: &def.tags,
});
}
}
extensions
}
pub fn objects(&self) -> Vec<(&String, &ObjectDef)> {
self.definitions
.iter()
.filter_map(|(name, def)| match def {
Definition::Object(object_def) => Some((name, object_def)),
_ => None,
})
.collect()
}
pub fn functions(&self) -> Vec<(&String, &FunctionDef)> {
self.definitions
.iter()
.filter_map(|(name, def)| match def {
Definition::Function(func_def) => Some((name, func_def)),
_ => None,
})
.collect()
}
pub fn callbacks(&self) -> Vec<(&String, &CallbackDef)> {
self.definitions
.iter()
.filter_map(|(name, def)| match def {
Definition::Callback(callback_def) => Some((name, callback_def)),
_ => None,
})
.collect()
}
pub fn callback_functions(&self) -> Vec<(&String, &CallbackFunctionDef)> {
self.definitions
.iter()
.filter_map(|(name, def)| match def {
Definition::CallbackFunction(callback_func_def) => Some((name, callback_func_def)),
_ => None,
})
.collect()
}
pub fn callback_infos(&self) -> Vec<(&String, &CallbackInfoDef)> {
self.definitions
.iter()
.filter_map(|(name, def)| match def {
Definition::CallbackInfo(callback_info_def) => Some((name, callback_info_def)),
_ => None,
})
.collect()
}
pub fn constants(&self) -> Vec<(&String, &ConstantDef)> {
self.definitions
.iter()
.filter_map(|(name, def)| match def {
Definition::Constant(const_def) => Some((name, const_def)),
_ => None,
})
.collect()
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct ApiMetadata {
pub api: String,
pub namespace: String,
pub c_prefix: Option<String>,
pub proc_table_prefix: String,
pub native_namespace: String,
pub copyright_year: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "category")]
pub enum Definition {
#[serde(rename = "native")]
Native(NativeType),
#[serde(rename = "typedef")]
Typedef(TypedefDef),
#[serde(rename = "enum")]
Enum(EnumDef),
#[serde(rename = "bitmask")]
Bitmask(BitmaskDef),
#[serde(rename = "function pointer")]
FunctionPointer(FunctionPointerDef),
#[serde(rename = "structure")]
Structure(StructureDef),
#[serde(rename = "object")]
Object(ObjectDef),
#[serde(rename = "constant")]
Constant(ConstantDef),
#[serde(rename = "function")]
Function(FunctionDef),
#[serde(rename = "callback")]
Callback(CallbackDef),
#[serde(rename = "callback function")]
CallbackFunction(CallbackFunctionDef),
#[serde(rename = "callback info")]
CallbackInfo(CallbackInfoDef),
}
#[derive(Debug, Clone, Deserialize)]
pub struct NativeType {
#[serde(default)]
pub tags: Vec<String>,
#[serde(rename = "wire transparent", default = "default_true")]
pub wire_transparent: bool,
#[serde(rename = "wasm type")]
pub wasm_type: Option<String>,
#[serde(rename = "is nullable pointer")]
pub is_nullable_pointer: Option<bool>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct TypedefDef {
#[serde(default)]
pub tags: Vec<String>,
#[serde(rename = "type")]
pub target_type: String,
}
pub struct Extension<'a> {
pub ty: &'a String,
pub tags: &'a Vec<String>,
}
impl PartialEq for Extension<'_> {
fn eq(&self, other: &Self) -> bool {
self.ty == other.ty
}
}
impl Hash for Extension<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.ty.hash(state);
}
}
impl Eq for Extension<'_> {}
#[derive(Debug, Clone, Deserialize)]
pub struct EnumDef {
#[serde(default)]
pub tags: Vec<String>,
pub values: Vec<EnumValueDef>,
#[serde(rename = "emscripten_no_enum_table")]
pub emscripten_no_enum_table: Option<bool>,
#[serde(rename = "emscripten_string_to_int")]
pub emscripten_string_to_int: Option<bool>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BitmaskDef {
#[serde(default)]
pub tags: Vec<String>,
pub values: Vec<EnumValueDef>,
#[serde(rename = "emscripten_no_enum_table")]
pub emscripten_no_enum_table: Option<bool>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct EnumValueDef {
pub name: String,
pub value: serde_json::Value,
#[serde(default)]
pub tags: Vec<String>,
pub jsrepr: Option<String>,
#[serde(default = "default_true")]
pub valid: bool,
#[serde(rename = "emscripten_string_to_int")]
pub emscripten_string_to_int: Option<bool>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct FunctionPointerDef {
#[serde(default)]
pub tags: Vec<String>,
returns: Option<ReturnType>,
args: Vec<RecordMember>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct StructureDef {
#[serde(default)]
pub tags: Vec<String>,
pub members: Vec<RecordMember>,
#[serde(default)]
pub extensible: ExtensibleType,
pub chained: Option<String>,
#[serde(rename = "chain roots", default)]
pub chain_roots: Vec<String>,
#[serde(rename = "_comment")]
pub comment: Option<String>,
pub out: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExtensibleType {
Direction(String), Bool(bool),
}
impl ExtensibleType {
pub fn extensible(&self) -> bool {
match self {
ExtensibleType::Direction(_) => true,
ExtensibleType::Bool(v) => *v,
}
}
}
impl Default for ExtensibleType {
fn default() -> Self {
ExtensibleType::Bool(false)
}
}
impl<'de> Deserialize<'de> for ExtensibleType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
let value = serde_json::Value::deserialize(deserializer)?;
match value {
serde_json::Value::String(s) => match s.as_str() {
"in" | "out" => Ok(ExtensibleType::Direction(s)),
_ => Err(D::Error::custom(format!("Invalid direction value: {}", s))),
},
serde_json::Value::Bool(b) => Ok(ExtensibleType::Bool(b)),
_ => Err(D::Error::custom(
"Expected string or boolean for extensible field",
)),
}
}
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(untagged)]
pub enum LengthValue {
String(String),
Number(u32),
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(untagged)]
pub enum ReturnType {
Simple(String),
Complex {
#[serde(rename = "type")]
return_type: String,
#[serde(default)]
optional: bool,
},
}
impl LengthValue {
pub fn is_string(&self) -> bool {
matches!(self, LengthValue::String(_))
}
pub fn is_number(&self) -> bool {
matches!(self, LengthValue::Number(_))
}
pub fn as_string(&self) -> Option<&str> {
match self {
LengthValue::String(s) => Some(s),
LengthValue::Number(_) => None,
}
}
pub fn as_number(&self) -> Option<u32> {
match self {
LengthValue::String(_) => None,
LengthValue::Number(n) => Some(*n),
}
}
}
impl ExtensibleType {
pub fn is_extensible(&self) -> bool {
match self {
ExtensibleType::Direction(_) => true,
ExtensibleType::Bool(b) => *b,
}
}
pub fn direction(&self) -> Option<&str> {
match self {
ExtensibleType::Direction(dir) => Some(dir),
ExtensibleType::Bool(_) => None,
}
}
pub fn is_input(&self) -> bool {
matches!(self, ExtensibleType::Direction(dir) if dir == "in")
}
pub fn is_output(&self) -> bool {
matches!(self, ExtensibleType::Direction(dir) if dir == "out")
}
pub fn is_boolean(&self) -> bool {
matches!(self, ExtensibleType::Bool(_))
}
}
impl ReturnType {
pub fn get_type(&self) -> &str {
match self {
ReturnType::Simple(s) => s,
ReturnType::Complex { return_type, .. } => return_type,
}
}
pub fn is_optional(&self) -> bool {
match self {
ReturnType::Simple(_) => false,
ReturnType::Complex { optional, .. } => *optional,
}
}
}
impl FunctionPointerDef {
pub fn returns(&self) -> Option<&ReturnType> {
self.returns.as_ref()
}
pub fn args(&self) -> &[RecordMember] {
&self.args
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct ObjectDef {
#[serde(default)]
pub tags: Vec<String>,
pub methods: Vec<MethodDef>,
#[serde(rename = "no autolock")]
pub no_autolock: Option<bool>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ConstantDef {
#[serde(default)]
pub tags: Vec<String>,
#[serde(rename = "type")]
pub const_type: String,
pub value: serde_json::Value,
pub cpp_value: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct FunctionDef {
#[serde(default)]
pub tags: Vec<String>,
pub returns: Option<ReturnType>,
pub args: Vec<RecordMember>,
#[serde(rename = "_comment")]
pub comment: Option<String>,
}
impl FunctionDef {
pub fn returns(&self) -> Option<&ReturnType> {
self.returns.as_ref()
}
pub fn args(&self) -> &[RecordMember] {
&self.args
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct CallbackDef {
#[serde(default)]
pub tags: Vec<String>,
returns: Option<ReturnType>,
args: Vec<RecordMember>,
}
impl CallbackDef {
pub fn returns(&self) -> Option<&ReturnType> {
self.returns.as_ref()
}
pub fn args(&self) -> &[RecordMember] {
&self.args
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct CallbackFunctionDef {
#[serde(default)]
pub tags: Vec<String>,
pub args: Vec<RecordMember>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CallbackInfoDef {
#[serde(default)]
pub tags: Vec<String>,
pub members: Vec<RecordMember>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct MethodDef {
pub name: String,
#[serde(default)]
pub tags: Vec<String>,
pub returns: Option<ReturnType>,
#[serde(default)]
pub args: Vec<RecordMember>,
#[serde(rename = "no autolock")]
pub no_autolock: Option<bool>,
pub extensible: Option<ExtensibleType>,
}
impl MethodDef {
pub fn returns(&self) -> Option<&ReturnType> {
self.returns.as_ref()
}
pub fn args(&self) -> &[RecordMember] {
&self.args
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct RecordMember {
pub name: String,
#[serde(rename = "type")]
pub member_type: String,
#[serde(default)]
pub annotation: Annotation,
pub length: Option<LengthValue>,
#[serde(default)]
pub optional: bool,
pub default: Option<serde_json::Value>,
#[serde(rename = "wire_is_data_only", default)]
pub wire_is_data_only: bool,
#[serde(rename = "skip_serialize", default)]
pub skip_serialize: bool,
#[serde(rename = "no_default")]
pub no_default: Option<bool>,
#[serde(rename = "array_element_optional")]
pub array_element_optional: Option<bool>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Annotation {
MutPtr,
ConstPtr,
ConstConstPtr,
#[default]
Value,
}
impl Annotation {
pub fn is_mut_ptr(&self) -> bool {
matches!(self, Annotation::MutPtr)
}
pub fn is_const_ptr(&self) -> bool {
matches!(self, Annotation::ConstPtr | Annotation::ConstConstPtr)
}
pub fn is_const_const_ptr(&self) -> bool {
matches!(self, Annotation::ConstConstPtr)
}
pub fn is_value(&self) -> bool {
matches!(self, Annotation::Value)
}
}
impl<'de> Deserialize<'de> for Annotation {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let annotation = String::deserialize(deserializer)?;
match annotation.as_str() {
"*" => Ok(Annotation::MutPtr),
"const*" => Ok(Annotation::ConstPtr),
"const*const*" => Ok(Annotation::ConstConstPtr),
_ => Ok(Annotation::Value),
}
}
}
pub struct DawnJsonParser;
impl DawnJsonParser {
pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<DawnApi, Box<dyn std::error::Error>> {
let content = fs::read_to_string(path)?;
Self::parse_string(&content)
}
pub fn parse_string(content: &str) -> Result<DawnApi, Box<dyn std::error::Error>> {
let api: DawnApi = serde_json::from_str(content)?;
Ok(api)
}
}
pub struct Name {
pub canonical_name: String,
}
impl Name {
pub fn new(canonical_name: &str) -> Self {
Self {
canonical_name: canonical_name.to_string(),
}
}
pub fn camel_case(&self) -> String {
self.canonical_name.to_upper_camel_case()
}
pub fn snake_case(&self) -> String {
let name = self.canonical_name.to_snake_case();
if name == "type" {
"r#type".into()
} else {
name
}
}
pub fn shouty_snake_case(&self) -> String {
self.canonical_name.to_shouty_snake_case()
}
pub fn kebab_case(&self) -> String {
self.canonical_name.to_kebab_case()
}
}
fn default_true() -> bool {
true
}