Skip to content

[9.0] Output a redirect map when merging multipath operations (#4545) #4546

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ transform-to-openapi: ## Generate the OpenAPI definition from the compiled schem
@npm run transform-to-openapi -- --schema output/schema/schema.json --flavor serverless --output output/openapi/elasticsearch-serverless-openapi.json

transform-to-openapi-for-docs: ## Generate the OpenAPI definition tailored for API docs generation
@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
@npm run transform-to-openapi -- --schema output/schema/schema.json --flavor stack --lift-enum-descriptions --merge-multipath-endpoints --multipath-redirects --output output/openapi/elasticsearch-openapi-docs.json

filter-for-serverless: ## Generate the serverless version from the compiled schema
@npm run --prefix compiler filter-by-availability -- --serverless --visibility=public --input ../output/schema/schema.json --output ../output/output/openapi/elasticsearch-serverless-openapi.json
Expand Down
17 changes: 17 additions & 0 deletions compiler-rs/clients_schema_to_openapi/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,22 @@ pub struct Cli {
/// merge endpoints with multiple paths into a single OpenAPI operation [default = false]
#[argh(switch)]
pub merge_multipath_endpoints: bool,

/// output a redirection map when merging multipath endpoints
#[argh(switch)]
pub multipath_redirects: bool,
}

impl Cli {
pub fn redirect_path(&self, output: &PathBuf) -> Option<String> {
if self.multipath_redirects {
let path = output.to_string_lossy();
let path = path.rsplit_once('.').unwrap().0;
Some(format!("{}.redirects.csv", path))
} else {
None
}
}
}

use derive_more::FromStr;
Expand All @@ -57,6 +73,7 @@ impl From<Cli> for Configuration {
flavor,
lift_enum_descriptions: cli.lift_enum_descriptions,
merge_multipath_endpoints: cli.merge_multipath_endpoints,
multipath_redirects: cli.multipath_redirects,
namespaces: if cli.namespace.is_empty() {
None
} else {
Expand Down
11 changes: 10 additions & 1 deletion compiler-rs/clients_schema_to_openapi/src/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// specific language governing permissions and limitations
// under the License.

use std::collections::BTreeMap;
use clients_schema::TypeName;
use openapiv3::{Components, Parameter, ReferenceOr, RequestBody, Response, Schema, StatusCode};
use crate::Configuration;
Expand All @@ -32,11 +33,19 @@ pub struct TypesAndComponents<'a> {
pub config: &'a Configuration,
pub model: &'a clients_schema::IndexedModel,
pub components: &'a mut Components,
// Redirections (if paths multipaths endpoints are merged)
pub redirects: Option<BTreeMap<String, String>>,
}

impl<'a> TypesAndComponents<'a> {
pub fn new(config: &'a Configuration, model: &'a clients_schema::IndexedModel, components: &'a mut Components) -> TypesAndComponents<'a> {
TypesAndComponents { config, model, components }
let redirects = if config.merge_multipath_endpoints && config.multipath_redirects {
Some(BTreeMap::new())
} else {
None
};

TypesAndComponents { config, model, components, redirects }
}

pub fn add_request_body(&mut self, endpoint: &str, body: RequestBody) -> ReferenceOr<RequestBody> {
Expand Down
28 changes: 25 additions & 3 deletions compiler-rs/clients_schema_to_openapi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,18 @@ pub struct Configuration {
/// be the longest one (with values for all optional parameters), and the other paths will be added
/// at the beginning of the operation's description.
pub merge_multipath_endpoints: bool,

/// Should we output a redirect map when merging multipath endpoints?
pub multipath_redirects: bool,
}

pub struct OpenApiConversion {
pub openapi: OpenAPI,
pub redirects: Option<String>,
}

/// Convert an API model into an OpenAPI v3 schema, optionally filtered for a given flavor
pub fn convert_schema(mut schema: IndexedModel, config: Configuration) -> anyhow::Result<OpenAPI> {
pub fn convert_schema(mut schema: IndexedModel, config: Configuration) -> anyhow::Result<OpenApiConversion> {
// Expand generics
schema = clients_schema::transform::expand_generics(schema, ExpandConfig::default())?;

Expand Down Expand Up @@ -73,7 +81,7 @@ pub fn convert_schema(mut schema: IndexedModel, config: Configuration) -> anyhow
/// Note: there are ways to represent [generics in JSON Schema], but its unlikely that tooling will understand it.
///
/// [generics in JSON Schema]: https://json-schema.org/blog/posts/dynamicref-and-generics
pub fn convert_expanded_schema(model: &IndexedModel, config: &Configuration) -> anyhow::Result<OpenAPI> {
pub fn convert_expanded_schema(model: &IndexedModel, config: &Configuration) -> anyhow::Result<OpenApiConversion> {
let mut openapi = OpenAPI {
openapi: "3.0.3".into(),
info: info(model),
Expand Down Expand Up @@ -129,7 +137,21 @@ pub fn convert_expanded_schema(model: &IndexedModel, config: &Configuration) ->
// comp.security_schemes.sort_keys();
// }

Ok(openapi)
let redirects = if let Some(redirects) = tac.redirects {
use std::fmt::Write;
let mut result = String::new();
for (source, target) in redirects.iter() {
writeln!(&mut result, "{},{}", source, target)?;
}
Some(result)
} else {
None
};

Ok(OpenApiConversion {
openapi,
redirects,
})
}

fn info(model: &IndexedModel) -> openapiv3::Info {
Expand Down
9 changes: 8 additions & 1 deletion compiler-rs/clients_schema_to_openapi/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,14 @@ fn main() -> anyhow::Result<()> {

let schema = IndexedModel::from_reader(File::open(&cli.schema)?)?;
let output = cli.output.clone();
let redirect_path = cli.redirect_path(&cli.output);
let openapi = clients_schema_to_openapi::convert_schema(schema, cli.into())?;
serde_json::to_writer_pretty(File::create(&output)?, &openapi)?;
serde_json::to_writer_pretty(File::create(&output)?, &openapi.openapi)?;

if let Some(redirects) = openapi.redirects {
let path = redirect_path.unwrap();
std::fs::write(path, &redirects)?;
}

Ok(())
}
31 changes: 21 additions & 10 deletions compiler-rs/clients_schema_to_openapi/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,27 @@ pub fn add_endpoint(
};

//---- Merge multipath endpoints if asked for

let operation_id: String = endpoint
.name
.chars()
.map(|x| match x {
'_' | '.' => '-',
_ => x,
})
.collect();

let mut new_endpoint: clients_schema::Endpoint;

let endpoint = if is_multipath && tac.config.merge_multipath_endpoints {

// Add redirects for operations that would have been generated otherwise
if let Some(ref mut map) = &mut tac.redirects {
for i in 1..endpoint.urls.len() {
map.insert(format!("{operation_id}-{i}"), operation_id.clone());
}
}

new_endpoint = endpoint.clone();
let endpoint = &mut new_endpoint;

Expand Down Expand Up @@ -314,9 +332,9 @@ pub fn add_endpoint(
parameters.append(&mut query_params.clone());

let sum_desc = split_summary_desc(&endpoint.description);

let privilege_desc = add_privileges(&endpoint.privileges);

let full_desc = match (sum_desc.description, privilege_desc) {
(Some(a), Some(b)) => Some(a+ &b),
(opt_a, opt_b) => opt_a.or(opt_b)
Expand Down Expand Up @@ -434,14 +452,7 @@ pub fn add_endpoint(
};

let mut operation = operation.clone();
let mut operation_id: String = endpoint
.name
.chars()
.map(|x| match x {
'_' | '.' => '-',
_ => x,
})
.collect();
let mut operation_id = operation_id.clone();
if operation_counter != 0 {
write!(&mut operation_id, "-{}", operation_counter)?;
}
Expand Down
Binary file modified compiler-rs/compiler-wasm-lib/pkg/compiler_wasm_lib_bg.wasm
Binary file not shown.
9 changes: 8 additions & 1 deletion compiler-rs/compiler-wasm-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,21 @@ pub fn convert0(cli: Cli, cwd: Option<String>) -> anyhow::Result<()> {
Some(ref cwd) => PathBuf::from(cwd).join(&cli.output),
None => cli.output.clone(),
};
let redirect_path = cli.redirect_path(&output);

let json = node_fs::read_file_sync_to_string(&input.to_string_lossy(), "utf8");
let schema = IndexedModel::from_reader(json.as_bytes())?;

let openapi = clients_schema_to_openapi::convert_schema(schema, cli.into())?;

let result = serde_json::to_string_pretty(&openapi)?;
let result = serde_json::to_string_pretty(&openapi.openapi)?;
node_fs::write_file_sync(&output.to_string_lossy(), &result);

if let Some(redirects) = openapi.redirects {
let path = redirect_path.unwrap();
node_fs::write_file_sync(&path, &redirects);
}

Ok(())
}

Expand Down
4 changes: 2 additions & 2 deletions output/openapi/elasticsearch-openapi.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading