Skip to content

Commit dac71c2

Browse files
authored
Merge branch 'main' into lifecycle-links
2 parents a5b1b3d + e4094f4 commit dac71c2

File tree

618 files changed

+10113
-2719
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

618 files changed

+10113
-2719
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ transform-to-openapi: ## Generate the OpenAPI definition from the compiled schem
5555
@npm run transform-to-openapi -- --schema output/schema/schema.json --flavor stack --output output/openapi/elasticsearch-openapi.json
5656
@npm run transform-to-openapi -- --schema output/schema/schema.json --flavor serverless --output output/openapi/elasticsearch-serverless-openapi.json
5757

58+
transform-to-openapi-for-docs: ## Generate the OpenAPI definition tailored for API docs generation
59+
@npm run transform-to-openapi -- --schema output/schema/schema.json --flavor stack --lift-enum-descriptions --merge-multipath-endpoints --output output/openapi/elasticsearch-openapi-docs.json
60+
5861
filter-for-serverless: ## Generate the serverless version from the compiled schema
5962
@npm run --prefix compiler filter-by-availability -- --serverless --visibility=public --input ../output/schema/schema.json --output ../output/output/openapi/elasticsearch-serverless-openapi.json
6063

compiler-rs/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

compiler-rs/clients_schema/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ pub struct Availability {
264264
pub visibility: Option<Visibility>,
265265
}
266266

267-
/// The availability of an
267+
/// The availability of an endpoint, field or parameter
268268
pub type Availabilities = IndexMap<Flavor, Availability>;
269269

270270
pub trait AvailabilityFilter: Fn(&Option<Availabilities>) -> bool {}

compiler-rs/clients_schema_to_openapi/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ clients_schema = {path="../clients_schema"}
1010
argh = { workspace = true }
1111
derive_more = { version = "2", features = ["from_str"] }
1212
serde_json = { workspace = true }
13-
serde_ignored = { workspace = true }
13+
itertools = { workspace = true }
1414
icu_segmenter = { workspace = true }
1515
openapiv3 = { workspace = true }
1616
anyhow = { workspace = true }

compiler-rs/clients_schema_to_openapi/src/cli.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,17 @@ pub struct Cli {
2020
#[argh(option, default = "SchemaFlavor::All")]
2121
pub flavor: SchemaFlavor,
2222

23-
/// add enum descriptions to property descriptions [default = true]
24-
#[argh(option, default = "true")]
25-
pub lift_enum_descriptions: bool,
26-
2723
/// generate only this namespace (can be repeated)
2824
#[argh(option)]
2925
pub namespace: Vec<String>,
26+
27+
/// add enum descriptions to property descriptions [default = true]
28+
#[argh(switch)]
29+
pub lift_enum_descriptions: bool,
30+
31+
/// merge endpoints with multiple paths into a single OpenAPI operation [default = false]
32+
#[argh(switch)]
33+
pub merge_multipath_endpoints: bool,
3034
}
3135

3236
use derive_more::FromStr;
@@ -42,20 +46,21 @@ pub enum SchemaFlavor {
4246
}
4347

4448
impl From<Cli> for Configuration {
45-
fn from(val: Cli) -> Configuration {
46-
let flavor = match val.flavor {
49+
fn from(cli: Cli) -> Configuration {
50+
let flavor = match cli.flavor {
4751
SchemaFlavor::All => None,
4852
SchemaFlavor::Serverless => Some(Flavor::Serverless),
4953
SchemaFlavor::Stack => Some(Flavor::Stack),
5054
};
5155

5256
Configuration {
5357
flavor,
54-
lift_enum_descriptions: val.lift_enum_descriptions,
55-
namespaces: if val.namespace.is_empty() {
58+
lift_enum_descriptions: cli.lift_enum_descriptions,
59+
merge_multipath_endpoints: cli.merge_multipath_endpoints,
60+
namespaces: if cli.namespace.is_empty() {
5661
None
5762
} else {
58-
Some(val.namespace)
63+
Some(cli.namespace)
5964
},
6065
}
6166
}

compiler-rs/clients_schema_to_openapi/src/lib.rs

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,24 @@ pub mod cli;
2323

2424
use indexmap::IndexMap;
2525

26-
use clients_schema::{Availabilities, Flavor, IndexedModel, Stability, Visibility};
26+
use clients_schema::{Availabilities, Availability, Flavor, IndexedModel, Stability, Visibility};
2727
use openapiv3::{Components, OpenAPI};
28+
use serde_json::Value;
2829
use clients_schema::transform::ExpandConfig;
2930
use crate::components::TypesAndComponents;
3031

3132
pub struct Configuration {
3233
pub flavor: Option<Flavor>,
3334
pub namespaces: Option<Vec<String>>,
35+
36+
/// If a property value is an enumeration, the description of possible values will be copied in the
37+
/// property's description (also works for arrays of enums).
3438
pub lift_enum_descriptions: bool,
39+
40+
/// Will output endpoints having multiple paths into a single operation. The operation's path will
41+
/// be the longest one (with values for all optional parameters), and the other paths will be added
42+
/// at the beginning of the operation's description.
43+
pub merge_multipath_endpoints: bool,
3544
}
3645

3746
/// Convert an API model into an OpenAPI v3 schema, optionally filtered for a given flavor
@@ -149,30 +158,37 @@ fn info(model: &IndexedModel) -> openapiv3::Info {
149158
}
150159
}
151160

152-
pub fn availability_as_extensions(availabilities: &Option<Availabilities>) -> IndexMap<String, serde_json::Value> {
161+
pub fn availability_as_extensions(availabilities: &Option<Availabilities>, flavor: &Option<Flavor>) -> IndexMap<String, serde_json::Value> {
153162
let mut result = IndexMap::new();
163+
convert_availabilities(availabilities, flavor, &mut result);
164+
result
165+
}
154166

167+
pub fn convert_availabilities(availabilities: &Option<Availabilities>, flavor: &Option<Flavor>, result: &mut IndexMap<String, Value>) {
155168
if let Some(avails) = availabilities {
156-
// We may have several availabilities, but since generally exists only on stateful (stack)
157-
for (_, availability) in avails {
158-
if let Some(stability) = &availability.stability {
159-
match stability {
169+
if let Some(flav) = flavor {
170+
if let Some(availability) = avails.get(flav) {
171+
let Availability {since,stability,..} = &availability;
172+
let stab = stability.clone().unwrap_or(Stability::Stable);
173+
let mut since_str = "".to_string();
174+
if let Some(since) = since {
175+
since_str = format!("; Added in {since}");
176+
}
177+
match stab {
160178
Stability::Beta => {
161-
result.insert("x-beta".to_string(), serde_json::Value::Bool(true));
179+
let beta_since = format!("Beta{since_str}");
180+
result.insert("x-state".to_string(), Value::String(beta_since));
162181
}
163182
Stability::Experimental => {
164-
result.insert("x-state".to_string(), serde_json::Value::String("Technical preview".to_string()));
183+
let exp_since = format!("Technical preview{since_str}");
184+
result.insert("x-state".to_string(), Value::String(exp_since));
165185
}
166-
Stability::Stable => {
167-
if let Some(since) = &availability.since {
168-
let stable_since = "Added in ".to_string() + since;
169-
result.insert("x-state".to_string(), serde_json::Value::String(stable_since));
170-
}
186+
Stability::Stable => {
187+
let stable_since = format!("Generally available{since_str}");
188+
result.insert("x-state".to_string(), Value::String(stable_since));
171189
}
172190
}
173191
}
174192
}
175193
}
176-
177-
result
178194
}

compiler-rs/clients_schema_to_openapi/src/paths.rs

Lines changed: 110 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,26 @@ use std::collections::HashMap;
1919
use std::fmt::Write;
2020

2121
use anyhow::{anyhow, bail};
22-
use clients_schema::Property;
22+
use clients_schema::{Privileges, Property};
2323
use indexmap::IndexMap;
2424
use indexmap::indexmap;
2525
use icu_segmenter::SentenceSegmenter;
26+
use itertools::Itertools;
2627
use openapiv3::{
2728
MediaType, Parameter, ParameterData, ParameterSchemaOrContent, PathItem, PathStyle, Paths, QueryStyle, ReferenceOr,
2829
RequestBody, Response, Responses, StatusCode, Example
2930
};
31+
use serde_json::Value;
3032
use clients_schema::SchemaExample;
31-
3233
use crate::components::TypesAndComponents;
34+
use crate::convert_availabilities;
3335

3436
/// Add an endpoint to the OpenAPI schema. This will result in the addition of a number of elements to the
3537
/// openapi schema's `paths` and `components` sections.
3638
pub fn add_endpoint(
3739
endpoint: &clients_schema::Endpoint,
3840
tac: &mut TypesAndComponents,
39-
out: &mut Paths,
41+
out: &mut Paths
4042
) -> anyhow::Result<()> {
4143
if endpoint.request.is_none() {
4244
// tracing::warn!("Endpoint {} is missing a request -- ignored", &endpoint.name);
@@ -60,6 +62,8 @@ pub fn add_endpoint(
6062
let request = tac.model.get_request(endpoint.request.as_ref().unwrap())?;
6163

6264
fn parameter_data(prop: &Property, in_path: bool, tac: &mut TypesAndComponents) -> anyhow::Result<ParameterData> {
65+
let mut extensions: IndexMap<String,Value> = Default::default();
66+
convert_availabilities(&prop.availability, &tac.config.flavor, &mut extensions);
6367
Ok(ParameterData {
6468
name: prop.name.clone(),
6569
description: tac.property_description(prop)?,
@@ -144,14 +148,16 @@ pub fn add_endpoint(
144148
// }
145149
};
146150

147-
let openapi_example = Example {
148-
value: example,
149-
description: schema_example.description.clone(),
150-
summary: schema_example.summary.clone(),
151-
external_value: None,
152-
extensions: Default::default(),
153-
};
154-
openapi_examples.insert(name.clone(), ReferenceOr::Item(openapi_example));
151+
if example.is_some() {
152+
let openapi_example = Example {
153+
value: example,
154+
description: schema_example.description.clone(),
155+
summary: schema_example.summary.clone(),
156+
external_value: None,
157+
extensions: Default::default(),
158+
};
159+
openapi_examples.insert(name.clone(), ReferenceOr::Item(openapi_example));
160+
}
155161
}
156162
openapi_examples
157163
}
@@ -228,6 +234,67 @@ pub fn add_endpoint(
228234
// TODO: add error responses
229235
};
230236

237+
//---- Merge multipath endpoints if asked for
238+
let mut new_endpoint: clients_schema::Endpoint;
239+
240+
let endpoint = if is_multipath && tac.config.merge_multipath_endpoints {
241+
new_endpoint = endpoint.clone();
242+
let endpoint = &mut new_endpoint;
243+
244+
// Sort paths from smallest to longest
245+
endpoint.urls.sort_by_key(|x| x.path.len());
246+
247+
// Keep the longest and its last method so that the operation's path+method are the same as the last one
248+
// (avoids the perception that it may have been chosen randomly).
249+
let mut longest_path = endpoint.urls.last().unwrap().clone();
250+
while longest_path.methods.len() > 1 {
251+
longest_path.methods.remove(0);
252+
}
253+
254+
// Replace endpoint urls with the longest path
255+
let mut urls = vec![longest_path];
256+
std::mem::swap(&mut endpoint.urls, &mut urls);
257+
258+
let split_desc = split_summary_desc(&endpoint.description);
259+
260+
// Make sure the description is stays at the top
261+
let mut description = match split_desc.summary {
262+
Some(summary) => format!("{summary}\n\n"),
263+
None => String::new(),
264+
};
265+
266+
// Convert removed paths to descriptions
267+
write!(description, "**All methods and paths for this operation:**\n\n")?;
268+
269+
for url in urls {
270+
for method in url.methods {
271+
let lower_method = method.to_lowercase();
272+
let path = &url.path;
273+
write!(
274+
description,
275+
r#"<div>
276+
<span class="operation-verb {lower_method}">{method}</span>
277+
<span class="operation-path">{path}</span>
278+
</div>
279+
"#
280+
)?;
281+
}
282+
}
283+
284+
if let Some(desc) = &split_desc.description {
285+
write!(description, "\n\n{}", desc)?;
286+
}
287+
288+
// Replace description
289+
endpoint.description = description;
290+
291+
// Done
292+
endpoint
293+
} else {
294+
// Not multipath or not asked to merge multipath
295+
endpoint
296+
};
297+
231298
//---- Build a path for each url + method
232299
let mut operation_counter = 0;
233300

@@ -250,9 +317,16 @@ pub fn add_endpoint(
250317
parameters.append(&mut query_params.clone());
251318

252319
let sum_desc = split_summary_desc(&endpoint.description);
320+
321+
let privilege_desc = add_privileges(&endpoint.privileges);
322+
323+
let full_desc = match (sum_desc.description, privilege_desc) {
324+
(Some(a), Some(b)) => Some(a+ &b),
325+
(opt_a, opt_b) => opt_a.or(opt_b)
326+
};
253327

254328
// add the x-state extension for availability
255-
let mut extensions = crate::availability_as_extensions(&endpoint.availability);
329+
let mut extensions = crate::availability_as_extensions(&endpoint.availability, &tac.config.flavor);
256330

257331
// add the x-codeSamples extension
258332
let mut code_samples = vec![];
@@ -268,24 +342,11 @@ pub fn add_endpoint(
268342
}
269343
}
270344
}
271-
if code_samples.is_empty() {
272-
// if there are no example requests we look for example responses
273-
// this can only happen for examples that do not have a request body
274-
if let Some(examples) = response_def.examples.clone() {
275-
if let Some((_, example)) = examples.first() {
276-
let request_line = example.method_request.clone().unwrap_or(String::from(""));
277-
if !request_line.is_empty() {
278-
code_samples.push(serde_json::json!({
279-
"lang": "Console",
280-
"source": request_line + "\n",
281-
}));
282-
}
283-
}
284-
}
285-
}
286345
if !code_samples.is_empty() {
287346
extensions.insert("x-codeSamples".to_string(), serde_json::json!(code_samples));
288347
}
348+
let mut ext_availability = crate::availability_as_extensions(&endpoint.availability, &tac.config.flavor);
349+
extensions.append(&mut ext_availability);
289350

290351
// Create the operation, it will be repeated if we have several methods
291352
let operation = openapiv3::Operation {
@@ -295,7 +356,7 @@ pub fn add_endpoint(
295356
vec![namespace.to_string()]
296357
},
297358
summary: sum_desc.summary,
298-
description: sum_desc.description,
359+
description: full_desc,
299360
external_docs: tac.convert_external_docs(endpoint),
300361
// external_docs: None, // Need values that differ from client purposes
301362
operation_id: None, // set in clone_operation below with operation_counter
@@ -310,7 +371,7 @@ pub fn add_endpoint(
310371
deprecated: endpoint.deprecation.is_some(),
311372
security: None,
312373
servers: vec![],
313-
extensions,
374+
extensions
314375
};
315376

316377

@@ -439,6 +500,26 @@ fn split_summary_desc(desc: &str) -> SplitDesc{
439500
}
440501
}
441502

503+
fn add_privileges(privileges: &Option<Privileges>) -> Option<String>{
504+
if let Some(privs) = privileges {
505+
let mut result = "\n ##Required authorization\n".to_string();
506+
if !privs.index.is_empty() {
507+
result += "* Index privileges: ";
508+
result += &privs.index.iter()
509+
.map(|a| format!("`{a}`"))
510+
.join(",");
511+
}
512+
if !privs.cluster.is_empty() {
513+
result += "* Cluster privileges: ";
514+
result += &privs.cluster.iter()
515+
.map(|a| format!("`{a}`"))
516+
.join(",");
517+
}
518+
return Some(result)
519+
}
520+
None
521+
}
522+
442523
#[derive(PartialEq,Debug)]
443524
struct SplitDesc {
444525
summary: Option<String>,

compiler-rs/clients_schema_to_openapi/src/schemas.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ impl<'a> TypesAndComponents<'a> {
469469
data.external_docs = self.convert_external_docs(prop);
470470
data.deprecated = prop.deprecation.is_some();
471471
data.description = self.property_description(prop)?;
472-
data.extensions = crate::availability_as_extensions(&prop.availability);
472+
data.extensions = crate::availability_as_extensions(&prop.availability, &self.config.flavor);
473473
// TODO: prop.aliases as extensions
474474
// TODO: prop.server_default as extension
475475
// TODO: prop.doc_id as extension (new representation of since and stability)
12.4 KB
Binary file not shown.

0 commit comments

Comments
 (0)