diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 23926ef81d..cc15cbb129 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,6 +4,7 @@ # Specification tooling owners /.github/ @elastic/clients-team /compiler/ @elastic/clients-team +/compiler-rs/ @elastic/clients-team /typescript-generator/ @elastic/clients-team /specification/_json_spec/ @elastic/clients-team /specification/_spec_utils/ @elastic/clients-team diff --git a/.github/labeler.yml b/.github/labeler.yml index 06d6ace8cd..3f409691c2 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -9,3 +9,4 @@ generator:typescript: compiler: - 'compiler/**/*' + - 'compiler-rs/**/*' diff --git a/.gitignore b/.gitignore index 1f98a77720..512899cbed 100644 --- a/.gitignore +++ b/.gitignore @@ -40,8 +40,6 @@ node_modules # Build output directory dist/ flight-recorder-generator/output -*.d.ts -java-generator/*.js specification/**/*.js specification/lib diff --git a/compiler-rs/.gitignore b/compiler-rs/.gitignore index ee44a96390..af515ad07b 100644 --- a/compiler-rs/.gitignore +++ b/compiler-rs/.gitignore @@ -1,2 +1,2 @@ .idea -target +target/ diff --git a/compiler-rs/clients_schema/src/lib.rs b/compiler-rs/clients_schema/src/lib.rs index 263bf8a916..a67c3d3ad2 100644 --- a/compiler-rs/clients_schema/src/lib.rs +++ b/compiler-rs/clients_schema/src/lib.rs @@ -15,7 +15,6 @@ // specific language governing permissions and limitations // under the License. -use std::cmp::Ordering; use std::fmt::{Debug, Display, Formatter}; use anyhow::anyhow; use indexmap::IndexMap; @@ -37,6 +36,14 @@ const fn is_false(v: &bool) -> bool { !(*v) } +// String type where efficient cloning brings significant improvements +// ArcStr is a compact reference counted string that makes cloning a very cheap operation. +// This is particularly important for `TypeName` that is cloned a lot, e.g. for `IndexedModel` keys +// See https://swatinem.de/blog/optimized-strings/ for a general discussion +// +// TODO: interning at deserialization time to reuse identical values (links from values to types) +type FastString = arcstr::ArcStr; + pub trait Documented { fn doc_url(&self) -> Option<&str>; fn doc_id(&self) -> Option<&str>; @@ -44,17 +51,11 @@ pub trait Documented { fn since(&self) -> Option<&str>; } -// ArcStr is a compact reference counted string that makes cloning a very cheap operation. -// This is particularly important for `TypeName` that is cloned a lot, e.g. for `IndexedModel` keys -// See https://swatinem.de/blog/optimized-strings/ for a general discussion -// -// TODO: interning at deserialization time to reuse identical values (links from values to types) -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Hash)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct TypeName { - pub namespace: arcstr::ArcStr, - pub name: arcstr::ArcStr, - // pub namespace: String, - // pub name: String, + // Order is important for Ord implementation + pub namespace: FastString, + pub name: FastString, } impl TypeName { @@ -76,15 +77,6 @@ impl Display for TypeName { } } -impl Ord for TypeName { - fn cmp(&self, other: &Self) -> Ordering { - match self.namespace.cmp(&other.namespace) { - Ordering::Equal => self.name.cmp(&other.name), - ordering @ _ => ordering, - } - } -} - //----------------------------------------------------------------------------- /// @@ -318,7 +310,7 @@ impl Flavor { pub fn visibility(&self, availabilities: &Option) -> Option { if let Some(ref availabilities) = availabilities { // Some availabilities defined - if let Some(ref availability) = availabilities.get(self) { + if let Some(availability) = availabilities.get(self) { // This one exists. Public by default availability.visibility.clone().or(Some(Visibility::Public)) } else { @@ -468,6 +460,7 @@ pub struct Inherits { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case", tag="kind")] +#[allow(clippy::large_enum_variant)] pub enum TypeDefinition { Interface(Interface), Request(Request), @@ -1143,22 +1136,22 @@ impl<'a> serde::de::Visitor<'a> for IndexMapVisitor { // Conversions between Model and IndexedModel //------------------------------------------------------------------------------------------------- -impl Into for Model { - fn into(self) -> IndexedModel { +impl From for IndexedModel { + fn from(value: Model) -> Self { IndexedModel { - info: self.info, - endpoints: self.endpoints, - types: self.types.into_iter().map(|t| (t.name().clone(), t)).collect(), + info: value.info, + endpoints: value.endpoints, + types: value.types.into_iter().map(|t| (t.name().clone(), t)).collect(), } } } -impl Into for IndexedModel { - fn into(self) -> Model { +impl From for Model { + fn from(value: IndexedModel) -> Model { Model { - info: self.info, - endpoints: self.endpoints, - types: self.types.into_iter().map(|(_, t)| t).collect(), + info: value.info, + endpoints: value.endpoints, + types: value.types.into_iter().map(|(_, t)| t).collect(), } } } @@ -1193,7 +1186,7 @@ mod tests { let search_type = result.get_type(&search_req).unwrap(); match search_type { - TypeDefinition::Request(r) => assert_eq!(true, !r.path.is_empty()), + TypeDefinition::Request(r) => assert!(!r.path.is_empty()), _ => 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 2620ae5c9b..1e367662a9 100644 --- a/compiler-rs/clients_schema/src/transform/availability.rs +++ b/compiler-rs/clients_schema/src/transform/availability.rs @@ -20,13 +20,14 @@ use crate::{Availabilities, Body, IndexedModel, Inherits, Property, TypeDefiniti use crate::transform::Worksheet; pub struct Availability { + #[allow(clippy::type_complexity)] avail_filter: Box) -> bool>, // Note: we could have avoided the use of interior mutability by adding // a `&mut Worksheet` parameter to all methods. worksheet: RefCell, } -impl <'a> Availability { +impl Availability { pub fn filter (mut model: IndexedModel, avail_filter: fn(&Option) -> bool) -> anyhow::Result { let filter = Availability { avail_filter: Box::new(avail_filter), @@ -38,8 +39,9 @@ impl <'a> Availability { // Initialize worksheet with request and response of all retained endpoints for endpoint in &model.endpoints { - endpoint.request.as_ref().map(|name| filter.filter_type(name)); - endpoint.response.as_ref().map(|name| filter.filter_type(name)); + for name in [&endpoint.request, &endpoint.response].into_iter().flatten() { + filter.filter_type(name); + } } while let Some(name) = { @@ -57,7 +59,7 @@ impl <'a> Availability { let ws = filter.worksheet.borrow(); model.types.retain(|k, _| ws.was_visited(k)); - return Ok(model); + Ok(model) } fn is_available(&self, availabilities: &Option) -> bool { @@ -71,7 +73,7 @@ impl <'a> Availability { fn filter_typedef(&self, typedef: &mut TypeDefinition) { match typedef { TypeDefinition::Interface(ref mut itf) => { - itf.inherits.as_ref().map(|i| self.filter_inherits(i)); + 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); }, @@ -86,7 +88,7 @@ impl <'a> Availability { }, TypeDefinition::Request(ref mut request) => { - request.inherits.as_ref().map(|i| self.filter_inherits(i)); + request.inherits.iter().for_each(|i| self.filter_inherits(i)); request.behaviors.iter().for_each(|i| self.filter_behaviors(i)); self.filter_properties(&mut request.path); self.filter_properties(&mut request.query); @@ -113,7 +115,7 @@ impl <'a> Availability { fn filter_properties(&self, props: &mut Vec) { props.retain(|p| self.is_available(&p.availability)); for prop in props { - self.filter_value_of(&mut prop.typ); + self.filter_value_of(&prop.typ); } } diff --git a/compiler-rs/clients_schema/src/transform/expand_generics.rs b/compiler-rs/clients_schema/src/transform/expand_generics.rs index aa4e8f5226..6d3c488f72 100644 --- a/compiler-rs/clients_schema/src/transform/expand_generics.rs +++ b/compiler-rs/clients_schema/src/transform/expand_generics.rs @@ -46,8 +46,9 @@ pub fn expand_generics( let mut ctx = Ctx::default(); for endpoint in &model.endpoints { - endpoint.request.as_ref().map(|t| expand_root_type(&t, &model, &mut ctx)); - endpoint.response.as_ref().map(|t| expand_root_type(&t, &model, &mut ctx)); + for name in [&endpoint.request, &endpoint.response].into_iter().flatten() { + expand_root_type(name, &model, &mut ctx)?; + } } // Add new types that were created to the model @@ -210,7 +211,7 @@ pub fn expand_generics( // We keep the generic parameters of behaviors, but expand their value for behavior in behaviors { for arg in &mut behavior.generics { - *arg = expand_valueof(arg, &mappings, model, ctx)?; + *arg = expand_valueof(arg, mappings, model, ctx)?; } } Ok(()) @@ -309,7 +310,7 @@ pub fn expand_generics( /// Builds the mapping from generic parameter name to actual value /// fn param_mapping(generics: &GenericParams, args: GenericArgs) -> GenericMapping { - generics.iter().map(|name| name.clone()).zip(args).collect() + generics.iter().cloned().zip(args).collect() } /// @@ -394,10 +395,10 @@ mod tests { if canonical_json != json_no_generics { std::fs::create_dir("test-output")?; let mut out = std::fs::File::create("test-output/schema-no-generics-canonical.json")?; - out.write(canonical_json.as_bytes())?; + out.write_all(canonical_json.as_bytes())?; let mut out = std::fs::File::create("test-output/schema-no-generics-new.json")?; - out.write(json_no_generics.as_bytes())?; + out.write_all(json_no_generics.as_bytes())?; panic!("Output differs from the canonical version. Both were written to 'test-output'"); } diff --git a/compiler-rs/clients_schema/src/transform/mod.rs b/compiler-rs/clients_schema/src/transform/mod.rs index 3a8bb06a9d..61397641d5 100644 --- a/compiler-rs/clients_schema/src/transform/mod.rs +++ b/compiler-rs/clients_schema/src/transform/mod.rs @@ -47,20 +47,24 @@ impl Worksheet { } } + /// Has this type name been visited? + pub fn was_visited(&self, name: &TypeName) -> bool { + self.visited.contains(name) + } +} + +impl Iterator for Worksheet { + type Item = TypeName; + /// Retrieves a type name from the work list, if some are left. This assumes the caller will /// process the corresponding type, and thus adds it to the list of visited type names. - pub fn next(&mut self) -> Option { + fn next(&mut self) -> Option { let result = self.pending.pop(); if let Some(ref name) = result { self.visited.insert(name.clone()); } result } - - /// Has this type name been visited? - pub fn was_visited(&self, name: &TypeName) -> bool { - self.visited.contains(name) - } } /// Transform a model to only keep the endpoints and types that match a predicate on the `availability` diff --git a/compiler-rs/clients_schema_to_openapi/src/lib.rs b/compiler-rs/clients_schema_to_openapi/src/lib.rs index f5ae627245..0bda20c3da 100644 --- a/compiler-rs/clients_schema_to_openapi/src/lib.rs +++ b/compiler-rs/clients_schema_to_openapi/src/lib.rs @@ -39,7 +39,7 @@ pub fn convert_schema_file( // 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)?; - let json_deser = &mut serde_json::Deserializer::from_str(&json); + let json_deser = &mut serde_json::Deserializer::from_str(json); let mut unused = HashSet::new(); let mut model: IndexedModel = serde_ignored::deserialize(json_deser, |path| { @@ -95,7 +95,7 @@ pub fn convert_schema( extensions: Default::default(), }; - let mut tac = TypesAndComponents::new(&model, openapi.components.as_mut().unwrap()); + let mut tac = TypesAndComponents::new(model, openapi.components.as_mut().unwrap()); // Endpoints for endpoint in &model.endpoints { diff --git a/compiler-rs/clients_schema_to_openapi/src/paths.rs b/compiler-rs/clients_schema_to_openapi/src/paths.rs index d0ba26021a..4b7f25ed68 100644 --- a/compiler-rs/clients_schema_to_openapi/src/paths.rs +++ b/compiler-rs/clients_schema_to_openapi/src/paths.rs @@ -48,7 +48,7 @@ pub fn add_endpoint(endpoint: &clients_schema::Endpoint, tac: &mut TypesAndCompo //}; // Will we produce multiple paths? If true, we will register components for reuse across paths - let is_multipath = endpoint.urls.len() > 1 || endpoint.urls.iter().find(|u| u.methods.len() > 1).is_some(); + let is_multipath = endpoint.urls.len() > 1 || endpoint.urls.iter().any(|u| u.methods.len() > 1); let request = tac.model.get_request(endpoint.request.as_ref().unwrap())?; diff --git a/compiler-rs/clients_schema_to_openapi/src/schemas.rs b/compiler-rs/clients_schema_to_openapi/src/schemas.rs index a87f2b271b..d07f3d67a9 100644 --- a/compiler-rs/clients_schema_to_openapi/src/schemas.rs +++ b/compiler-rs/clients_schema_to_openapi/src/schemas.rs @@ -292,7 +292,7 @@ impl <'a> TypesAndComponents<'a> { // A container is represented by an object will all optional properties and exactly one that // needs to be set. let mut schema = ObjectType { - properties: self.convert_properties(variant_props.iter().map(|p| *p))?, + properties: self.convert_properties(variant_props.iter().copied())?, required: vec![], additional_properties: None, min_properties: Some(1), @@ -302,8 +302,8 @@ impl <'a> TypesAndComponents<'a> { if !container_props.is_empty() { // Create a schema for the container property, and group it in an "allOf" with variants let container_props_schema = ObjectType { - properties: self.convert_properties(container_props.iter().map(|p| *p))?, - required: self.required_properties(container_props.iter().map(|p| *p)), + properties: self.convert_properties(container_props.iter().copied())?, + required: self.required_properties(container_props.iter().copied()), additional_properties: None, min_properties: None, max_properties: None, diff --git a/compiler-rs/compiler-wasm-lib/pkg/compiler_wasm_lib.d.ts b/compiler-rs/compiler-wasm-lib/pkg/compiler_wasm_lib.d.ts new file mode 100644 index 0000000000..e660ea4a6d --- /dev/null +++ b/compiler-rs/compiler-wasm-lib/pkg/compiler_wasm_lib.d.ts @@ -0,0 +1,8 @@ +/* tslint:disable */ +/* eslint-disable */ +/** +* @param {string} json +* @param {string} flavor +* @returns {string} +*/ +export function convert_schema_to_openapi(json: string, flavor: string): string; 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 628035e4af..f3418179a7 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/pkg/compiler_wasm_lib_bg.wasm.d.ts b/compiler-rs/compiler-wasm-lib/pkg/compiler_wasm_lib_bg.wasm.d.ts new file mode 100644 index 0000000000..d91825f6f3 --- /dev/null +++ b/compiler-rs/compiler-wasm-lib/pkg/compiler_wasm_lib_bg.wasm.d.ts @@ -0,0 +1,8 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory; +export function convert_schema_to_openapi(a: number, b: number, c: number, d: number, e: number): void; +export function __wbindgen_add_to_stack_pointer(a: number): number; +export function __wbindgen_malloc(a: number, b: number): number; +export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number; +export function __wbindgen_free(a: number, b: number, c: number): void; diff --git a/compiler-rs/openapi_to_clients_schema/src/lib.rs b/compiler-rs/openapi_to_clients_schema/src/lib.rs index de0db5073a..6b3a383180 100644 --- a/compiler-rs/openapi_to_clients_schema/src/lib.rs +++ b/compiler-rs/openapi_to_clients_schema/src/lib.rs @@ -53,14 +53,14 @@ fn generate_types( if let Some(ref components) = open_api.components { let mut types = types::Types::default(); for (id, schema) in &components.schemas { - let result = types::generate_type(open_api, &id, &schema.into(), &mut types); + let result = types::generate_type(open_api, id, &schema.into(), &mut types); if let Err(err) = result { warn!("Problem with type '{id}'\n {err}\n Definition: {:?}", &schema); } } let _ = types.check_tracker(); // TODO: actually fail - model.types = types.types(); + model.types = types.types().into_iter().map(|t| (t.name().clone(), t)).collect(); } Ok(()) diff --git a/compiler-rs/openapi_to_clients_schema/src/openapi.rs b/compiler-rs/openapi_to_clients_schema/src/openapi.rs index 0209ebe6ce..6a63178f71 100644 --- a/compiler-rs/openapi_to_clients_schema/src/openapi.rs +++ b/compiler-rs/openapi_to_clients_schema/src/openapi.rs @@ -22,7 +22,6 @@ use openapiv3::*; use std::ops::Deref; use anyhow::{anyhow, bail}; use serde_json::Value as JsonValue; -use tracing::info; /// A wrapper around an openapi schema, also providing helper methods to explore it. pub struct OpenAPI(pub openapiv3::OpenAPI); @@ -58,8 +57,8 @@ impl OpenAPI { /// 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), - ReferenceOr::Item(schema) => Ok(&schema), + ReferenceOr::Reference { reference } => self.ref_to_schema(reference), + ReferenceOr::Item(schema) => Ok(schema), } } @@ -113,7 +112,7 @@ impl OpenAPI { 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)?; + result = self.merge_in_any(result, schema)?; } return Ok(result); }, @@ -193,8 +192,8 @@ pub fn schema_to_any(schema: &SchemaKind) -> anyhow::Result { maximum: x.maximum, enumeration: x.enumeration.iter() .filter_map(|e| *e) - .filter_map(|s| serde_json::Number::from_f64(s)) - .map(|s| JsonValue::Number(s)) + .filter_map(serde_json::Number::from_f64) + .map(JsonValue::Number) .collect(), ..AnySchema::default() }), diff --git a/compiler-rs/openapi_to_clients_schema/src/types.rs b/compiler-rs/openapi_to_clients_schema/src/types.rs index f10479812c..de0e94c34b 100644 --- a/compiler-rs/openapi_to_clients_schema/src/types.rs +++ b/compiler-rs/openapi_to_clients_schema/src/types.rs @@ -85,7 +85,7 @@ pub fn generate_type ( let type_name = ref_to_typename(id); types.add(id, TypeDefinition::type_alias( type_name.clone(), - ref_to_typename(&ref_id).into() + ref_to_typename(ref_id).into() )); Ok(type_name) } @@ -164,7 +164,7 @@ fn generate_type_for_schema( // We choose to handle it like a oneOf, even if oneOf is more constrained, as allOf is used for // composition/inheritance. AllOf {all_of} => { - let merged = open_api.merge_schemas(&all_of, &data)?; + 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} => { @@ -276,7 +276,7 @@ fn generate_schema_kind_type( fn generate_union_of( open_api: &OpenAPI, id: &str, - items: &Vec>, + items: &[ReferenceOr], types: &mut Types ) -> anyhow::Result { // Open API items are ref_or_schema that we turn into a value_of @@ -299,7 +299,7 @@ fn generate_union_of( fn generate_schema_kind_one_of( open_api: &OpenAPI, id: &str, - one_of: &Vec>, + one_of: &[ReferenceOr], discriminator: &Option, base: BaseType, types: &mut Types @@ -333,7 +333,7 @@ fn generate_schema_kind_one_of( base, typ: ValueOf::UnionOf(union_of), generics: Vec::default(), - variants: variants, // May be set below + variants, // May be set below }; types.add(id, TypeDefinition::TypeAlias(type_alias)); @@ -374,7 +374,7 @@ fn generate_interface_def( open_api: &OpenAPI, id: &str, base: BaseType, - required: &Vec, + required: &[String], properties: &IndexMap>>, additional_properties: &Option, types: &mut Types @@ -405,6 +405,7 @@ fn generate_interface_def( container_property: false, es_quirk: None, server_default: None, + availability: None, }; props.push(property); @@ -474,7 +475,7 @@ fn generate_value_for_schema( Ok((&builtins::STRING).into()) } String(_) => { - let type_name = generate_type_for_schema(open_api, &id_gen(), &schema, types)?; + let type_name = generate_type_for_schema(open_api, &id_gen(), schema, types)?; Ok(type_name.into()) } Number(_) => { @@ -488,7 +489,7 @@ fn generate_value_for_schema( Ok((&builtins::LONG).into()) } Object(_) => { - let type_name = generate_type_for_schema(open_api, &id_gen(), &schema, types)?; + let type_name = generate_type_for_schema(open_api, &id_gen(), schema, types)?; Ok(type_name.into()) } Array(array) => { @@ -521,7 +522,7 @@ fn generate_value_for_schema( Ok((&builtins::NULL).into()) }, Any(_) => { - let type_name = generate_type_for_schema(open_api, &id_gen(), &schema, types)?; + let type_name = generate_type_for_schema(open_api, &id_gen(), schema, types)?; Ok(type_name.into()) } } @@ -532,7 +533,7 @@ fn id_to_typename(id: &str) -> TypeName { TypeName { namespace: "_global".into(), - name: id.to_case(Case::UpperCamel), + name: id.to_case(Case::UpperCamel).into(), } } diff --git a/compiler/package-lock.json b/compiler/package-lock.json index 205bcd2e81..eb4885e470 100644 --- a/compiler/package-lock.json +++ b/compiler/package-lock.json @@ -33,6 +33,7 @@ } }, "../compiler-rs/compiler-wasm-lib/pkg": { + "name": "compiler-wasm-lib", "version": "0.1.0" }, "node_modules/@babel/code-frame": {