Skip to content

Commit 6d3adaa

Browse files
Backport x-codeSamples changes to 8.19 (#4603)
* Add request details to examples (#4445) * add request heading line to request examples * add request heading line to response examples * changes to the compiler to include the request heading * add example requests for endpoints that do not use bodies (#4489) * add a few example requests for endpoints that do not use bodies * Remove x-codeSamples from overlay * restructure existing examples w/o bodies as request examples * skip examples w/o bodies when generating openapi examples * add a lot of missing examples * remove duplicate test * more examples missed in previous commit --------- Co-authored-by: lcawl <lcawley@elastic.co> * add language client examples to YAML files (#4514) * expand model to include language versions of examples * add language client examples to YAML files * use $ELASTICSEARCH_URL in curl examples * remove unnecessary generate-language-examples task from the openapi generation * upgrade request converter to latest * Only add x-codeSamples extensions to docs builds of openapi (#4582) * address example errors --------- Co-authored-by: lcawl <lcawley@elastic.co>
1 parent 968a9d6 commit 6d3adaa

File tree

674 files changed

+58263
-1099
lines changed

Some content is hidden

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

674 files changed

+58263
-1099
lines changed

Makefile

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ setup: ## Install dependencies for contrib target
4040
@npm install --prefix compiler
4141
@npm install --prefix typescript-generator
4242
@npm install @redocly/cli
43+
@npm install --prefix docs/examples
4344

4445
clean-dep: ## Clean npm dependencies
4546
@rm -rf compiler/node_modules
@@ -53,7 +54,9 @@ transform-to-openapi: ## Generate the OpenAPI definition from the compiled schem
5354
@npm run transform-to-openapi -- --schema output/schema/schema.json --flavor serverless --output output/openapi/elasticsearch-serverless-openapi.json
5455

5556
transform-to-openapi-for-docs: ## Generate the OpenAPI definition tailored for API docs generation
56-
@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
57+
@make generate-language-examples
58+
@make generate
59+
@npm run transform-to-openapi -- --schema output/schema/schema.json --flavor stack --lift-enum-descriptions --merge-multipath-endpoints --multipath-redirects --include-language-examples --output output/openapi/elasticsearch-openapi-docs.json
5760

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
@@ -67,6 +70,10 @@ overlay-docs: ## Apply overlays to OpenAPI documents
6770
@npx @redocly/cli bundle output/openapi/elasticsearch-openapi.tmp2.json --ext json -o output/openapi/elasticsearch-openapi.examples.json
6871
rm output/openapi/elasticsearch-openapi.tmp*.json
6972

73+
generate-language-examples:
74+
@node docs/examples/generate-language-examples.js
75+
@npm run format:fix-examples --prefix compiler
76+
7077
lint-docs: ## Lint the OpenAPI documents after overlays
7178
@npx @redocly/cli lint "output/openapi/elasticsearch-*.json" --config "docs/linters/redocly.yaml" --format stylish --max-problems 500
7279

compiler-rs/clients_schema/src/lib.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,19 +484,28 @@ impl TypeDefinition {
484484

485485
/// The Example type is used for both requests and responses.
486486
///
487-
/// This type definition is taken from the OpenAPI spec
487+
/// This type definition is based on the OpenAPI spec
488488
/// https://spec.openapis.org/oas/v3.1.0#example-object
489-
/// with the exception of using String as the 'value' type.
489+
/// with the exception of using String as the 'value' type,
490+
/// and some custom additions.
490491
///
491492
/// The OpenAPI v3 spec also defines the 'Example' type, so
492493
/// to distinguish them, this type is called SchemaExample.
493494
495+
#[derive(Debug, Clone, Serialize, Deserialize)]
496+
pub struct ExampleAlternative {
497+
pub language: String,
498+
pub code: String,
499+
}
500+
494501
#[derive(Debug, Clone, Serialize, Deserialize)]
495502
pub struct SchemaExample {
496503
pub summary: Option<String>,
504+
pub method_request: Option<String>,
497505
pub description: Option<String>,
498506
pub value: Option<String>,
499507
pub external_value: Option<String>,
508+
pub alternatives: Option<Vec<ExampleAlternative>>,
500509
}
501510

502511
/// Common attributes for all type definitions

compiler-rs/clients_schema_to_openapi/src/cli.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ pub struct Cli {
3535
/// output a redirection map when merging multipath endpoints
3636
#[argh(switch)]
3737
pub multipath_redirects: bool,
38+
39+
/// include the x-codeSamples extension with language examples for all endpoints
40+
#[argh(switch)]
41+
pub include_language_examples: bool,
3842
}
3943

4044
impl Cli {
@@ -74,6 +78,7 @@ impl From<Cli> for Configuration {
7478
lift_enum_descriptions: cli.lift_enum_descriptions,
7579
merge_multipath_endpoints: cli.merge_multipath_endpoints,
7680
multipath_redirects: cli.multipath_redirects,
81+
include_language_examples: cli.include_language_examples,
7782
namespaces: if cli.namespace.is_empty() {
7883
None
7984
} else {

compiler-rs/clients_schema_to_openapi/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ pub struct Configuration {
4444

4545
/// Should we output a redirect map when merging multipath endpoints?
4646
pub multipath_redirects: bool,
47+
48+
/// include the x-codeSamples extension with language examples for all endpoints
49+
pub include_language_examples: bool,
4750
}
4851

4952
pub struct OpenApiConversion {

compiler-rs/clients_schema_to_openapi/src/paths.rs

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -148,14 +148,16 @@ pub fn add_endpoint(
148148
// }
149149
};
150150

151-
let openapi_example = Example {
152-
value: example,
153-
description: schema_example.description.clone(),
154-
summary: schema_example.summary.clone(),
155-
external_value: None,
156-
extensions: Default::default(),
157-
};
158-
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+
}
159161
}
160162
openapi_examples
161163
}
@@ -202,7 +204,7 @@ pub fn add_endpoint(
202204
// If this endpoint response has examples in schema.json, convert them to the
203205
// OpenAPI format and add them to the endpoint response in the OpenAPI document.
204206
let response_examples = if let Some(examples) = &response_def.examples {
205-
get_openapi_examples(examples)
207+
get_openapi_examples(examples)
206208
} else {
207209
IndexMap::new()
208210
};
@@ -343,8 +345,34 @@ pub fn add_endpoint(
343345

344346
// add the x-state extension for availability
345347
let mut extensions = crate::availability_as_extensions(&endpoint.availability, &tac.config.flavor);
346-
let mut ext_availability = crate::availability_as_extensions(&endpoint.availability, &tac.config.flavor);
347-
extensions.append(&mut ext_availability);
348+
349+
if tac.config.include_language_examples {
350+
// add the x-codeSamples extension
351+
let mut code_samples = vec![];
352+
if let Some(examples) = request.examples.clone() {
353+
if let Some((_, example)) = examples.first() {
354+
let request_line = example.method_request.clone().unwrap_or(String::from(""));
355+
let request_body = example.value.clone().unwrap_or(String::from(""));
356+
if !request_line.is_empty() {
357+
code_samples.push(serde_json::json!({
358+
"lang": "Console",
359+
"source": request_line + "\n" + request_body.as_str(),
360+
}));
361+
}
362+
if let Some(alternatives) = example.alternatives.clone() {
363+
for alternative in alternatives.iter() {
364+
code_samples.push(serde_json::json!({
365+
"lang": alternative.language,
366+
"source": alternative.code.as_str(),
367+
}));
368+
}
369+
}
370+
}
371+
}
372+
if !code_samples.is_empty() {
373+
extensions.insert("x-codeSamples".to_string(), serde_json::json!(code_samples));
374+
}
375+
}
348376

349377
// Create the operation, it will be repeated if we have several methods
350378
let operation = openapiv3::Operation {
@@ -369,7 +397,7 @@ pub fn add_endpoint(
369397
deprecated: endpoint.deprecation.is_some(),
370398
security: None,
371399
servers: vec![],
372-
extensions
400+
extensions,
373401
};
374402

375403

Binary file not shown.

compiler/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"lint:fix": "ts-standard --fix src",
99
"format:check": "prettier --config .prettierrc.json --loglevel warn --check ../specification/",
1010
"format:fix": "prettier --config .prettierrc.json --loglevel warn --write ../specification/",
11+
"format:fix-examples": "prettier --config .prettierrc.json --loglevel warn --write ../specification/**/*.yaml",
1112
"generate-schema": "ts-node src/index.ts",
1213
"transform-expand-generics": "ts-node src/transform/expand-generics.ts",
1314
"transform-to-openapi": "ts-node src/transform/schema-to-openapi.ts",

compiler/src/model/metamodel.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,14 @@ export class Interface extends BaseType {
260260
variants?: Container
261261
}
262262

263+
/**
264+
* An alternative of an example, coded in a given language.
265+
*/
266+
export class ExampleAlternative {
267+
language: string
268+
code: string
269+
}
270+
263271
/**
264272
* The Example type is used for both requests and responses
265273
* This type definition is taken from the OpenAPI spec
@@ -271,10 +279,14 @@ export class Example {
271279
summary?: string
272280
/** Long description. */
273281
description?: string
282+
/** request method and URL */
283+
method_request?: string
274284
/** Embedded literal example. Mutually exclusive with `external_value` */
275285
value?: string
276286
/** A URI that points to the literal example */
277287
external_value?: string
288+
/** An array of alternatives for this example in other languages */
289+
alternatives?: ExampleAlternative[]
278290
}
279291

280292
/**
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
const fs = require('fs');
21+
const path = require('path');
22+
const { parseDocument: yamlParseDocument } = require('yaml');
23+
const { convertRequests, loadSchema } = require('@elastic/request-converter');
24+
25+
const LANGUAGES = ['Python', 'JavaScript', 'Ruby', 'PHP', 'curl'];
26+
27+
async function generateLanguages(example) {
28+
const doc = yamlParseDocument(await fs.promises.readFile(example, 'utf8'));
29+
const data = doc.toJS();
30+
let request = data.method_request;
31+
if (data.value) {
32+
if (typeof data.value === 'string') {
33+
request += '\n' + data.value;
34+
}
35+
else {
36+
request += '\n' + JSON.stringify(data.value);
37+
}
38+
}
39+
const alternatives = [];
40+
for (const lang of LANGUAGES) {
41+
alternatives.push({
42+
language: lang,
43+
code: (await convertRequests(request, lang, {})).trim(),
44+
});
45+
}
46+
data.alternatives = alternatives.concat((data.alternatives ?? []).filter(pair => !LANGUAGES.includes(pair.language)));
47+
doc.delete('alternatives');
48+
doc.add(doc.createPair('alternatives', data.alternatives));
49+
await fs.promises.writeFile(example, doc.toString({lineWidth: 132}));
50+
}
51+
52+
async function* walkExamples(dir) {
53+
for await (const d of await fs.promises.opendir(dir)) {
54+
const entry = path.join(dir, d.name);
55+
if (d.isDirectory()) {
56+
yield* walkExamples(entry);
57+
}
58+
else if (d.isFile() && entry.includes('/examples/request/') && entry.endsWith('.yaml')) {
59+
yield entry;
60+
}
61+
}
62+
}
63+
64+
async function main() {
65+
let count = 0;
66+
let errors = 0;
67+
await loadSchema('output/schema/schema.json');
68+
for await (const example of walkExamples('./specification/')) {
69+
try {
70+
await generateLanguages(example);
71+
}
72+
catch (err) {
73+
console.log(`${example}: ${err}`);
74+
errors += 1;
75+
}
76+
count += 1;
77+
}
78+
console.log(`${count} examples processed, ${errors} errors.`);
79+
}
80+
81+
main();

0 commit comments

Comments
 (0)