blob: 3d407c52579a265c5193678360b2f1aadf82d6f7 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use heck::SnakeCase;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Map;
use serde_json::{json, Value};
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::path::PathBuf;
/// Converts an UpperCamelCased name like "FooBar" into a lower_snake_cased one
/// like "foo_bar." This is used to normalize attribute names such that names
/// written in either case are synonyms.
pub fn to_lower_snake_case(str: &str) -> String {
str.to_snake_case().to_lowercase()
}
// This function takes a table or union payload, and "wraps" it into a struct with a single member
// named "payload."
pub fn wrap_non_struct_payload(payloads: &mut HashMap<String, Value>, name: String) {
payloads.insert(
name.clone(),
json!({
"members": json!([
json!({
"name": "payload",
"type": json!({
"kind": "identifier",
"identifier": &name,
}),
}),
]),
}),
);
}
#[derive(Serialize, Deserialize)]
pub struct TableOfContentsItem {
pub name: String,
pub link: String,
pub description: String,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct FidlJson {
pub name: String,
#[serde(default)]
pub maybe_attributes: Vec<Value>,
pub library_dependencies: Vec<Value>,
pub bits_declarations: Vec<Value>,
pub const_declarations: Vec<Value>,
pub enum_declarations: Vec<Value>,
pub protocol_declarations: Vec<Value>,
pub table_declarations: Vec<Value>,
pub type_alias_declarations: Vec<Value>,
pub struct_declarations: Vec<Value>,
pub external_struct_declarations: Vec<Value>,
pub union_declarations: Vec<Value>,
pub declaration_order: Vec<String>,
pub declarations: Map<String, Value>,
}
impl FidlJson {
pub fn from_path(path: &PathBuf) -> Result<FidlJson, io::Error> {
let mut fidl_file = match File::open(path) {
Err(why) => {
eprintln!(
"Couldn't open file {path}: {reason}",
path = path.display(),
reason = why,
);
return Err(why);
}
Ok(file) => file,
};
let mut s = String::new();
fidl_file.read_to_string(&mut s)?;
Ok(serde_json::from_str(&s)?)
}
pub fn resolve_method_payloads(&mut self) {
// Take note off all types used as transactional message bodies.
let mut payload_types = HashSet::<String>::new();
for protocol in self.protocol_declarations.iter_mut() {
let methods = protocol["methods"].as_array_mut().unwrap();
for method in methods.iter_mut() {
let m = method.as_object_mut().unwrap();
let req = m.get("maybe_request_payload");
if req.is_some() {
let typ = req.unwrap();
payload_types.insert(typ["identifier"].as_str().unwrap().to_string());
}
let resp = m.get("maybe_response_payload");
if resp.is_some() {
let typ = resp.unwrap();
payload_types.insert(typ["identifier"].as_str().unwrap().to_string());
}
}
}
// Remove decls used only as payloads from struct_declarations and
// external_struct_declarations, since we do not need standalone documentation for those
// types.
let mut payloads = HashMap::<String, Value>::new();
let struct_lists =
vec![&mut self.struct_declarations, &mut self.external_struct_declarations];
for struct_list in struct_lists {
struct_list.retain(|strukt| {
let strukt_name = strukt["name"].as_str().unwrap().to_string();
if payload_types.contains(&strukt_name) {
// A naming context of len == 1 means the declaration is anonymous, and does
// not need documentation for its standalone type. It can thus be removed from
// the declaration list altogether, and its definition can be moved into the
// payload store instead.
if strukt["naming_context"].as_array().unwrap().len() > 1 {
payloads.insert(strukt_name, strukt.to_owned());
return false;
}
// This declaration is used both as a top-level type which needs its own
// documentation, as well as a payload, so it needs to be cloned into the
// payload store.
payloads.insert(strukt_name, strukt.clone());
}
true
});
}
// Treat table and union payloads as "structs of one" with the lone member "payload"
// pointing to the user-specified table/union.
for table in &self.table_declarations {
let table_name = table["name"].as_str().unwrap().to_string();
if !payload_types.contains(&table_name) {
continue;
}
wrap_non_struct_payload(&mut payloads, table_name);
}
for union in &self.union_declarations {
let union_name = union["name"].as_str().unwrap().to_string();
if !payload_types.contains(&union_name) {
continue;
}
wrap_non_struct_payload(&mut payloads, union_name);
}
// Do the same for imported table/union payloads.
for ext_lib in &self.library_dependencies {
if let Some(ext_decls) = ext_lib["declarations"].as_object() {
for (name, ext_decl) in ext_decls {
// Only need to look at union/tables contained in the payload list.
if !payload_types.contains(name)
|| (ext_decl["kind"] != "union" && ext_decl["kind"] != "table")
{
continue;
}
wrap_non_struct_payload(&mut payloads, name.to_string());
}
}
}
// Insert copies of extracted payloads into the method definitions that utilize them.
for protocol in self.protocol_declarations.iter_mut() {
let methods = protocol["methods"].as_array_mut().unwrap();
for method in methods.iter_mut() {
let m = method.as_object_mut().unwrap();
let req = m.get("maybe_request_payload");
if req.is_some() {
let typ = req.unwrap();
let payload = payloads.get(typ["identifier"].as_str().unwrap()).unwrap();
m.insert("maybe_request".to_string(), payload.to_owned().clone());
}
let resp = match m.get("maybe_response_payload") {
Some(resp) => Some(resp),
None => m.get("maybe_response_success_type"),
};
if resp.is_some() {
let typ = resp.unwrap();
let payload = payloads.get(typ["identifier"].as_str().unwrap()).unwrap();
m.insert("maybe_response".to_string(), payload.to_owned().clone());
}
}
}
}
pub fn sort_declarations(&mut self) {
let cmp_name = |a: &Value, b: &Value| a["name"].as_str().cmp(&b["name"].as_str());
let FidlJson {
name: _,
maybe_attributes: _,
library_dependencies: _,
bits_declarations,
const_declarations,
enum_declarations,
protocol_declarations,
table_declarations,
type_alias_declarations,
struct_declarations,
external_struct_declarations: _,
union_declarations,
declaration_order: _,
declarations: _,
} = self;
bits_declarations.sort_unstable_by(cmp_name);
const_declarations.sort_unstable_by(cmp_name);
enum_declarations.sort_unstable_by(cmp_name);
protocol_declarations.sort_unstable_by(cmp_name);
for protocol in protocol_declarations.iter_mut() {
protocol["methods"].as_array_mut().unwrap().sort_unstable_by(cmp_name);
}
table_declarations.sort_unstable_by(cmp_name);
type_alias_declarations.sort_unstable_by(cmp_name);
struct_declarations.sort_unstable_by(cmp_name);
union_declarations.sort_unstable_by(cmp_name);
}
}
pub struct FidlJsonPackageData {
pub declarations: Vec<String>,
pub fidl_json_map: HashMap<String, FidlJson>,
}
impl FidlJsonPackageData {
pub fn new() -> Self {
FidlJsonPackageData { declarations: Vec::new(), fidl_json_map: HashMap::new() }
}
pub fn insert(&mut self, mut fidl_json: FidlJson) {
self.declarations.append(&mut fidl_json.declaration_order);
let package_name = fidl_json.name.clone();
self.fidl_json_map
.entry(package_name)
.and_modify(|package_fidl_json| {
// Merge
package_fidl_json.maybe_attributes.append(&mut fidl_json.maybe_attributes);
package_fidl_json.bits_declarations.append(&mut fidl_json.bits_declarations);
package_fidl_json.const_declarations.append(&mut fidl_json.const_declarations);
package_fidl_json.enum_declarations.append(&mut fidl_json.enum_declarations);
package_fidl_json
.protocol_declarations
.append(&mut fidl_json.protocol_declarations);
package_fidl_json.struct_declarations.append(&mut fidl_json.struct_declarations);
package_fidl_json.table_declarations.append(&mut fidl_json.table_declarations);
package_fidl_json
.type_alias_declarations
.append(&mut fidl_json.type_alias_declarations);
package_fidl_json.union_declarations.append(&mut fidl_json.union_declarations);
package_fidl_json.declaration_order.append(&mut fidl_json.declaration_order);
})
.or_insert(fidl_json);
}
}
#[cfg(test)]
mod test {
use super::*;
use serde_json::json;
#[test]
fn to_lower_snake_case_test() {
assert_eq!(to_lower_snake_case("FooBarBaz"), "foo_bar_baz");
assert_eq!(to_lower_snake_case("Foo bar Baz"), "foo_bar_baz");
assert_eq!(to_lower_snake_case("foo barBaz QUX"), "foo_bar_baz_qux");
}
#[test]
fn sort_declarations_test() {
let mut f = FidlJson {
name: "fuchsia.test".to_string(),
maybe_attributes: vec![json!({"name": "doc", "value": "Fuchsia Test API"})],
library_dependencies: Vec::new(),
bits_declarations: serde_json::from_str("[{\"name\": \"ABit\"},{\"name\": \"LastBit\"},{\"name\": \"AnotherBit\"}]").unwrap(),
const_declarations: serde_json::from_str("[{\"name\": \"fuchsia.test/Const\"},{\"name\": \"fuchsia.test/AConst\"}]").unwrap(),
enum_declarations: serde_json::from_str("[{\"name\": \"fuchsia.test/Enum\"},{\"name\": \"fuchsia.test/Third\"},{\"name\": \"fuchsia.test/Second\"}]").unwrap(),
protocol_declarations: serde_json::from_str("[{\"name\": \"Protocol1\",\"methods\": [{\"name\": \"Method 2\"},{\"name\": \"Method 1\"}]},{\"name\": \"AnotherProtocol\",\"methods\": [{\"name\": \"AMethod\"},{\"name\": \"BMethod\"}]}]").unwrap(),
table_declarations: serde_json::from_str("[{\"name\": \"4\"},{\"name\": \"2A\"},{\"name\": \"11\"},{\"name\": \"zzz\"}]").unwrap(),
type_alias_declarations: serde_json::from_str("[{\"name\": \"fuchsia.test/type\"},{\"name\": \"fuchsia.test/alias\"}]").unwrap(),
struct_declarations: serde_json::from_str("[{\"name\": \"fuchsia.test/SomeLongAnonymousPrefix1\"},{\"name\": \"fuchsia.test/Struct\"},{\"name\": \"fuchsia.test/SomeLongAnonymousPrefix0\"}]").unwrap(),
external_struct_declarations: serde_json::from_str("[{\"name\": \"fuchsia.external/SomeLongAnonymousPrefix2\"}]").unwrap(),
union_declarations: serde_json::from_str("[{\"name\": \"union1\"},{\"name\": \"Union1\"},{\"name\": \"UnIoN1\"}]").unwrap(),
declaration_order: Vec::new(),
declarations: Map::new(),
};
f.sort_declarations();
assert_eq!(&f.bits_declarations[0]["name"], "ABit");
assert_eq!(&f.bits_declarations[1]["name"], "AnotherBit");
assert_eq!(&f.bits_declarations[2]["name"], "LastBit");
assert_eq!(&f.const_declarations[0]["name"], "fuchsia.test/AConst");
assert_eq!(&f.const_declarations[1]["name"], "fuchsia.test/Const");
assert_eq!(&f.enum_declarations[0]["name"], "fuchsia.test/Enum");
assert_eq!(&f.enum_declarations[1]["name"], "fuchsia.test/Second");
assert_eq!(&f.enum_declarations[2]["name"], "fuchsia.test/Third");
assert_eq!(&f.table_declarations[0]["name"], "11");
assert_eq!(&f.table_declarations[1]["name"], "2A");
assert_eq!(&f.table_declarations[2]["name"], "4");
assert_eq!(&f.table_declarations[3]["name"], "zzz");
assert_eq!(&f.type_alias_declarations[0]["name"], "fuchsia.test/alias");
assert_eq!(&f.type_alias_declarations[1]["name"], "fuchsia.test/type");
assert_eq!(&f.struct_declarations[0]["name"], "fuchsia.test/SomeLongAnonymousPrefix0");
assert_eq!(&f.struct_declarations[1]["name"], "fuchsia.test/SomeLongAnonymousPrefix1");
assert_eq!(&f.struct_declarations[2]["name"], "fuchsia.test/Struct");
assert_eq!(
&f.external_struct_declarations[0]["name"],
"fuchsia.external/SomeLongAnonymousPrefix2"
);
assert_eq!(&f.union_declarations[0]["name"], "UnIoN1");
assert_eq!(&f.union_declarations[1]["name"], "Union1");
assert_eq!(&f.union_declarations[2]["name"], "union1");
}
}