Skip to content
23 changes: 23 additions & 0 deletions compiler-rs/clients_schema/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,25 @@ impl TypeDefinition {
}
}

/**
* The Example type is used for both requests and responses
* This type definition is taken from the OpenAPI spec
* https://spec.openapis.org/oas/v3.1.0#example-object
* With the exception of using String as the 'value' type.
* This type matches the 'Example' type in metamodel.ts. The
* data serialized by the Typescript code in schema.json,
* needs to be deserialized into this equivalent type.
* The OpenAPI v3 spec also defines the 'Example' type, so
* to distinguish them, this type is called SchemaExample.
*/
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SchemaExample {
pub summary: Option<String>,
pub description: Option<String>,
pub value: Option<String>,
pub external_value: Option<String>,
}

/// Common attributes for all type definitions
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -675,6 +694,8 @@ pub struct Request {

#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub attached_behaviors: Vec<String>,

pub examples: Option<IndexMap<String, SchemaExample>>
}

impl WithBaseType for Request {
Expand Down Expand Up @@ -703,6 +724,8 @@ pub struct Response {

#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub exceptions: Vec<ResponseException>,

pub examples: Option<IndexMap<String, SchemaExample>>
}

impl WithBaseType for Response {
Expand Down
43 changes: 40 additions & 3 deletions compiler-rs/clients_schema_to_openapi/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@ use std::fmt::Write;

use anyhow::{anyhow, bail};
use clients_schema::Property;
use indexmap::IndexMap;
use indexmap::indexmap;
use icu_segmenter::SentenceSegmenter;
use openapiv3::{
MediaType, Parameter, ParameterData, ParameterSchemaOrContent, PathItem, PathStyle, Paths, QueryStyle, ReferenceOr,
RequestBody, Response, Responses, StatusCode,
RequestBody, Response, Responses, StatusCode, Example
};
use clients_schema::SchemaExample;
use serde_json::json;

use crate::components::TypesAndComponents;

Expand Down Expand Up @@ -116,15 +119,42 @@ pub fn add_endpoint(

//---- Prepare request body

// This function converts the IndexMap<String, SchemaExample> examples of
// schema.json to IndexMap<String, ReferenceOr<Example>> which is the format
// that OpenAPI expects.
fn get_openapi_examples(schema_examples: IndexMap<String, SchemaExample>) -> IndexMap<String, ReferenceOr<Example>> {
let mut openapi_examples = indexmap! {};
for (name, schema_example) in schema_examples {
let openapi_example = Example {
value: Some(json!(schema_example.value)),
description: schema_example.description.clone(),
summary: schema_example.summary.clone(),
external_value: None,
extensions: Default::default(),
};
openapi_examples.insert(name.clone(), ReferenceOr::Item(openapi_example));
}
return openapi_examples;
}


let mut request_examples: IndexMap<String, ReferenceOr<Example>> = indexmap! {};
// If this endpoint request has examples in schema.json, convert them to the
// OpenAPI format and add them to the endpoint request in the OpenAPI document.
if request.examples.is_some() {
request_examples = get_openapi_examples(request.examples.as_ref().unwrap().clone());
}

let request_body = tac.convert_request(request)?.map(|schema| {
let media = MediaType {
schema: Some(schema),
example: None,
examples: Default::default(),
examples: request_examples,
encoding: Default::default(),
extensions: Default::default(),
};


let body = RequestBody {
description: None,
// FIXME: nd-json requests
Expand All @@ -142,17 +172,24 @@ pub fn add_endpoint(

//---- Prepare request responses


// FIXME: buggy for responses with no body
// TODO: handle binary responses
let response_def = tac.model.get_response(endpoint.response.as_ref().unwrap())?;
let mut response_examples: IndexMap<String, ReferenceOr<Example>> = indexmap! {};
// If this endpoint response has examples in schema.json, convert them to the
// OpenAPI format and add them to the endpoint response in the OpenAPI document.
if response_def.examples.is_some() {
response_examples = get_openapi_examples(response_def.examples.as_ref().unwrap().clone());
}
let response = Response {
description: "".to_string(),
headers: Default::default(),
content: indexmap! {
"application/json".to_string() => MediaType {
schema: tac.convert_response(response_def)?,
example: None,
examples: Default::default(),
examples: response_examples,
encoding: Default::default(),
extensions: Default::default(),
}
Expand Down
Binary file modified compiler-rs/compiler-wasm-lib/pkg/compiler_wasm_lib_bg.wasm
Binary file not shown.
5 changes: 5 additions & 0 deletions compiler/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import validateModel from './steps/validate-model'
import addContentType from './steps/add-content-type'
import readDefinitionValidation from './steps/read-definition-validation'
import addDeprecation from './steps/add-deprecation'
import ExamplesProcessor from './steps/add-examples'

const nvmrc = readFileSync(join(__dirname, '..', '..', '.nvmrc'), 'utf8')
const nodejsMajor = process.version.split('.').shift()?.slice(1) ?? ''
Expand Down Expand Up @@ -65,6 +66,9 @@ if (outputFolder === '' || outputFolder === undefined) {

const compiler = new Compiler(specsFolder, outputFolder)

const examplesProcessor = new ExamplesProcessor(specsFolder)
const addExamples = examplesProcessor.addExamples.bind(examplesProcessor)

compiler
.generateModel()
.step(addInfo)
Expand All @@ -74,6 +78,7 @@ compiler
.step(validateRestSpec)
.step(addDescription)
.step(validateModel)
.step(addExamples)
.write()
.then(() => {
console.log('Done')
Expand Down
15 changes: 15 additions & 0 deletions compiler/src/model/metamodel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,19 @@ export class Interface extends BaseType {
variants?: Container
}

/**
* The Example type is used for both requests and responses
* This type definition is taken from the OpenAPI spec
* https://spec.openapis.org/oas/v3.1.0#example-object
* With the exception of using String as the 'value' type
*/
export class Example {
summary?: string
description?: string
value?: string
external_value?: string
}

/**
* A request type
*/
Expand Down Expand Up @@ -288,6 +301,7 @@ export class Request extends BaseType {
body: Body
behaviors?: Behavior[]
attachedBehaviors?: string[]
examples?: Map<string, Example>
}

/**
Expand All @@ -300,6 +314,7 @@ export class Response extends BaseType {
behaviors?: Behavior[]
attachedBehaviors?: string[]
exceptions?: ResponseException[]
examples?: Map<string, Example>
}

export class ResponseException {
Expand Down
Loading