diff --git a/compiler-rs/Cargo.lock b/compiler-rs/Cargo.lock index 43b2451532..403e2c3ce0 100644 --- a/compiler-rs/Cargo.lock +++ b/compiler-rs/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "aho-corasick" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" -dependencies = [ - "memchr", -] - [[package]] name = "anstream" version = "0.5.0" @@ -138,6 +129,7 @@ version = "0.1.0" dependencies = [ "anyhow", "arcstr", + "derive_more", "indexmap", "once_cell", "serde", @@ -161,7 +153,6 @@ dependencies = [ "maplit", "openapiv3", "quantiles", - "regex", "serde", "serde_ignored", "serde_json", @@ -209,6 +200,26 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "derive_more" +version = "1.0.0-beta.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7abbfc297053be59290e3152f8cbcd52c8642e0728b69ee187d991d4c1af08d" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0-beta.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bba3e9872d7c58ce7ef0fcf1844fcc3e23ef2a58377b50df35dd98e42a5726e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "either_n" version = "0.2.0" @@ -274,12 +285,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -361,35 +366,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "regex" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89089e897c013b3deb627116ae56a6955a72b8bed395c9526af31c9fe528b484" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa250384981ea14565685dea16a9ccc4d1c541a13f82b9c168572264d1df8c56" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" - [[package]] name = "ryu" version = "1.0.13" diff --git a/compiler-rs/clients_schema/Cargo.toml b/compiler-rs/clients_schema/Cargo.toml index 798b6f3806..d8253445bd 100644 --- a/compiler-rs/clients_schema/Cargo.toml +++ b/compiler-rs/clients_schema/Cargo.toml @@ -5,7 +5,8 @@ edition = "2021" publish = false [dependencies] -serde = {version = "1.0", features=["derive", 'rc']} +derive_more = { version = "1.0.0-beta.6", features = ["from"] } +serde = { version = "1.0", features=["derive", 'rc']} serde_json = "1.0" typed-builder = "0.11" once_cell = "1.16" diff --git a/compiler-rs/clients_schema/src/builtins.rs b/compiler-rs/clients_schema/src/builtins.rs index ed574896b1..85b288a0e2 100644 --- a/compiler-rs/clients_schema/src/builtins.rs +++ b/compiler-rs/clients_schema/src/builtins.rs @@ -15,21 +15,22 @@ // specific language governing permissions and limitations // under the License. -use crate::TypeName; use once_cell::sync::Lazy; -pub static STRING: Lazy = Lazy::new(|| TypeName::new("_builtins", "string")); +use crate::TypeName; + +pub static STRING: Lazy = Lazy::new(|| TypeName::new("_builtins", "string")); pub static BOOLEAN: Lazy = Lazy::new(|| TypeName::new("_builtins", "boolean")); -pub static OBJECT: Lazy = Lazy::new(|| TypeName::new("_builtins", "object")); -pub static BINARY: Lazy = Lazy::new(|| TypeName::new("_builtins", "binary")); -pub static VOID: Lazy = Lazy::new(|| TypeName::new("_builtins", "void")); -pub static NUMBER: Lazy = Lazy::new(|| TypeName::new("_builtins", "number")); -pub static BYTE: Lazy = Lazy::new(|| TypeName::new("_builtins", "byte")); +pub static OBJECT: Lazy = Lazy::new(|| TypeName::new("_builtins", "object")); +pub static BINARY: Lazy = Lazy::new(|| TypeName::new("_builtins", "binary")); +pub static VOID: Lazy = Lazy::new(|| TypeName::new("_builtins", "void")); +pub static NUMBER: Lazy = Lazy::new(|| TypeName::new("_builtins", "number")); +pub static BYTE: Lazy = Lazy::new(|| TypeName::new("_builtins", "byte")); pub static INTEGER: Lazy = Lazy::new(|| TypeName::new("_builtins", "integer")); -pub static LONG: Lazy = Lazy::new(|| TypeName::new("_builtins", "long")); -pub static FLOAT: Lazy = Lazy::new(|| TypeName::new("_builtins", "float")); -pub static DOUBLE: Lazy = Lazy::new(|| TypeName::new("_builtins", "double")); -pub static NULL: Lazy = Lazy::new(|| TypeName::new("_builtins", "null")); +pub static LONG: Lazy = Lazy::new(|| TypeName::new("_builtins", "long")); +pub static FLOAT: Lazy = Lazy::new(|| TypeName::new("_builtins", "float")); +pub static DOUBLE: Lazy = Lazy::new(|| TypeName::new("_builtins", "double")); +pub static NULL: Lazy = Lazy::new(|| TypeName::new("_builtins", "null")); pub static DICTIONARY: Lazy = Lazy::new(|| TypeName::new("_builtins", "Dictionary")); pub static USER_DEFINED: Lazy = Lazy::new(|| TypeName::new("_builtins", "UserDefined")); diff --git a/compiler-rs/clients_schema/src/lib.rs b/compiler-rs/clients_schema/src/lib.rs index a67c3d3ad2..f771442881 100644 --- a/compiler-rs/clients_schema/src/lib.rs +++ b/compiler-rs/clients_schema/src/lib.rs @@ -16,7 +16,9 @@ // under the License. use std::fmt::{Debug, Display, Formatter}; + use anyhow::anyhow; +use derive_more::From; use indexmap::IndexMap; // Re-export crates whose types we expose publicly pub use once_cell; @@ -26,9 +28,9 @@ pub mod builtins; pub mod transform; use once_cell::sync::Lazy; -use serde::{Deserialize, Serialize}; use serde::de::SeqAccess; use serde::ser::SerializeSeq; +use serde::{Deserialize, Serialize}; #[allow(clippy::trivially_copy_pass_by_ref)] // needs to match signature for use in serde attribute #[inline] @@ -44,7 +46,7 @@ const fn is_false(v: &bool) -> bool { // TODO: interning at deserialization time to reuse identical values (links from values to types) type FastString = arcstr::ArcStr; -pub trait Documented { +pub trait Documented { fn doc_url(&self) -> Option<&str>; fn doc_id(&self) -> Option<&str>; fn description(&self) -> Option<&str>; @@ -79,18 +81,16 @@ impl Display for TypeName { //----------------------------------------------------------------------------- -/// /// Type of a value. Used both for property types and nested type definitions. -/// -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)] -#[serde(tag = "kind", rename_all="snake_case")] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd, From)] +#[serde(tag = "kind", rename_all = "snake_case")] pub enum ValueOf { InstanceOf(InstanceOf), ArrayOf(ArrayOf), UnionOf(UnionOf), DictionaryOf(DictionaryOf), UserDefinedValue(UserDefinedValue), - LiteralValue(LiteralValue) + LiteralValue(LiteralValue), } impl ValueOf { @@ -99,30 +99,6 @@ impl ValueOf { } } -impl From for ValueOf { - fn from(value: InstanceOf) -> Self { - ValueOf::InstanceOf(value) - } -} - -impl From for ValueOf { - fn from(value: ArrayOf) -> Self { - ValueOf::ArrayOf(value) - } -} - -impl From for ValueOf { - fn from(value: UnionOf) -> Self { - ValueOf::UnionOf(value) - } -} - -impl From for ValueOf { - fn from(value: DictionaryOf) -> Self { - ValueOf::DictionaryOf(value) - } -} - impl From for ValueOf { fn from(name: TypeName) -> Self { ValueOf::InstanceOf(InstanceOf::new(name)) @@ -143,17 +119,15 @@ impl From<&Lazy> for ValueOf { //----------------------------------------------------------------------------- -/// /// A single value -/// #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)] pub struct InstanceOf { - #[serde(rename="type")] + #[serde(rename = "type")] pub typ: TypeName, /// generic parameters: either concrete types or open parameters from the enclosing type #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub generics: Vec + pub generics: Vec, } impl InstanceOf { @@ -165,28 +139,22 @@ impl InstanceOf { } } -/// /// An array -/// #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)] pub struct ArrayOf { - pub value: Box + pub value: Box, } -/// /// One of several possible types which don't necessarily have a common superclass -/// #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)] pub struct UnionOf { - pub items: Vec + pub items: Vec, } -/// /// A dictionary (or map). The key is a string or a number (or a union thereof), possibly through an alias. /// /// If `singleKey` is true, then this dictionary can only have a single key. This is a common pattern in ES APIs, /// used to associate a value to a field name or some other identifier. -/// #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)] #[serde(rename_all = "camelCase")] pub struct DictionaryOf { @@ -195,7 +163,6 @@ pub struct DictionaryOf { pub single_key: bool, } -/// /// A user defined value. To be used when bubbling a generic parameter up to the top-level class is /// inconvenient or impossible (e.g. for lists of user-defined values of possibly different types). /// @@ -204,23 +171,19 @@ pub struct DictionaryOf { /// /// Think twice before using this as it defeats the purpose of a strongly typed API, and deserialization /// will also require to buffer raw JSON data which may have performance implications. -/// #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)] #[serde(rename_all = "camelCase")] -pub struct UserDefinedValue { -} +pub struct UserDefinedValue {} -/// /// A literal value. This is used for tagged unions, where each type member of a union has a 'type' /// attribute that defines its kind. This metamodel heavily uses this approach with its 'kind' attributes. /// /// It may later be used to set a property to a constant value, which is why it accepts not only strings but also /// other primitive types. -/// #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)] #[serde(rename_all = "camelCase")] pub struct LiteralValue { - pub value: LiteralValueValue + pub value: LiteralValueValue, } impl Display for LiteralValue { @@ -234,7 +197,7 @@ impl Display for LiteralValue { pub enum LiteralValueValue { String(String), Number(f64), - Boolean(bool) + Boolean(bool), } impl Display for LiteralValueValue { @@ -324,15 +287,13 @@ impl Flavor { } } -/// /// An interface or request interface property. -/// #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Property { pub name: String, - #[serde(rename="type")] + #[serde(rename = "type")] pub typ: ValueOf, pub required: bool, @@ -410,7 +371,7 @@ pub enum ServerDefault { //-------------------------------------------------------------------------------------------- #[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "snake_case", tag="kind")] +#[serde(rename_all = "snake_case", tag = "kind")] pub enum Variants { ExternalTag(ExternalTag), InternalTag(InternalTag), @@ -421,7 +382,7 @@ pub enum Variants { #[serde(rename_all = "camelCase")] pub struct ExternalTag { #[serde(default)] - pub non_exhaustive: bool + pub non_exhaustive: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -445,21 +406,19 @@ pub struct Container { pub non_exhaustive: bool, } -/// /// Inherits clause (aka extends or implements) for an interface or request -/// #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Inherits { - #[serde(rename="type")] + #[serde(rename = "type")] pub typ: TypeName, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub generics: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "snake_case", tag="kind")] +#[derive(Debug, Clone, Serialize, Deserialize, From)] +#[serde(rename_all = "snake_case", tag = "kind")] #[allow(clippy::large_enum_variant)] pub enum TypeDefinition { Interface(Interface), @@ -469,42 +428,6 @@ pub enum TypeDefinition { TypeAlias(TypeAlias), } -impl TypeDefinition { - pub fn type_alias(name: TypeName, value: ValueOf) -> TypeDefinition { - TypeDefinition::TypeAlias(TypeAlias::new(name, value)) - } -} - -impl From for TypeDefinition { - fn from(value: Interface) -> Self { - TypeDefinition::Interface(value) - } -} - -impl From for TypeDefinition { - fn from(value: Request) -> Self { - TypeDefinition::Request(value) - } -} - -impl From for TypeDefinition { - fn from(value: Response) -> Self { - TypeDefinition::Response(value) - } -} - -impl From for TypeDefinition { - fn from(value: Enum) -> Self { - TypeDefinition::Enum(value) - } -} - -impl From for TypeDefinition { - fn from(value: TypeAlias) -> Self { - TypeDefinition::TypeAlias(value) - } -} - impl TypeDefinition { pub fn name(&self) -> &TypeName { &self.base().name @@ -533,9 +456,7 @@ impl TypeDefinition { } } -/// /// Common attributes for all type definitions -/// #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct BaseType { @@ -565,8 +486,7 @@ pub struct BaseType { /// Additional identifiers for use by code generators. Usage depends on the actual type: /// - on unions (modeled as alias(union_of)), these are identifiers for the union members /// - for additional properties, this is the name of the dict that holds these properties - /// - for additional property, this is the name of the key and value fields that hold the - /// additional property + /// - for additional property, this is the name of the key and value fields that hold the additional property #[serde(default, skip_serializing_if = "Vec::is_empty")] pub codegen_names: Vec, @@ -586,7 +506,7 @@ impl BaseType { es_quirk: None, description: None, variant_name: None, - spec_location: None + spec_location: None, } } } @@ -613,7 +533,7 @@ trait WithBaseType { fn base(&self) -> &BaseType; } -impl Documented for T { +impl Documented for T { fn doc_url(&self) -> Option<&str> { self.base().doc_url() } @@ -631,9 +551,7 @@ impl Documented for T { } } -/// /// An interface type -/// #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Interface { @@ -668,7 +586,7 @@ pub struct Interface { // Identify containers #[serde(skip_serializing_if = "Option::is_none")] - pub variants: Option + pub variants: Option, } impl WithBaseType for Interface { @@ -677,9 +595,7 @@ impl WithBaseType for Interface { } } -/// /// A request type -/// #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] // Note: does not extend Interface as properties are split across path, query and body @@ -702,9 +618,9 @@ pub struct Request { /// Query string properties pub query: Vec, - /// Body type. Most often a list of properties (that can extend those of the inherited class, see above), except for a - /// few specific cases that use other types such as bulk (array) or create (generic parameter). Or NoBody for requests - /// that don't have a body. + /// Body type. Most often a list of properties (that can extend those of the inherited class, see above), except + /// for a few specific cases that use other types such as bulk (array) or create (generic parameter). Or NoBody + /// for requests that don't have a body. pub body: Body, #[serde(default, skip_serializing_if = "Vec::is_empty")] @@ -720,9 +636,7 @@ impl WithBaseType for Request { } } -/// /// A response type -/// #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Response { @@ -758,15 +672,15 @@ pub struct ResponseException { pub body: Body, - pub status_codes: Vec + pub status_codes: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "snake_case", tag="kind")] +#[serde(rename_all = "snake_case", tag = "kind")] pub enum Body { Value(ValueBody), Properties(PropertiesBody), - NoBody(NoBody) + NoBody(NoBody), } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -786,16 +700,13 @@ pub struct PropertiesBody { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct NoBody { -} +pub struct NoBody {} -/// /// An enumeration member. /// /// When enumeration members can become ambiguous when translated to an identifier, the `name` property will be a good /// identifier name, and `stringValue` will be the string value to use on the wire. /// See DateMathTimeUnit for an example of this, which have members for "m" (minute) and "M" (month). -/// #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct EnumMember { @@ -833,9 +744,7 @@ impl From<&str> for EnumMember { } } -/// /// An enumeration -/// #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Enum { @@ -847,19 +756,17 @@ pub struct Enum { #[serde(default)] pub is_open: bool, - pub members: Vec + pub members: Vec, } -/// /// An alias for an existing type. -/// #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TypeAlias { #[serde(flatten)] pub base: BaseType, - #[serde(rename="type")] + #[serde(rename = "type")] pub typ: ValueOf, /// generic parameters: either concrete types or open parameters from the enclosing type @@ -883,7 +790,7 @@ impl TypeAlias { } #[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "snake_case", tag="kind")] +#[serde(rename_all = "snake_case", tag = "kind")] pub enum TypeAliasVariants { ExternalTag(ExternalTag), InternalTag(InternalTag), @@ -924,7 +831,6 @@ pub struct Endpoint { /// The version when this endpoint reached its current stability level. /// Missing data means "forever", i.e. before any of the target client versions produced from this spec. - /// #[serde(skip_serializing_if = "Option::is_none")] pub since: Option, @@ -972,7 +878,7 @@ pub struct Privileges { pub index: Vec, #[serde(default)] - pub cluster: Vec + pub cluster: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -1004,7 +910,7 @@ pub struct License { #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct Model { - #[serde(rename="_info", skip_serializing_if = "Option::is_none")] + #[serde(rename = "_info", skip_serializing_if = "Option::is_none")] pub info: Option, pub endpoints: Vec, pub types: Vec, @@ -1020,11 +926,10 @@ impl Model { /// An api model with types indexed by their name. This version is much more convenient to use /// when traversing the type graph and provides convenience accessors to the various kinds of /// types. -/// #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct IndexedModel { - #[serde(rename="_info", skip_serializing_if = "Option::is_none")] + #[serde(rename = "_info", skip_serializing_if = "Option::is_none")] pub info: Option, pub endpoints: Vec, @@ -1100,7 +1005,8 @@ impl IndexedModel { // IndexedModel serialization and deserialization //------------------------------------------------------------------------------------------------- -fn serialize_types (value: &IndexMap, serializer: S) -> Result where S: serde::Serializer { +fn serialize_types(value: &IndexMap, serializer: S) -> Result +where S: serde::Serializer { let mut seq = serializer.serialize_seq(Some(value.len()))?; for (_, v) in value { seq.serialize_element(v)?; @@ -1108,7 +1014,8 @@ fn serialize_types (value: &IndexMap, serializer: S seq.end() } -fn deserialize_types<'de, D>(deser: D) -> Result, D::Error> where D: serde::Deserializer<'de> { +fn deserialize_types<'de, D>(deser: D) -> Result, D::Error> +where D: serde::Deserializer<'de> { deser.deserialize_seq(IndexMapVisitor) } @@ -1121,7 +1028,8 @@ impl<'a> serde::de::Visitor<'a> for IndexMapVisitor { write!(formatter, "an array") } - fn visit_seq(self, mut seq: A) -> Result where A: SeqAccess<'a> { + fn visit_seq(self, mut seq: A) -> Result + where A: SeqAccess<'a> { let mut result = IndexMap::with_capacity(seq.size_hint().unwrap_or(0)); while let Some(item) = seq.next_element::()? { @@ -1172,10 +1080,12 @@ mod tests { let jd = &mut serde_json::Deserializer::from_str(&json); let result = serde_path_to_error::deserialize::<_, IndexedModel>(jd); - let result = result.map_err(|e| { - println!("Error at path {}", e.path()); - e.into_inner() - }).unwrap(); + let result = result + .map_err(|e| { + println!("Error at path {}", e.path()); + e.into_inner() + }) + .unwrap(); // Simple smoke test let search_req = TypeName { @@ -1187,7 +1097,7 @@ mod tests { match search_type { TypeDefinition::Request(r) => assert!(!r.path.is_empty()), - _ => panic!("Expecting a Request") + _ => panic!("Expecting a Request"), }; } } diff --git a/compiler-rs/clients_schema/src/transform/availability.rs b/compiler-rs/clients_schema/src/transform/availability.rs index 1e367662a9..1843b868cd 100644 --- a/compiler-rs/clients_schema/src/transform/availability.rs +++ b/compiler-rs/clients_schema/src/transform/availability.rs @@ -16,8 +16,9 @@ // under the License. use std::cell::RefCell; -use crate::{Availabilities, Body, IndexedModel, Inherits, Property, TypeDefinition, TypeName, ValueOf}; + use crate::transform::Worksheet; +use crate::{Availabilities, Body, IndexedModel, Inherits, Property, TypeDefinition, TypeName, ValueOf}; pub struct Availability { #[allow(clippy::type_complexity)] @@ -28,7 +29,10 @@ pub struct Availability { } impl Availability { - pub fn filter (mut model: IndexedModel, avail_filter: fn(&Option) -> bool) -> anyhow::Result { + pub fn filter( + mut model: IndexedModel, + avail_filter: fn(&Option) -> bool, + ) -> anyhow::Result { let filter = Availability { avail_filter: Box::new(avail_filter), worksheet: Worksheet::default().into(), @@ -76,16 +80,16 @@ impl Availability { itf.inherits.iter().for_each(|i| self.filter_inherits(i)); itf.behaviors.iter().for_each(|i| self.filter_behaviors(i)); self.filter_properties(&mut itf.properties); - }, + } TypeDefinition::Enum(ref mut enm) => { enm.members.retain(|member| self.is_available(&member.availability)); - }, + } TypeDefinition::TypeAlias(ref alias) => { self.filter_value_of(&alias.typ); alias.generics.iter().for_each(|g| self.filter_type(g)); - }, + } TypeDefinition::Request(ref mut request) => { request.inherits.iter().for_each(|i| self.filter_inherits(i)); @@ -93,12 +97,12 @@ impl Availability { self.filter_properties(&mut request.path); self.filter_properties(&mut request.query); self.filter_body(&mut request.body); - }, + } TypeDefinition::Response(ref mut response) => { response.behaviors.iter().for_each(|i| self.filter_behaviors(i)); self.filter_body(&mut response.body); - }, + } } } diff --git a/compiler-rs/clients_schema/src/transform/expand_generics.rs b/compiler-rs/clients_schema/src/transform/expand_generics.rs index 6d3c488f72..53aed06437 100644 --- a/compiler-rs/clients_schema/src/transform/expand_generics.rs +++ b/compiler-rs/clients_schema/src/transform/expand_generics.rs @@ -16,8 +16,10 @@ // under the License. use std::collections::HashMap; + use anyhow::bail; use indexmap::IndexMap; + use crate::*; #[derive(Default)] @@ -34,14 +36,10 @@ type GenericArgs = Vec; type GenericMapping = HashMap; /// Expand all generics by creating new concrete types for every instanciation of a generic type. -/// +/// /// The resulting model has no generics anymore. Top-level generic parameters (e.g. SearchRequest's TDocument) are /// replaced by user_defined_data. -/// -pub fn expand_generics( - model: IndexedModel -) -> anyhow::Result { - +pub fn expand_generics(model: IndexedModel) -> anyhow::Result { let mut model = model; let mut ctx = Ctx::default(); @@ -63,7 +61,7 @@ pub fn expand_generics( fn expand_root_type(t: &TypeName, model: &IndexedModel, ctx: &mut Ctx) -> anyhow::Result<()> { const NO_GENERICS: &Vec = &Vec::new(); - const USER_DEFINED: ValueOf = ValueOf::UserDefinedValue(UserDefinedValue{}); + const USER_DEFINED: ValueOf = ValueOf::UserDefinedValue(UserDefinedValue {}); use TypeDefinition::*; let generics = match model.get_type(t)? { @@ -81,13 +79,16 @@ pub fn expand_generics( Ok(()) } - /// /// Expand a type definition, given concrete values for its generic parameters. /// The new type definition is stored in the context. /// /// Returns the name to use for this (type, args) combination - /// - fn expand_type(name: &TypeName, args: GenericArgs, model: &IndexedModel, ctx: &mut Ctx) -> anyhow::Result { + fn expand_type( + name: &TypeName, + args: GenericArgs, + model: &IndexedModel, + ctx: &mut Ctx, + ) -> anyhow::Result { if name.is_builtin() { return Ok(name.clone()); } @@ -105,7 +106,6 @@ pub fn expand_generics( TypeDefinition::Response(resp) => expand_response(resp, args, model, ctx)?, TypeDefinition::TypeAlias(ref alias) => expand_type_alias(alias, args, model, ctx)?, TypeDefinition::Enum(_) => def.clone(), - }; new_def.base_mut().name = name.clone(); ctx.new_types.insert(name.clone(), new_def); @@ -114,7 +114,12 @@ pub fn expand_generics( Ok(name) } - fn expand_interface(itf: &Interface, args: GenericArgs, model: &IndexedModel, ctx: &mut Ctx) -> anyhow::Result { + fn expand_interface( + itf: &Interface, + args: GenericArgs, + model: &IndexedModel, + ctx: &mut Ctx, + ) -> anyhow::Result { // Clone and modify in place let mut itf = itf.clone(); @@ -138,7 +143,12 @@ pub fn expand_generics( Ok(itf.into()) } - fn expand_request(req: &Request, args: GenericArgs, model: &IndexedModel, ctx: &mut Ctx) -> anyhow::Result { + fn expand_request( + req: &Request, + args: GenericArgs, + model: &IndexedModel, + ctx: &mut Ctx, + ) -> anyhow::Result { // Clone and modify in place let mut req = req.clone(); @@ -157,7 +167,12 @@ pub fn expand_generics( Ok(req.into()) } - fn expand_response(resp: &Response, args: GenericArgs, model: &IndexedModel, ctx: &mut Ctx) -> anyhow::Result { + fn expand_response( + resp: &Response, + args: GenericArgs, + model: &IndexedModel, + ctx: &mut Ctx, + ) -> anyhow::Result { // Clone and modify in place let mut resp = resp.clone(); @@ -172,7 +187,12 @@ pub fn expand_generics( Ok(resp.into()) } - fn expand_type_alias(t: &TypeAlias, args: GenericArgs, model: &IndexedModel, ctx: &mut Ctx) -> anyhow::Result { + fn expand_type_alias( + t: &TypeAlias, + args: GenericArgs, + model: &IndexedModel, + ctx: &mut Ctx, + ) -> anyhow::Result { let mapping = param_mapping(&t.generics, args); let value = expand_valueof(&t.typ, &mapping, model, ctx)?; @@ -188,11 +208,22 @@ pub fn expand_generics( // Expanding type parts in place //--------------------------------------------------------------------------------------------- - fn expand_inherits(i: Inherits, mappings: &GenericMapping, model: &IndexedModel, ctx: &mut Ctx) -> anyhow::Result { - let expanded = expand_valueof(&InstanceOf { - typ: i.typ, - generics: i.generics - }.into(), mappings, model, ctx)?; + fn expand_inherits( + i: Inherits, + mappings: &GenericMapping, + model: &IndexedModel, + ctx: &mut Ctx, + ) -> anyhow::Result { + let expanded = expand_valueof( + &InstanceOf { + typ: i.typ, + generics: i.generics, + } + .into(), + mappings, + model, + ctx, + )?; if let ValueOf::InstanceOf(inst) = expanded { Ok(Inherits { @@ -204,10 +235,13 @@ pub fn expand_generics( } } - /// /// Expand behaviors in place - /// - fn expand_behaviors(behaviors: &mut Vec, mappings: &GenericMapping, model: &IndexedModel, ctx: &mut Ctx) -> anyhow::Result<()> { + fn expand_behaviors( + behaviors: &mut Vec, + mappings: &GenericMapping, + model: &IndexedModel, + ctx: &mut Ctx, + ) -> anyhow::Result<()> { // We keep the generic parameters of behaviors, but expand their value for behavior in behaviors { for arg in &mut behavior.generics { @@ -217,28 +251,34 @@ pub fn expand_generics( Ok(()) } - /// /// Expand properties in place - /// - fn expand_properties(props: &mut Vec, mappings: &GenericMapping, model: &IndexedModel, ctx: &mut Ctx) -> anyhow::Result<()> { + fn expand_properties( + props: &mut Vec, + mappings: &GenericMapping, + model: &IndexedModel, + ctx: &mut Ctx, + ) -> anyhow::Result<()> { for prop in props { prop.typ = expand_valueof(&prop.typ, mappings, model, ctx)?; } Ok(()) } - /// /// Expand body in place - /// - fn expand_body(body: &mut Body, mappings: &GenericMapping, model: &IndexedModel, ctx: &mut Ctx) -> anyhow::Result<()> { + fn expand_body( + body: &mut Body, + mappings: &GenericMapping, + model: &IndexedModel, + ctx: &mut Ctx, + ) -> anyhow::Result<()> { match body { Body::Value(ref mut value) => { value.value = expand_valueof(&value.value, mappings, model, ctx)?; - }, + } Body::Properties(ref mut prop_body) => { expand_properties(&mut prop_body.properties, mappings, model, ctx)?; - }, - Body::NoBody(_) => {}, + } + Body::NoBody(_) => {} } Ok(()) @@ -248,14 +288,17 @@ pub fn expand_generics( // Expanding values //--------------------------------------------------------------------------------------------- - fn expand_valueof(value: &ValueOf, mappings: &GenericMapping, model: &IndexedModel, ctx: &mut Ctx) -> anyhow::Result { + fn expand_valueof( + value: &ValueOf, + mappings: &GenericMapping, + model: &IndexedModel, + ctx: &mut Ctx, + ) -> anyhow::Result { match value { ValueOf::ArrayOf(ref arr) => { let value = expand_valueof(&arr.value, mappings, model, ctx)?; - Ok(ArrayOf{ - value: Box::new(value) - }.into()) - }, + Ok(ArrayOf { value: Box::new(value) }.into()) + } ValueOf::DictionaryOf(dict) => { let key = expand_valueof(&dict.key, mappings, model, ctx)?; @@ -264,9 +307,9 @@ pub fn expand_generics( single_key: dict.single_key, key: Box::new(key), value: Box::new(value), - }.into()) - - }, + } + .into()) + } ValueOf::InstanceOf(inst) => { // If this is a generic parameter, return its mapping @@ -275,30 +318,31 @@ pub fn expand_generics( } // Expand generic parameters, if any - let args = inst.generics.iter() + let args = inst + .generics + .iter() .map(|arg| expand_valueof(arg, mappings, model, ctx)) .collect::, _>>()?; Ok(InstanceOf { typ: expand_type(&inst.typ, args, model, ctx)?, - generics: Vec::new() - }.into()) - }, + generics: Vec::new(), + } + .into()) + } ValueOf::UnionOf(u) => { - let items = u.items.iter() + let items = u + .items + .iter() .map(|item| expand_valueof(item, mappings, model, ctx)) .collect::, _>>()?; Ok(UnionOf { items }.into()) - }, + } - ValueOf::UserDefinedValue(_) => { - Ok(value.clone()) - }, + ValueOf::UserDefinedValue(_) => Ok(value.clone()), - ValueOf::LiteralValue(_) => { - Ok(value.clone()) - }, + ValueOf::LiteralValue(_) => Ok(value.clone()), } } @@ -306,16 +350,12 @@ pub fn expand_generics( // Misc //--------------------------------------------------------------------------------------------- - /// /// Builds the mapping from generic parameter name to actual value - /// fn param_mapping(generics: &GenericParams, args: GenericArgs) -> GenericMapping { generics.iter().cloned().zip(args).collect() } - /// /// Creates an expanded type name if needed (i.e. when `generics` is not empty) - /// fn expanded_name(type_name: &TypeName, args: &GenericArgs) -> TypeName { if args.is_empty() { return type_name.clone(); @@ -336,18 +376,12 @@ pub fn expand_generics( } } - /// /// Appends the type representation of a value to a string - /// fn push_valueof_name(name: &mut String, value: &ValueOf) { use std::fmt::Write; match value { - ValueOf::LiteralValue(lit) => { - write!(name, "{}", lit).unwrap() - } - ValueOf::UserDefinedValue(_) => { - write!(name, "UserDefined").unwrap() - } + ValueOf::LiteralValue(lit) => write!(name, "{}", lit).unwrap(), + ValueOf::UserDefinedValue(_) => write!(name, "UserDefined").unwrap(), ValueOf::ArrayOf(a) => { name.push_str("Array"); push_valueof_name(name, a.value.as_ref()); @@ -374,11 +408,11 @@ pub fn expand_generics( #[cfg(test)] mod tests { use std::io::Write; + use super::*; #[test] pub fn compare_with_js_version() -> testresult::TestResult { - let canonical_json = { // Deserialize and reserialize to have a consistent JSON format let json = std::fs::read_to_string("../../output/schema/schema-no-generics.json")?; @@ -406,5 +440,3 @@ mod tests { Ok(()) } } - - diff --git a/compiler-rs/clients_schema/src/transform/mod.rs b/compiler-rs/clients_schema/src/transform/mod.rs index 61397641d5..1a5feeecc3 100644 --- a/compiler-rs/clients_schema/src/transform/mod.rs +++ b/compiler-rs/clients_schema/src/transform/mod.rs @@ -17,13 +17,14 @@ //! Utilities to transform API models and common transformations: //! * filtering according to availability -//! mod availability; mod expand_generics; use std::collections::HashSet; + use availability::Availability; + use crate::{Availabilities, IndexedModel, TypeName}; /// The working state of a type graph traversal algorithm. It keeps track of the types that @@ -31,10 +32,9 @@ use crate::{Availabilities, IndexedModel, TypeName}; /// /// Using this structure allows to flatten recursion and also handle recursive data structures /// by ensuring a type is never visited twice. -/// #[derive(Default)] pub struct Worksheet { - visited: HashSet::, + visited: HashSet, pending: Vec, } @@ -69,16 +69,13 @@ impl Iterator for Worksheet { /// Transform a model to only keep the endpoints and types that match a predicate on the `availability` /// properties. -/// pub fn filter_availability( model: IndexedModel, - avail_filter: fn(&Option) -> bool + avail_filter: fn(&Option) -> bool, ) -> anyhow::Result { Availability::filter(model, avail_filter) } -pub fn expand_generics( - model: IndexedModel -) -> anyhow::Result { +pub fn expand_generics(model: IndexedModel) -> anyhow::Result { expand_generics::expand_generics(model) } diff --git a/compiler-rs/clients_schema_to_openapi/Cargo.toml b/compiler-rs/clients_schema_to_openapi/Cargo.toml index 355e7e52b0..84e7c41b43 100644 --- a/compiler-rs/clients_schema_to_openapi/Cargo.toml +++ b/compiler-rs/clients_schema_to_openapi/Cargo.toml @@ -16,7 +16,6 @@ anyhow = "1.0" indexmap = "1.9" convert_case = "0.6" either_n = "0.2.0" -regex = "1.8" maplit = "1.0" tracing = "0.1.37" diff --git a/compiler-rs/clients_schema_to_openapi/src/components.rs b/compiler-rs/clients_schema_to_openapi/src/components.rs index d56da11212..4315a12534 100644 --- a/compiler-rs/clients_schema_to_openapi/src/components.rs +++ b/compiler-rs/clients_schema_to_openapi/src/components.rs @@ -15,8 +15,9 @@ // specific language governing permissions and limitations // under the License. -use openapiv3::{Components, Parameter, ReferenceOr, RequestBody, Response, Schema, StatusCode}; use clients_schema::TypeName; +use openapiv3::{Components, Parameter, ReferenceOr, RequestBody, Response, Schema, StatusCode}; + use crate::utils::SchemaName; pub struct TypesAndComponents<'a> { @@ -24,37 +25,43 @@ pub struct TypesAndComponents<'a> { pub components: &'a mut Components, } -impl <'a> TypesAndComponents<'a> { +impl<'a> TypesAndComponents<'a> { pub fn new(model: &'a clients_schema::IndexedModel, components: &'a mut Components) -> TypesAndComponents<'a> { - TypesAndComponents { - model, - components, - } + TypesAndComponents { model, components } } - pub fn add_request_body(&mut self, endpoint: &str, body: RequestBody,) -> ReferenceOr { - self.components.request_bodies.insert(endpoint.to_string(), ReferenceOr::Item(body)); + pub fn add_request_body(&mut self, endpoint: &str, body: RequestBody) -> ReferenceOr { + self.components + .request_bodies + .insert(endpoint.to_string(), ReferenceOr::Item(body)); ReferenceOr::Reference { - reference: format!("#/components/requestBodies/{}", endpoint) + reference: format!("#/components/requestBodies/{}", endpoint), } } pub fn add_parameter(&mut self, endpoint: &str, param: Parameter, duplicate: bool) -> ReferenceOr { - let suffix = if duplicate {"_"} else {""}; + let suffix = if duplicate { "_" } else { "" }; let result = ReferenceOr::Reference { - reference: format!("#/components/parameters/{}#{}{}", endpoint, ¶m.parameter_data_ref().name, suffix) + reference: format!( + "#/components/parameters/{}#{}{}", + endpoint, + ¶m.parameter_data_ref().name, + suffix + ), }; self.components.parameters.insert( format!("{}#{}{}", endpoint, ¶m.parameter_data_ref().name, suffix), - ReferenceOr::Item(param) + ReferenceOr::Item(param), ); result } pub fn add_response(&mut self, endpoint: &str, status: StatusCode, response: Response) -> ReferenceOr { - self.components.responses.insert(format!("{}#{}", endpoint, status), ReferenceOr::Item(response)); + self.components + .responses + .insert(format!("{}#{}", endpoint, status), ReferenceOr::Item(response)); ReferenceOr::Reference { - reference: format!("#/components/responses/{}#{}", endpoint, status) + reference: format!("#/components/responses/{}#{}", endpoint, status), } } diff --git a/compiler-rs/clients_schema_to_openapi/src/lib.rs b/compiler-rs/clients_schema_to_openapi/src/lib.rs index 0bda20c3da..808d62329b 100644 --- a/compiler-rs/clients_schema_to_openapi/src/lib.rs +++ b/compiler-rs/clients_schema_to_openapi/src/lib.rs @@ -15,27 +15,27 @@ // specific language governing permissions and limitations // under the License. +mod components; mod paths; mod schemas; -mod components; mod utils; use std::collections::HashSet; use std::io::{BufWriter, Write}; use std::path::Path; + +use clients_schema::{Availabilities, Endpoint, IndexedModel}; use openapiv3::{Components, OpenAPI}; use tracing::warn; -use clients_schema::{Availabilities, Endpoint, IndexedModel}; use crate::components::TypesAndComponents; pub fn convert_schema_file( path: impl AsRef, filter: Option) -> bool>, endpoint_filter: fn(e: &Endpoint) -> bool, - out: impl Write + out: impl Write, ) -> anyhow::Result<()> { - // Parsing from a string is faster than using a buffered reader when there is a need for look-ahead // See https://github.com/serde-rs/json/issues/160 let json = &std::fs::read_to_string(path)?; @@ -43,7 +43,7 @@ pub fn convert_schema_file( let mut unused = HashSet::new(); let mut model: IndexedModel = serde_ignored::deserialize(json_deser, |path| { - if let serde_ignored::Path::Map {parent: _, key} = path { + if let serde_ignored::Path::Map { parent: _, key } = path { unused.insert(key); } })?; @@ -63,10 +63,13 @@ pub fn convert_schema_file( Ok(()) } -pub fn convert_schema( - model: &IndexedModel, -) -> anyhow::Result { - +/// Convert an API model into an OpenAPI v3 schema. The input model must have all generics expanded, converstion +/// will fail otherwise. +/// +/// Note: there are ways to represent [generics in JSON Schema], but its unlikely that tooling will understood it. +/// +/// [generics in JSON Schema]: https://json-schema.org/blog/posts/dynamicref-and-generics +pub fn convert_schema(model: &IndexedModel) -> anyhow::Result { let mut openapi = OpenAPI { openapi: "3.0.3".into(), info: info(model), @@ -128,7 +131,7 @@ fn info(model: &IndexedModel) -> openapiv3::Info { name: info.license.name.clone(), url: Some(info.license.url.clone()), extensions: Default::default(), - }) + }), ) } else { ("".to_string(), None) diff --git a/compiler-rs/clients_schema_to_openapi/src/main.rs b/compiler-rs/clients_schema_to_openapi/src/main.rs index 1cd1264319..06234875f1 100644 --- a/compiler-rs/clients_schema_to_openapi/src/main.rs +++ b/compiler-rs/clients_schema_to_openapi/src/main.rs @@ -15,15 +15,15 @@ // specific language governing permissions and limitations // under the License. +use std::path::{Path, PathBuf}; + use clap::{Parser, ValueEnum}; +use clients_schema::{Availabilities, Visibility}; use tracing::Level; use tracing_subscriber::fmt::format::FmtSpan; use tracing_subscriber::FmtSubscriber; -use std::path::{Path, PathBuf}; -use clients_schema::{Availabilities, Visibility}; fn main() -> anyhow::Result<()> { - let cli = Cli::parse(); let subscriber = FmtSubscriber::builder() @@ -64,8 +64,7 @@ fn main() -> anyhow::Result<()> { // } impl Cli { - fn run(self) -> anyhow::Result<()>{ - + fn run(self) -> anyhow::Result<()> { let json = if self.schema == Path::new("-") { std::io::read_to_string(std::io::stdin())? } else { @@ -77,9 +76,7 @@ impl Cli { if let Some(flavor) = self.flavor { if flavor != SchemaFlavor::All { let filter: fn(&Option) -> bool = match flavor { - SchemaFlavor::All => |_| { - true - }, + SchemaFlavor::All => |_| true, SchemaFlavor::Stack => |a| { // Generate public and private items for Stack clients_schema::Flavor::Stack.available(a) @@ -128,7 +125,7 @@ pub struct Cli { /// Elasticsearch flavor to produce #[arg(short, long)] - flavor: Option + flavor: Option, } #[derive(Debug, Clone, PartialEq, ValueEnum)] @@ -152,8 +149,9 @@ mod tests { Cli { schema: "../../output/schema/schema-no-generics.json".into(), flavor: None, - output: Some("../../output/openapi/elasticsearch-openapi.json".into()) - }.run() + output: Some("../../output/openapi/elasticsearch-openapi.json".into()), + } + .run() } #[test] @@ -161,8 +159,9 @@ mod tests { Cli { schema: "../../output/schema/schema-no-generics.json".into(), flavor: Some(SchemaFlavor::Serverless), - output: Some("../../output/openapi/elasticsearch-serverless-openapi.json".into()) - }.run() + output: Some("../../output/openapi/elasticsearch-serverless-openapi.json".into()), + } + .run() } #[test] @@ -170,7 +169,8 @@ mod tests { Cli { schema: "../../output/schema/schema.json".into(), flavor: Some(SchemaFlavor::Serverless), - output: Some("../../output/openapi/elasticsearch-serverless-openapi2.json".into()) - }.run() + output: Some("../../output/openapi/elasticsearch-serverless-openapi2.json".into()), + } + .run() } } diff --git a/compiler-rs/clients_schema_to_openapi/src/paths.rs b/compiler-rs/clients_schema_to_openapi/src/paths.rs index 4b7f25ed68..0e8bb66b28 100644 --- a/compiler-rs/clients_schema_to_openapi/src/paths.rs +++ b/compiler-rs/clients_schema_to_openapi/src/paths.rs @@ -17,20 +17,24 @@ use std::collections::HashMap; use std::fmt::Write; + use anyhow::{anyhow, bail}; +use clients_schema::Property; use indexmap::indexmap; +use openapiv3::{ + MediaType, Parameter, ParameterData, ParameterSchemaOrContent, PathItem, PathStyle, Paths, QueryStyle, ReferenceOr, + RequestBody, Response, Responses, StatusCode, +}; -use openapiv3::{MediaType, Parameter, ParameterData, ParameterSchemaOrContent, PathItem, Paths, PathStyle, QueryStyle, ReferenceOr, RequestBody, Response, Responses, StatusCode}; -use regex::Regex; - -use clients_schema::Property; use crate::components::TypesAndComponents; /// Add an endpoint to the OpenAPI schema. This will result in the addition of a number of elements to the /// openapi schema's `paths` and `components` sections. -/// -pub fn add_endpoint(endpoint: &clients_schema::Endpoint, tac: &mut TypesAndComponents, out: &mut Paths) -> anyhow::Result<()> { - +pub fn add_endpoint( + endpoint: &clients_schema::Endpoint, + tac: &mut TypesAndComponents, + out: &mut Paths, +) -> anyhow::Result<()> { if endpoint.request.is_none() { tracing::warn!("Endpoint {} is missing a request -- ignored", &endpoint.name); return Ok(()); @@ -42,7 +46,7 @@ pub fn add_endpoint(endpoint: &clients_schema::Endpoint, tac: &mut TypesAndCompo } // Namespace - //let namespace = match endpoint.name.split_once('.') { + // let namespace = match endpoint.name.split_once('.') { // Some((ns, _)) => ns, // None => "core", //}; @@ -78,12 +82,15 @@ pub fn add_endpoint(endpoint: &clients_schema::Endpoint, tac: &mut TypesAndCompo }; // Reuse reference if multiple paths, and inline otherwise - path_params.insert(prop.name.clone(), if is_multipath { - tac.add_parameter(&endpoint.name, parameter, false) - } else { - ReferenceOr::Item(parameter) - }); - }; + path_params.insert( + prop.name.clone(), + if is_multipath { + tac.add_parameter(&endpoint.name, parameter, false) + } else { + ReferenceOr::Item(parameter) + }, + ); + } //----- Prepare query parameters @@ -104,7 +111,7 @@ pub fn add_endpoint(endpoint: &clients_schema::Endpoint, tac: &mut TypesAndCompo } else { ReferenceOr::Item(parameter) }); - }; + } //---- Prepare request body @@ -120,7 +127,7 @@ pub fn add_endpoint(endpoint: &clients_schema::Endpoint, tac: &mut TypesAndCompo let body = RequestBody { description: None, // FIXME: nd-json requests - content: indexmap!{ "application/json".to_string() => media }, + content: indexmap! { "application/json".to_string() => media }, required: endpoint.request_body_required, extensions: Default::default(), }; @@ -167,16 +174,18 @@ pub fn add_endpoint(endpoint: &clients_schema::Endpoint, tac: &mut TypesAndCompo let mut operation_counter = 0; for url_template in &endpoint.urls { - // Path and query parameters let mut parameters = Vec::new(); for path_variable in get_path_parameters(&url_template.path) { - let parameter = path_params.get(path_variable) - .ok_or_else(|| anyhow!("Missing path parameter definition {} for endpoint {}", - path_variable, &endpoint.name) - )?; + let parameter = path_params.get(path_variable).ok_or_else(|| { + anyhow!( + "Missing path parameter definition {} for endpoint {}", + path_variable, + &endpoint.name + ) + })?; parameters.push(parameter.clone()); } @@ -202,7 +211,6 @@ pub fn add_endpoint(endpoint: &clients_schema::Endpoint, tac: &mut TypesAndCompo extensions: Default::default(), // FIXME: translate availability? }; - let mut operation_path = url_template.path.clone(); // Disabled -- OpenAPI path templates do not contain the query string @@ -210,7 +218,12 @@ pub fn add_endpoint(endpoint: &clients_schema::Endpoint, tac: &mut TypesAndCompo // Add query parameter names to the path template // See https://www.rfc-editor.org/rfc/rfc6570#section-3.2.8 if !&request.query.is_empty() { - let params = &request.query.iter().map(|p| p.name.as_str()).collect::>().join(","); + let params = &request + .query + .iter() + .map(|p| p.name.as_str()) + .collect::>() + .join(","); operation_path = format!("{operation_path}{{?{params}}}"); } } @@ -219,7 +232,8 @@ pub fn add_endpoint(endpoint: &clients_schema::Endpoint, tac: &mut TypesAndCompo // Check if this path has already been encountered with a different http method (possibly in a // different endpoint) - let path = out.paths + let path = out + .paths .entry(operation_path) .or_insert(ReferenceOr::Item(PathItem::default())); @@ -244,7 +258,7 @@ pub fn add_endpoint(endpoint: &clients_schema::Endpoint, tac: &mut TypesAndCompo let path = match path { ReferenceOr::Item(ref mut item) => item, - _ => bail!("Expecting an item (should not happe)") + _ => bail!("Expecting an item (should not happe)"), }; for method in &url_template.methods { @@ -261,12 +275,14 @@ pub fn add_endpoint(endpoint: &clients_schema::Endpoint, tac: &mut TypesAndCompo }; let mut operation = operation.clone(); - let mut operation_id: String = endpoint.name + let mut operation_id: String = endpoint + .name .chars() .map(|x| match x { '_' | '.' => '-', - _ => x - }).collect(); + _ => x, + }) + .collect(); if operation_counter != 0 { write!(&mut operation_id, "-{}", operation_counter)?; } @@ -280,12 +296,19 @@ pub fn add_endpoint(endpoint: &clients_schema::Endpoint, tac: &mut TypesAndCompo Ok(()) } -fn get_path_parameters (template: &str) -> Vec<&str> { - let regex = Regex::new(r"\{([^}]*)\}").unwrap(); - regex - .find_iter(template) - .map(|m| &template[m.start()+1 .. m.end()-1]) - .collect() +fn get_path_parameters(template: &str) -> Vec<&str> { + // Note: could be done with regex, but it adds 1 MB to the resulting WASM module + let mut result = Vec::new(); + let mut template = template; + while let Some((_start, end)) = template.split_once('{') { + if let Some((name, remainder)) = end.split_once('}') { + result.push(name); + template = remainder; + } else { + break; + } + } + result } #[cfg(test)] @@ -294,6 +317,12 @@ mod tests { #[test] fn test_path_parameters() { - assert_eq!(get_path_parameters("/{index}/{id}"), vec!{"index", "id"}) + assert_eq!(get_path_parameters("/{index}/{id}"), vec! {"index", "id"}); + assert_eq!(get_path_parameters("{index}{id}/"), vec! {"index", "id"}); + assert_eq!(get_path_parameters("/{index}/{id}/"), vec! {"index", "id"}); + + // Should normally not happen as we expect the model to be correct. Just make sure we don't crash. + assert_eq!(get_path_parameters("{index}{id/"), vec! {"index"}); + assert_eq!(get_path_parameters("{index{id}/"), vec! {"index{id"}); } } diff --git a/compiler-rs/clients_schema_to_openapi/src/schemas.rs b/compiler-rs/clients_schema_to_openapi/src/schemas.rs index d07f3d67a9..f6f72e0863 100644 --- a/compiler-rs/clients_schema_to_openapi/src/schemas.rs +++ b/compiler-rs/clients_schema_to_openapi/src/schemas.rs @@ -16,56 +16,51 @@ // under the License. use anyhow::bail; +use clients_schema::{ + Body, Enum, Interface, LiteralValueValue, PropertiesBody, Property, Request, Response, TypeAlias, + TypeAliasVariants, TypeDefinition, TypeName, ValueOf, +}; use indexmap::IndexMap; -use openapiv3::{AdditionalProperties, ArrayType, Discriminator, ExternalDocumentation, NumberType, ObjectType, ReferenceOr, Schema, SchemaData, SchemaKind, StringType, Type}; -use clients_schema::{Body, Enum, Interface, LiteralValueValue, PropertiesBody, Property, Request, Response, TypeAlias, TypeAliasVariants, TypeDefinition, TypeName, ValueOf}; +use openapiv3::{ + AdditionalProperties, ArrayType, Discriminator, ExternalDocumentation, NumberType, ObjectType, ReferenceOr, Schema, + SchemaData, SchemaKind, StringType, Type, +}; use crate::components::TypesAndComponents; -use crate::utils::{ IntoSchema, SchemaName, ReferenceOrBoxed }; +use crate::utils::{IntoSchema, ReferenceOrBoxed, SchemaName}; // A placeholder in components.schema to handle recursive types const SCHEMA_PLACEHOLDER: ReferenceOr = ReferenceOr::Reference { - reference: String::new() + reference: String::new(), }; -/// /// Convert `schema.json` type and value definitions to OpenAPI schemas: /// /// The `convert_*` functions return a concrete schema and not a reference and do not store them in /// the OpenAPI `components.schema`. This is the role of `for_type_name` hat creates and stores the /// schema and returns a reference. -/// -impl <'a> TypesAndComponents<'a> { - - /// +impl<'a> TypesAndComponents<'a> { /// Convert a value. Returns a schema reference and not a concrete schema, as values can /// be simple references to types. - /// pub fn convert_value_of(&mut self, value_of: &ValueOf) -> anyhow::Result> { - Ok(match value_of { - // // Instance_of ValueOf::InstanceOf(instance) => { // Do not care about generics, we work with an expanded schema self.for_type_name(&instance.typ)? - }, + } - // // Array - ValueOf::ArrayOf(array) => { - ReferenceOr::Item(Schema { - schema_data: Default::default(), - schema_kind: SchemaKind::Type(Type::Array(ArrayType { - items: Some(self.convert_value_of(&array.value)?.boxed()), - min_items: None, - max_items: None, - unique_items: false, - })) - }) - }, + ValueOf::ArrayOf(array) => ReferenceOr::Item(Schema { + schema_data: Default::default(), + schema_kind: SchemaKind::Type(Type::Array(ArrayType { + items: Some(self.convert_value_of(&array.value)?.boxed()), + min_items: None, + max_items: None, + unique_items: false, + })), + }), - // // Union ValueOf::UnionOf(union) => { let mut items = Vec::new(); @@ -75,37 +70,35 @@ impl <'a> TypesAndComponents<'a> { ReferenceOr::Item(Schema { schema_data: Default::default(), - schema_kind: SchemaKind::OneOf { - one_of: items, - } + schema_kind: SchemaKind::OneOf { one_of: items }, }) - }, + } - // // Dictionary // See https://swagger.io/docs/specification/data-models/dictionaries/ ValueOf::DictionaryOf(dict) => { ObjectType { properties: Default::default(), required: vec![], - additional_properties: Some(AdditionalProperties::Schema(Box::new(self.convert_value_of(&dict.value)?))), + additional_properties: Some(AdditionalProperties::Schema(Box::new( + self.convert_value_of(&dict.value)?, + ))), // Single key dictionaries have exactly one property min_properties: if dict.single_key { Some(1) } else { None }, max_properties: if dict.single_key { Some(1) } else { None }, - }.into_schema_ref() - }, + } + .into_schema_ref() + } - // // User defined value ValueOf::UserDefinedValue(_) => { ReferenceOr::Item(Schema { schema_data: Default::default(), // FIXME: not the right way to represent an arbitrary value - schema_kind: SchemaKind::Type(Type::Object(ObjectType::default())) + schema_kind: SchemaKind::Type(Type::Object(ObjectType::default())), }) - }, + } - // // Literal value ValueOf::LiteralValue(literal) => { let str_value = match &literal.value { @@ -129,9 +122,7 @@ impl <'a> TypesAndComponents<'a> { }) } - /// /// Return the reference for a type name, registering it if needed - /// pub fn for_type_name(&mut self, type_name: &TypeName) -> anyhow::Result> { let schema_name = type_name.schema_name(); @@ -143,28 +134,23 @@ impl <'a> TypesAndComponents<'a> { // Builtin types if type_name.namespace == "_builtins" { return match type_name.name.as_str() { - "string" => { - Ok(Type::String(StringType { - format: Default::default(), - pattern: None, - enumeration: vec![], - min_length: None, - max_length: None, - }).into_schema_ref()) - }, - "boolean" => { - Ok(Type::Boolean {}.into_schema_ref()) - }, - "number" => { - Ok(Type::Number(NumberType::default()).into_schema_ref()) - }, + "string" => Ok(Type::String(StringType { + format: Default::default(), + pattern: None, + enumeration: vec![], + min_length: None, + max_length: None, + }) + .into_schema_ref()), + "boolean" => Ok(Type::Boolean {}.into_schema_ref()), + "number" => Ok(Type::Number(NumberType::default()).into_schema_ref()), "void" => { // Empty object Ok(ObjectType::default().into_schema_ref()) - }, + } "null" => { - // Note that there is no null type; instead, the nullable attribute is used as a modifier of the base type. - // https://swagger.io/docs/specification/data-models/data-types/ + // Note that there is no null type; instead, the nullable attribute is used as a modifier of the + // base type. https://swagger.io/docs/specification/data-models/data-types/ // FIXME: null should be handled in unions by setting "nullable" to the resulting schema Ok(Type::String(StringType { @@ -173,22 +159,25 @@ impl <'a> TypesAndComponents<'a> { enumeration: vec![], min_length: None, max_length: None, - }).into_schema_ref_with_data_fn(|data| { data.nullable = true; })) - }, + }) + .into_schema_ref_with_data_fn(|data| { + data.nullable = true; + })) + } "binary" => { // FIXME: must be handled in requests and responses Ok(ObjectType::default().into_schema_ref()) } _ => bail!("unknown builtin type: {}", type_name), - } + }; } if type_name.namespace == "_types" { match type_name.name.as_str() { "double" | "long" | "integer" | "float" => { return Ok(Type::Number(NumberType::default()).into_schema_ref()); - }, - _ => {}, + } + _ => {} } } @@ -202,7 +191,7 @@ impl <'a> TypesAndComponents<'a> { Request(_) => bail!("Requests should be handled using for_request"), Response(_) => bail!("Responses should be handled using for_request"), - Enum(enumm) => self.convert_enum(enumm)?.into_schema_ref(), + Enum(enumm) => self.convert_enum(enumm)?.into_schema_ref(), Interface(itf) => self.convert_interface_definition(itf)?.into_schema_ref(), TypeAlias(alias) => self.convert_type_alias(alias)?.into_schema_ref(), }; @@ -219,11 +208,15 @@ impl <'a> TypesAndComponents<'a> { self.for_body(&response.body) } - pub fn convert_external_docs(&self, obj: & impl clients_schema::Documented) - -> Option { + pub fn convert_external_docs(&self, obj: &impl clients_schema::Documented) -> Option { // FIXME: does the model contain resolved doc_id? - obj.doc_url().map (|url| { - let branch: &str = self.model.info.as_ref().and_then(|i| i.version.as_deref()).unwrap_or("current"); + obj.doc_url().map(|url| { + let branch: &str = self + .model + .info + .as_ref() + .and_then(|i| i.version.as_deref()) + .unwrap_or("current"); ExternalDocumentation { description: None, url: url.trim().replace("{branch}", branch), @@ -233,19 +226,19 @@ impl <'a> TypesAndComponents<'a> { } fn for_body(&mut self, body: &Body) -> anyhow::Result>> { - let result = match body { Body::NoBody(_) => None, Body::Value(value_body) => Some(self.convert_value_of(&value_body.value)?), // TODO codegen_name? - Body::Properties(PropertiesBody { properties }) => { - Some(ObjectType { + Body::Properties(PropertiesBody { properties }) => Some( + ObjectType { properties: self.convert_properties(properties.iter())?, required: self.required_properties(properties.iter()), additional_properties: None, min_properties: None, max_properties: None, - }.into_schema_ref()) - } + } + .into_schema_ref(), + ), }; Ok(result) @@ -260,7 +253,10 @@ impl <'a> TypesAndComponents<'a> { Ok(result) } - fn convert_properties<'b> (&mut self, props: impl Iterator) -> anyhow::Result>>> { + fn convert_properties<'b>( + &mut self, + props: impl Iterator, + ) -> anyhow::Result>>> { let mut result = IndexMap::new(); for prop in props { result.insert(prop.name.clone(), self.convert_property(prop)?.boxed()); @@ -268,17 +264,19 @@ impl <'a> TypesAndComponents<'a> { Ok(result) } - fn required_properties<'b> (&mut self, props: impl Iterator) -> Vec { - props.filter_map(|prop| prop.required.then(|| prop.name.clone())).collect() + fn required_properties<'b>(&mut self, props: impl Iterator) -> Vec { + props + .filter(|&prop| prop.required).map(|prop| prop.name.clone()) + .collect() } - /// /// Convert an interface definition into a schema - /// fn convert_interface_definition(&mut self, itf: &Interface) -> anyhow::Result { - if !itf.generics.is_empty() { - bail!("Interface definition {} has generic parameters. Expand generics before conversion", itf.base.name); + bail!( + "Interface definition {} has generic parameters. Expand generics before conversion", + itf.base.name + ); } let mut schema = if let Some(container) = &itf.variants { @@ -286,8 +284,16 @@ impl <'a> TypesAndComponents<'a> { let _non_exhaustive = container.non_exhaustive; // Split container properties and variants - let container_props = itf.properties.iter().filter(|p| p.container_property).collect::>(); - let variant_props = itf.properties.iter().filter(|p| !p.container_property).collect::>(); + let container_props = itf + .properties + .iter() + .filter(|p| p.container_property) + .collect::>(); + let variant_props = itf + .properties + .iter() + .filter(|p| !p.container_property) + .collect::>(); // A container is represented by an object will all optional properties and exactly one that // needs to be set. @@ -297,7 +303,8 @@ impl <'a> TypesAndComponents<'a> { additional_properties: None, min_properties: Some(1), max_properties: Some(1), - }.into_schema(); + } + .into_schema(); if !container_props.is_empty() { // Create a schema for the container property, and group it in an "allOf" with variants @@ -307,16 +314,17 @@ impl <'a> TypesAndComponents<'a> { additional_properties: None, min_properties: None, max_properties: None, - }.into_schema_ref(); + } + .into_schema_ref(); schema = SchemaKind::AllOf { all_of: vec![container_props_schema, schema.into_schema_ref()], - }.into_schema(); + } + .into_schema(); } self.fill_schema_with_base(&mut schema, &itf.base); schema - } else { let schema = ObjectType { properties: self.convert_properties(itf.properties.iter())?, @@ -324,7 +332,8 @@ impl <'a> TypesAndComponents<'a> { additional_properties: None, min_properties: None, max_properties: None, - }.into_schema(); + } + .into_schema(); schema }; @@ -333,7 +342,8 @@ impl <'a> TypesAndComponents<'a> { if let Some(inherit) = &itf.inherits { schema = SchemaKind::AllOf { all_of: vec![self.for_type_name(&inherit.typ)?, schema.into_schema_ref()], - }.into_schema(); + } + .into_schema(); } // Behaviors @@ -349,21 +359,22 @@ impl <'a> TypesAndComponents<'a> { additional_properties: Some(AdditionalProperties::Schema(Box::new(value_schema))), min_properties: if single { Some(1) } else { None }, max_properties: if single { Some(1) } else { None }, - }.into_schema(); + } + .into_schema(); } - _ => bail!("Unknown behavior {}", &bh.typ) + _ => bail!("Unknown behavior {}", &bh.typ), } } Ok(schema) } - /// /// Creates alias an alias that references another type. - /// fn convert_type_alias(&mut self, alias: &TypeAlias) -> anyhow::Result { - if !alias.generics.is_empty() { - bail!("Type alias {} has generic parameters. Expand generics before conversion", alias.base.name); + bail!( + "Type alias {} has generic parameters. Expand generics before conversion", + alias.base.name + ); } let mut schema = self @@ -371,10 +382,10 @@ impl <'a> TypesAndComponents<'a> { .into_schema_with_base(self, &alias.base); match &alias.variants { - None => {}, + None => {} Some(TypeAliasVariants::ExternalTag(_tag)) => { // TODO: typed-keys: add an extension to identify it? - }, + } Some(TypeAliasVariants::InternalTag(tag)) => { // TODO: add tag.default_tag as an extension schema.schema_data.discriminator = Some(Discriminator { @@ -382,22 +393,17 @@ impl <'a> TypesAndComponents<'a> { mapping: Default::default(), extensions: Default::default(), }); - }, + } }; Ok(schema) } - /// /// Register an enumeration and return the schema reference. - /// fn convert_enum(&mut self, enumm: &Enum) -> anyhow::Result { - // TODO: enum.is_open - let enum_values = enumm.members.iter().map(|m| { - Some(m.name.clone()) - }).collect::>(); + let enum_values = enumm.members.iter().map(|m| Some(m.name.clone())).collect::>(); Ok(StringType { format: Default::default(), @@ -405,7 +411,8 @@ impl <'a> TypesAndComponents<'a> { enumeration: enum_values, min_length: None, max_length: None, - }.into_schema_with_base(self, &enumm.base)) + } + .into_schema_with_base(self, &enumm.base)) } fn fill_schema_with_base(&self, schema: &mut Schema, base: &clients_schema::BaseType) { @@ -450,5 +457,3 @@ impl <'a> TypesAndComponents<'a> { // TODO: prop.deprecation as extension } } - - diff --git a/compiler-rs/clients_schema_to_openapi/src/utils.rs b/compiler-rs/clients_schema_to_openapi/src/utils.rs index e937313ca5..d4b78a8be2 100644 --- a/compiler-rs/clients_schema_to_openapi/src/utils.rs +++ b/compiler-rs/clients_schema_to_openapi/src/utils.rs @@ -15,18 +15,17 @@ // specific language governing permissions and limitations // under the License. -use openapiv3::{ObjectType, ReferenceOr, Schema, SchemaData, SchemaKind, StringType, Type}; use clients_schema::TypeName; +use openapiv3::{ObjectType, ReferenceOr, Schema, SchemaData, SchemaKind, StringType, Type}; + use crate::components::TypesAndComponents; -/// /// Extensions to `ReferenceOr` to ease conversion to boxed versions. -/// pub trait ReferenceOrBoxed { fn boxed(self) -> ReferenceOr>; } -impl ReferenceOrBoxed for ReferenceOr { +impl ReferenceOrBoxed for ReferenceOr { fn boxed(self) -> ReferenceOr> { match self { ReferenceOr::Item(t) => ReferenceOr::Item(Box::new(t)), @@ -35,9 +34,7 @@ impl ReferenceOrBoxed for ReferenceOr { } } -/// /// Extension to `TypeName` to return its name as an OpenAPI schema -/// pub trait SchemaName { /// Name in the `#/components/schema` section fn schema_name(&self) -> String; @@ -52,17 +49,16 @@ impl SchemaName for TypeName { fn schema_ref(&self) -> ReferenceOr { ReferenceOr::Reference { - reference: format!("#/components/schemas/{}", self) + reference: format!("#/components/schemas/{}", self), } } } -/// /// Convenience extensions to turn OpenAPI type declarations into a `ReferenceOr`. /// This avoids a lot of boiler plate when creating schema objects. -/// pub trait IntoSchema { - fn into_schema_ref(self) -> ReferenceOr where Self: Sized{ + fn into_schema_ref(self) -> ReferenceOr + where Self: Sized { ReferenceOr::Item(self.into_schema()) } @@ -74,7 +70,8 @@ pub trait IntoSchema { // result // } - fn into_schema_ref_with_data_fn(self, f: fn (&mut SchemaData) -> ()) -> ReferenceOr where Self: Sized { + fn into_schema_ref_with_data_fn(self, f: fn(&mut SchemaData) -> ()) -> ReferenceOr + where Self: Sized { let mut result = self.into_schema_ref(); if let ReferenceOr::Item(ref mut schema) = &mut result { f(&mut schema.schema_data); @@ -82,13 +79,15 @@ pub trait IntoSchema { result } - fn into_schema_with_base(self, tac: &TypesAndComponents, base: &clients_schema::BaseType) -> Schema where Self: Sized { + fn into_schema_with_base(self, tac: &TypesAndComponents, base: &clients_schema::BaseType) -> Schema + where Self: Sized { let mut schema = self.into_schema(); tac.fill_data_with_base(&mut schema.schema_data, base); schema } - fn into_schema_with_data_fn(self, f: fn (&mut SchemaData) -> ()) -> Schema where Self: Sized { + fn into_schema_with_data_fn(self, f: fn(&mut SchemaData) -> ()) -> Schema + where Self: Sized { let mut schema = self.into_schema(); f(&mut schema.schema_data); schema @@ -104,16 +103,15 @@ impl IntoSchema for Schema { } impl IntoSchema for ReferenceOr { - fn into_schema_ref(self) -> ReferenceOr where Self: Sized { + fn into_schema_ref(self) -> ReferenceOr + where Self: Sized { self } fn into_schema(self) -> Schema { match self { ReferenceOr::Item(schema) => schema, - ReferenceOr::Reference { .. } => SchemaKind::AllOf { - all_of: vec![self] - }.into_schema() + ReferenceOr::Reference { .. } => SchemaKind::AllOf { all_of: vec![self] }.into_schema(), } } } diff --git a/compiler-rs/compiler-wasm-lib/Cargo.toml b/compiler-rs/compiler-wasm-lib/Cargo.toml index dd7e197cee..18d6fd1f4c 100644 --- a/compiler-rs/compiler-wasm-lib/Cargo.toml +++ b/compiler-rs/compiler-wasm-lib/Cargo.toml @@ -24,5 +24,8 @@ web-sys = { version = "0.3.64", features = ["console"] } wasm-bindgen-test = "0.3.37" [package.metadata.wasm-pack.profile.release] +opt-level = 'z' +lto = true +codegen-units = 1 wasm-opt = ['-Oz'] diff --git a/compiler-rs/compiler-wasm-lib/pkg/compiler_wasm_lib_bg.wasm b/compiler-rs/compiler-wasm-lib/pkg/compiler_wasm_lib_bg.wasm index f3418179a7..6ce81c1721 100644 Binary files a/compiler-rs/compiler-wasm-lib/pkg/compiler_wasm_lib_bg.wasm and b/compiler-rs/compiler-wasm-lib/pkg/compiler_wasm_lib_bg.wasm differ diff --git a/compiler-rs/compiler-wasm-lib/src/lib.rs b/compiler-rs/compiler-wasm-lib/src/lib.rs index e37a13ba03..2bf0c5c8f5 100644 --- a/compiler-rs/compiler-wasm-lib/src/lib.rs +++ b/compiler-rs/compiler-wasm-lib/src/lib.rs @@ -16,11 +16,10 @@ // under the License. use anyhow::bail; -use wasm_bindgen::prelude::*; use clients_schema::{Availabilities, Visibility}; +use wasm_bindgen::prelude::*; - -#[cfg(not(target_arch="wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), not(feature = "cargo-clippy")))] compile_error!("To build this crate use `make compiler-wasm-lib`"); #[wasm_bindgen] @@ -30,7 +29,6 @@ pub fn convert_schema_to_openapi(json: &str, flavor: &str) -> Result anyhow::Result { - let filter: Option) -> bool> = match flavor { "all" => None, "stack" => Some(|a| { diff --git a/compiler-rs/openapi_to_clients_schema/src/lib.rs b/compiler-rs/openapi_to_clients_schema/src/lib.rs index 6b3a383180..ca763ab2cc 100644 --- a/compiler-rs/openapi_to_clients_schema/src/lib.rs +++ b/compiler-rs/openapi_to_clients_schema/src/lib.rs @@ -15,20 +15,18 @@ // specific language governing permissions and limitations // under the License. +pub mod endpoints; pub mod openapi; pub mod types; -pub mod endpoints; use std::convert::Into; -use tracing::warn; + use clients_schema::IndexedModel; use openapi::OpenAPI; +use tracing::warn; -/// /// Generate a schema.json from an OpenAPI schema -/// pub fn generate(open_api: &OpenAPI) -> anyhow::Result { - let mut json_schema = clients_schema::IndexedModel::default(); generate_types(open_api, &mut json_schema)?; @@ -42,14 +40,8 @@ pub fn generate(open_api: &OpenAPI) -> anyhow::Result anyhow::Result<()> { - +fn generate_types(open_api: &OpenAPI, model: &mut IndexedModel) -> anyhow::Result<()> { if let Some(ref components) = open_api.components { let mut types = types::Types::default(); for (id, schema) in &components.schemas { diff --git a/compiler-rs/openapi_to_clients_schema/src/main.rs b/compiler-rs/openapi_to_clients_schema/src/main.rs index 87be8dec54..0405ac91ad 100644 --- a/compiler-rs/openapi_to_clients_schema/src/main.rs +++ b/compiler-rs/openapi_to_clients_schema/src/main.rs @@ -16,20 +16,18 @@ // under the License. use std::collections::BTreeSet; +use std::path::PathBuf; + use openapi_to_clients_schema::openapi::OpenAPI; -use tracing::{Level, info}; +use tracing::{info, Level}; use tracing_subscriber::FmtSubscriber; -use std::path::PathBuf; fn main() -> anyhow::Result<()> { - - let subscriber = FmtSubscriber::builder() - .with_max_level(Level::TRACE) - .finish(); + let subscriber = FmtSubscriber::builder().with_max_level(Level::TRACE).finish(); tracing::subscriber::set_global_default(subscriber)?; - //let file = "../../ent-search/swagger/v1/enterprise-search.json"; - //let file = "../../ent-search/swagger/v1/workplace-search.json"; + // let file = "../../ent-search/swagger/v1/enterprise-search.json"; + // let file = "../../ent-search/swagger/v1/workplace-search.json"; let file = "./openapi_to_clients_schema/fixtures/workplace-search.json"; let src = PathBuf::from(file); @@ -58,4 +56,3 @@ fn main() -> anyhow::Result<()> { Ok(()) } - diff --git a/compiler-rs/openapi_to_clients_schema/src/openapi.rs b/compiler-rs/openapi_to_clients_schema/src/openapi.rs index 6a63178f71..5b4911629c 100644 --- a/compiler-rs/openapi_to_clients_schema/src/openapi.rs +++ b/compiler-rs/openapi_to_clients_schema/src/openapi.rs @@ -18,9 +18,10 @@ //! OpenAPI schema utilities use std::fmt::Debug; -use openapiv3::*; use std::ops::Deref; + use anyhow::{anyhow, bail}; +use openapiv3::*; use serde_json::Value as JsonValue; /// A wrapper around an openapi schema, also providing helper methods to explore it. @@ -35,26 +36,20 @@ impl Deref for OpenAPI { } impl OpenAPI { - - /// /// Get the schema for a reference, if it exists, and follows references until we find a schema - /// pub fn ref_to_schema(&self, reference: &str) -> anyhow::Result<&Schema> { - if let Some(name) = reference.strip_prefix("#/components/schemas/") { match self.components.as_ref().unwrap().schemas.get(name) { Some(ReferenceOr::Item(sch)) => return Ok(sch), Some(ReferenceOr::Reference { reference }) => return self.ref_to_schema(reference), - _ => {}, + _ => {} } } bail!("No schema definition found for {reference}") } - /// /// Get the schema for a reference or schema - /// pub fn get_schema<'a>(&'a self, r_or_s: &'a ReferenceOr) -> anyhow::Result<&'a Schema> { match r_or_s { ReferenceOr::Reference { reference } => self.ref_to_schema(reference), @@ -62,36 +57,30 @@ impl OpenAPI { } } - /// /// Is this type nullable? - /// pub fn is_nullable(&self, id: &str) -> bool { self.components .as_ref() .and_then(|c| c.schemas.get(id)) .map(|s| match s { ReferenceOr::Reference { reference: _r } => false, - ReferenceOr::Item(item) => item.schema_data.nullable + ReferenceOr::Item(item) => item.schema_data.nullable, }) .unwrap_or_default() } - /// /// Is this a `not` schema? - /// pub fn is_not(&self, r_or_s: &ReferenceOr) -> bool { if let Ok(schema) = self.get_schema(r_or_s) { - if let SchemaKind::Not {..} = schema.schema_kind { + if let SchemaKind::Not { .. } = schema.schema_kind { return true; } } false } - /// /// Merge a series of schemas and return a single combined schema that is guaranteed to not be an `Any`. /// Used to resolve `allOf` directives. - /// pub fn merge_schemas(&self, schemas: &Vec>, data: &SchemaData) -> anyhow::Result { let mut merged = AnySchema::default(); for schema in schemas { @@ -105,18 +94,17 @@ impl OpenAPI { } fn merge_in_any(&self, acc: AnySchema, schema: &ReferenceOr) -> anyhow::Result { - let schema = self.get_schema(schema)?; match &schema.schema_kind { - SchemaKind::AllOf { all_of } | SchemaKind::Any(AnySchema{ all_of, .. }) if !all_of.is_empty() => { + SchemaKind::AllOf { all_of } | SchemaKind::Any(AnySchema { all_of, .. }) if !all_of.is_empty() => { let mut result = acc; for schema in all_of { result = self.merge_in_any(result, schema)?; } return Ok(result); - }, - _ => {}, + } + _ => {} } let schema = schema_to_any(&schema.schema_kind)?; @@ -161,10 +149,8 @@ impl OpenAPI { } } -/// /// Converts a schema to the "any" form that has all properties of an OpenAPI schema. /// Used for schema merging operations -/// pub fn schema_to_any(schema: &SchemaKind) -> anyhow::Result { // TODO: could return Cow to reduce cloning @@ -174,7 +160,9 @@ pub fn schema_to_any(schema: &SchemaKind) -> anyhow::Result { typ: Some("string".into()), format: format_to_string(&x.format), pattern: x.pattern.clone(), - enumeration: x.enumeration.iter() + enumeration: x + .enumeration + .iter() // turn Vec> into Vec .filter_map(|e| e.as_ref().map(|s| JsonValue::String(s.clone()))) .collect(), @@ -190,7 +178,9 @@ pub fn schema_to_any(schema: &SchemaKind) -> anyhow::Result { exclusive_maximum: Some(x.exclusive_maximum), minimum: x.minimum, maximum: x.maximum, - enumeration: x.enumeration.iter() + enumeration: x + .enumeration + .iter() .filter_map(|e| *e) .filter_map(serde_json::Number::from_f64) .map(JsonValue::Number) @@ -205,7 +195,9 @@ pub fn schema_to_any(schema: &SchemaKind) -> anyhow::Result { exclusive_maximum: Some(x.exclusive_maximum), minimum: x.minimum.map(|x| x as f64), maximum: x.maximum.map(|x| x as f64), - enumeration: x.enumeration.iter() + enumeration: x + .enumeration + .iter() .filter_map(|e| *e) .map(|s| JsonValue::Number(s.into())) .collect(), @@ -232,7 +224,7 @@ pub fn schema_to_any(schema: &SchemaKind) -> anyhow::Result { typ: Some("boolean".into()), ..AnySchema::default() }), - } + }, SchemaKind::OneOf { one_of } => Ok(AnySchema { one_of: one_of.clone(), @@ -258,35 +250,27 @@ pub fn schema_to_any(schema: &SchemaKind) -> anyhow::Result { } } - -/// /// Concatenates two vectors and returns the resulting vector -/// fn merge_vec(mut vec1: Vec, mut vec2: Vec) -> Vec { vec1.append(&mut vec2); vec1 } -/// /// Merge two options. Fails if both options are set and have different values. -/// fn merge_option(opt1: Option, opt2: Option) -> anyhow::Result> { match (&opt1, &opt2) { (Some(v1), Some(v2)) if v1 == v2 => bail!("Merge conflict: {opt1:?} and {opt2:?}"), - _ => Ok(opt1.or(opt2)) + _ => Ok(opt1.or(opt2)), } } pub fn any_to_schema(any: AnySchema) -> anyhow::Result { - // TODO: SchemaData.default could be used for disambiguation let typ: Option<&str> = if let Some(typ) = &any.typ { Some(typ) - } else if any.additional_properties.is_some() || - !any.properties.is_empty() || - !any.required.is_empty() { - Some("object") - } else if let Some(JsonValue::String(_)) = any.enumeration.get(0) { + } else if any.additional_properties.is_some() || !any.properties.is_empty() || !any.required.is_empty() { + Some("object") + } else if let Some(JsonValue::String(_)) = any.enumeration.first() { Some("string") } else { // TODO: add more heuristics @@ -296,7 +280,6 @@ pub fn any_to_schema(any: AnySchema) -> anyhow::Result { let typ = typ.ok_or_else(|| anyhow!("Cannot infer schema type in {:?}", any))?; match typ { - "object" => Ok(SchemaKind::Type(Type::Object(ObjectType { properties: any.properties, required: any.required, @@ -308,8 +291,16 @@ pub fn any_to_schema(any: AnySchema) -> anyhow::Result { "string" => Ok(SchemaKind::Type(Type::String(StringType { format: as_unknown_or_empty(any.format), pattern: any.pattern, - enumeration: any.enumeration.iter() - .map(|v| if let JsonValue::String(s) = v { Some(s.clone()) } else { None }) + enumeration: any + .enumeration + .iter() + .map(|v| { + if let JsonValue::String(s) = v { + Some(s.clone()) + } else { + None + } + }) .collect(), min_length: any.min_length, max_length: any.max_length, @@ -335,8 +326,7 @@ pub fn any_to_schema(any: AnySchema) -> anyhow::Result { enumeration: vec![], // TODO: not supported in schema.json }))), - "boolean" => Ok(SchemaKind::Type(Type::Boolean { - })), + "boolean" => Ok(SchemaKind::Type(Type::Boolean {})), "array" => Ok(SchemaKind::Type(Type::Array(ArrayType { items: any.items, @@ -373,13 +363,12 @@ fn format_to_string(value: &VariantOrUnknownOrEmpty) -> Option { Ref(&'a str), Schema(&'a Schema), } -impl <'a> From<&'a ReferenceOr> for RefOrSchema<'a> { +impl<'a> From<&'a ReferenceOr> for RefOrSchema<'a> { fn from(value: &'a ReferenceOr) -> Self { match value { ReferenceOr::Reference { reference } => RefOrSchema::Ref(reference), @@ -388,7 +377,7 @@ impl <'a> From<&'a ReferenceOr> for RefOrSchema<'a> { } } -impl <'a> From<&'a ReferenceOr>> for RefOrSchema<'a> { +impl<'a> From<&'a ReferenceOr>> for RefOrSchema<'a> { fn from(value: &'a ReferenceOr>) -> Self { match value { ReferenceOr::Reference { reference } => RefOrSchema::Ref(reference), @@ -397,7 +386,7 @@ impl <'a> From<&'a ReferenceOr>> for RefOrSchema<'a> { } } -impl <'a> From<&'a Box>> for RefOrSchema<'a> { +impl<'a> From<&'a Box>> for RefOrSchema<'a> { fn from(value: &'a Box>) -> Self { match value.as_ref() { ReferenceOr::Reference { reference } => RefOrSchema::Ref(reference), diff --git a/compiler-rs/openapi_to_clients_schema/src/types.rs b/compiler-rs/openapi_to_clients_schema/src/types.rs index de0e94c34b..42354dc41d 100644 --- a/compiler-rs/openapi_to_clients_schema/src/types.rs +++ b/compiler-rs/openapi_to_clients_schema/src/types.rs @@ -15,12 +15,14 @@ // specific language governing permissions and limitations // under the License. -use clients_schema::*; -use openapiv3::*; -use indexmap::IndexMap; use std::collections::{BTreeMap, HashSet}; + use anyhow::{anyhow, bail}; +use clients_schema::*; +use indexmap::IndexMap; +use openapiv3::*; use tracing::{error, info}; + use crate::openapi; use crate::openapi::{OpenAPI, RefOrSchema}; @@ -31,7 +33,6 @@ pub struct Types { } impl Types { - pub fn track(&mut self, id: &str) { self.tracker.insert(id.to_string()); } @@ -41,7 +42,7 @@ impl Types { } pub fn add(&mut self, id: &str, type_def: TypeDefinition) { - //info!("Adding type '{id}'"); + // info!("Adding type '{id}'"); self.types.insert(id.to_string(), type_def); } @@ -67,26 +68,23 @@ impl Types { } } -/// /// Generate a top-level type, which can be an alias or a type definition -/// -pub fn generate_type ( +pub fn generate_type( open_api: &OpenAPI, id: &str, definition: &RefOrSchema, - types: &mut Types + types: &mut Types, ) -> anyhow::Result { - - //info!("Generating type '{id}'"); + // info!("Generating type '{id}'"); types.track(id); match definition { RefOrSchema::Ref(ref_id) => { // Type alias let type_name = ref_to_typename(id); - types.add(id, TypeDefinition::type_alias( - type_name.clone(), - ref_to_typename(ref_id).into() - )); + types.add( + id, + TypeAlias::new(type_name.clone(), ref_to_typename(ref_id).into()).into(), + ); Ok(type_name) } RefOrSchema::Schema(schema) => { @@ -96,19 +94,16 @@ pub fn generate_type ( } } -/// /// Generate a type definition from an OpenAPI component schema. /// This can result in several types if the schema contains anonymous nested structures -/// fn generate_type_for_schema( open_api: &OpenAPI, id: &str, schema: &Schema, - types: &mut Types + types: &mut Types, ) -> anyhow::Result { - if let Some(typedef) = types.get(id) { - //info!("Type '{id}' already generated"); + // info!("Type '{id}' already generated"); return Ok(typedef.name().clone()); } @@ -120,7 +115,10 @@ fn generate_type_for_schema( let mut base = BaseType::new(type_name.clone()); base.description = data.description.clone(); if data.deprecated { - base.deprecation = Some(Deprecation {version: "".into(), description: "".into()}) + base.deprecation = Some(Deprecation { + version: "".into(), + description: "".into(), + }) } if let Some(ref docs) = data.external_docs { base.doc_url = Some(docs.url.clone()) @@ -136,7 +134,10 @@ fn generate_type_for_schema( if data.discriminator.is_some() { // FIXME: data.discriminator -> internally tagged variant - bail!("Discriminator in schema {} has to become an internally tagged variant", id); + bail!( + "Discriminator in schema {} has to become an internally tagged variant", + id + ); } use openapiv3::SchemaKind::*; @@ -145,16 +146,16 @@ fn generate_type_for_schema( // Type alias to a primitive type or enum generate_schema_kind_type(open_api, id, t, base, types)?; } - Not {..} => { + Not { .. } => { bail!("Unsupported schema kind for '{}' - {:?}", id, schema.schema_kind); - }, + } Any(any) => { let not_any = Schema { schema_data: data.clone(), - schema_kind: openapi::any_to_schema(any.clone())? + schema_kind: openapi::any_to_schema(any.clone())?, }; generate_type_for_schema(open_api, id, ¬_any, types)?; - }, + } // Definitions: // - oneOf: validates the value against exactly one of the subschemas // - anyOf: validates the value against any (one or more) of the subschemas @@ -163,11 +164,11 @@ fn generate_type_for_schema( // AnyOf sits in between oneOf and allOf and doesn't have a direct equivalence in schema.json // We choose to handle it like a oneOf, even if oneOf is more constrained, as allOf is used for // composition/inheritance. - AllOf {all_of} => { + AllOf { all_of } => { let merged = open_api.merge_schemas(all_of, data)?; generate_type_for_schema(open_api, id, &merged, types)?; } - AnyOf {any_of: one_of} | OneOf {one_of} => { + AnyOf { any_of: one_of } | OneOf { one_of } => { generate_schema_kind_one_of(open_api, id, one_of, &data.discriminator, base, types)?; } } @@ -175,22 +176,19 @@ fn generate_type_for_schema( Ok(type_name) } -/// /// Generate the TypeDefinition for an openapi::SchemaKind::Type /// - `id`: name of the enclosing OpenApi type /// - `t`: the OpenApi type definition /// - `base`: common fields for the type definition /// - `types`: the target schema types, where the generated definition will be added, along with any synthetic types -/// that may be needed to represent nested structures. -/// +/// that may be needed to represent nested structures. fn generate_schema_kind_type( open_api: &OpenAPI, id: &str, t: &openapiv3::Type, base: BaseType, - types: &mut Types + types: &mut Types, ) -> anyhow::Result<()> { - fn alias(base: BaseType, name: TypeName) -> TypeDefinition { TypeDefinition::TypeAlias(TypeAlias { base, @@ -207,7 +205,9 @@ fn generate_schema_kind_type( String(string) if !string.enumeration.is_empty() => { // Enumeration - let members = string.enumeration.iter() + let members = string + .enumeration + .iter() .filter_map(|name| name.as_ref()) // filter empty options. Why are they here? .map(|name| name.as_str().into()) .collect(); @@ -215,11 +215,11 @@ fn generate_schema_kind_type( let enum_def = TypeDefinition::Enum(clients_schema::Enum { base, members, - is_open: false + is_open: false, }); types.add(id, enum_def); - }, + } //--------------------------------------------------------------------- String(_) => types.add(id, alias(base, builtins::STRING.clone())), @@ -232,7 +232,9 @@ fn generate_schema_kind_type( // OpenAPI Integer and Number accept an enumeration, but since it's just a list of valid // values with no identifier, we can't produce an enum out of that list. // TODO: choose int/long depending on min/max values - types.add(id, alias(base, builtins::LONG.clone())), + { + types.add(id, alias(base, builtins::LONG.clone())) + } //--------------------------------------------------------------------- Number(_) => { @@ -244,7 +246,10 @@ fn generate_schema_kind_type( Array(array) => { // NOTE: array.unique_items indicates a Set. We don't have that in schema.json, and it actually // doesn't exist in the JSON data model - let items = array.items.as_ref().ok_or(anyhow!("Array type in '{}' has no items", id))?; + let items = array + .items + .as_ref() + .ok_or(anyhow!("Array type in '{}' has no items", id))?; let value = generate_value_of(open_api, items.into(), || format!("{}_arrayitem", id), types)?; let alias_def = TypeDefinition::TypeAlias(TypeAlias { @@ -255,19 +260,26 @@ fn generate_schema_kind_type( }); types.add(id, alias_def); - }, + } //--------------------------------------------------------------------- Object(obj) => { if let (0, Some(ref value)) = (obj.properties.len(), &obj.additional_properties) { // No fixed properties: it's a dictionary generate_dictionary_def(open_api, id, base, value, types)?; - } else { // Regular type - generate_interface_def(open_api, id, base, &obj.required, &obj.properties, &obj.additional_properties, types)?; + generate_interface_def( + open_api, + id, + base, + &obj.required, + &obj.properties, + &obj.additional_properties, + types, + )?; } - }, + } } Ok(()) @@ -277,23 +289,18 @@ fn generate_union_of( open_api: &OpenAPI, id: &str, items: &[ReferenceOr], - types: &mut Types + types: &mut Types, ) -> anyhow::Result { // Open API items are ref_or_schema that we turn into a value_of // If producing that value_of requires the creation of a synthetic type, it will be // named by appending the item number to the type name (e.g. "foo_1") - let items = items.iter() + let items = items + .iter() .enumerate() - .map(|(idx, item)| generate_value_of( - open_api, - item.into(), || format!("{}_{}", id, idx), - types - )) + .map(|(idx, item)| generate_value_of(open_api, item.into(), || format!("{}_{}", id, idx), types)) .collect::, _>>()?; - Ok(UnionOf { - items, - }) + Ok(UnionOf { items }) } fn generate_schema_kind_one_of( @@ -302,9 +309,8 @@ fn generate_schema_kind_one_of( one_of: &[ReferenceOr], discriminator: &Option, base: BaseType, - types: &mut Types + types: &mut Types, ) -> anyhow::Result<()> { - let filtered = one_of.iter().filter(|s| !open_api.is_not(s)).collect::>(); if filtered.len() == 1 { return generate_type(open_api, id, &filtered[0].into(), types).map(|_| ()); @@ -345,7 +351,7 @@ fn generate_dictionary_def( id: &str, base: BaseType, value: &AdditionalProperties, - types: &mut Types + types: &mut Types, ) -> anyhow::Result<()> { let dict = TypeDefinition::TypeAlias(TypeAlias { base, @@ -356,13 +362,13 @@ fn generate_dictionary_def( generics: vec![ ValueOf::instance_of(builtins::STRING.clone()), match value { - AdditionalProperties::Any(_) => - (&builtins::USER_DEFINED).into(), + AdditionalProperties::Any(_) => (&builtins::USER_DEFINED).into(), - AdditionalProperties::Schema(schema) => + AdditionalProperties::Schema(schema) => { generate_value_of(open_api, schema.into(), || format!("{}_value", id), types)? + } }, - ] + ], }), }); @@ -377,7 +383,7 @@ fn generate_interface_def( required: &[String], properties: &IndexMap>>, additional_properties: &Option, - types: &mut Types + types: &mut Types, ) -> anyhow::Result<()> { // Regular type @@ -409,7 +415,7 @@ fn generate_interface_def( }; props.push(property); - }; + } let mut typedef = Interface { base, @@ -426,7 +432,9 @@ fn generate_interface_def( if let Some(props) = additional_properties { let prop_value: ValueOf = match props { AdditionalProperties::Any(_) => (&builtins::USER_DEFINED).into(), - AdditionalProperties::Schema(schema) => generate_value_of(open_api, schema.into(), || format!("{id}_props"), types)?, + AdditionalProperties::Schema(schema) => { + generate_value_of(open_api, schema.into(), || format!("{id}_props"), types)? + } }; typedef.behaviors.push(clients_schema::Inherits { typ: builtins::ADDITIONAL_PROPERTIES.clone(), @@ -438,24 +446,17 @@ fn generate_interface_def( Ok(()) } -/// /// Generate a value_of for a reference or a schema. If the schema doesn't denote one that can be represented as a /// value_of, a synthetic type is produced. -/// fn generate_value_of( open_api: &OpenAPI, value: RefOrSchema, id_gen: impl Fn() -> String, - types: &mut Types + types: &mut Types, ) -> anyhow::Result { - match value { - RefOrSchema::Ref(reference) => { - Ok(ref_to_typename(reference).into()) - }, - RefOrSchema::Schema(schema) => { - generate_value_for_schema(open_api, schema, id_gen, types) - } + RefOrSchema::Ref(reference) => Ok(ref_to_typename(reference).into()), + RefOrSchema::Schema(schema) => generate_value_for_schema(open_api, schema, id_gen, types), } } @@ -463,17 +464,14 @@ fn generate_value_for_schema( open_api: &OpenAPI, schema: &Schema, id_gen: impl Fn() -> String, - types: &mut Types + types: &mut Types, ) -> anyhow::Result { - use openapiv3::SchemaKind::*; match &schema.schema_kind { Type(typ) => { use openapiv3::Type::*; match typ { - String(string) if string.enumeration.is_empty() => { - Ok((&builtins::STRING).into()) - } + String(string) if string.enumeration.is_empty() => Ok((&builtins::STRING).into()), String(_) => { let type_name = generate_type_for_schema(open_api, &id_gen(), schema, types)?; Ok(type_name.into()) @@ -493,15 +491,16 @@ fn generate_value_for_schema( Ok(type_name.into()) } Array(array) => { - let item = array.items.as_ref().ok_or(anyhow!("Array type in '{}' has no items", id_gen()))?; + let item = array + .items + .as_ref() + .ok_or(anyhow!("Array type in '{}' has no items", id_gen()))?; let item = generate_value_of(open_api, item.into(), id_gen, types)?; - Ok(ValueOf::ArrayOf(ArrayOf {value: Box::new(item)})) - } - Boolean { .. } => { - Ok((&builtins::BOOLEAN).into()) + Ok(ValueOf::ArrayOf(ArrayOf { value: Box::new(item) })) } + Boolean { .. } => Ok((&builtins::BOOLEAN).into()), } - }, + } // Do not factorize below to keep exhaustiveness, if some specific handling is needed/possible OneOf { .. } => { let type_name = generate_type_for_schema(open_api, &id_gen(), schema, types)?; @@ -515,12 +514,8 @@ fn generate_value_for_schema( let type_name = generate_type_for_schema(open_api, &id_gen(), schema, types)?; Ok(type_name.into()) } - Not { .. } => { - Ok((&builtins::VOID).into()) - } - Any(AnySchema{ typ: Some(typ), .. }) if typ == "null" => { - Ok((&builtins::NULL).into()) - }, + Not { .. } => Ok((&builtins::VOID).into()), + Any(AnySchema { typ: Some(typ), .. }) if typ == "null" => Ok((&builtins::NULL).into()), Any(_) => { let type_name = generate_type_for_schema(open_api, &id_gen(), schema, types)?; Ok(type_name.into()) diff --git a/compiler-rs/rustfmt.toml b/compiler-rs/rustfmt.toml new file mode 100644 index 0000000000..2d69e202dc --- /dev/null +++ b/compiler-rs/rustfmt.toml @@ -0,0 +1,12 @@ +# This uses unstable features. Format using `cargo +nightly fmt` + +max_width = 120 +comment_width = 120 + +normalize_doc_attributes = true # useful for generated code +group_imports = "StdExternalCrate" + +wrap_comments = true +normalize_comments = true + +where_single_line = true