Skip to content

Commit

Permalink
docs: ensure doc consistancy and correctness about processing (#87)
Browse files Browse the repository at this point in the history
Co-authored-by: Sergio Moya <1083296+smoya@users.noreply.github.com>
  • Loading branch information
jonaslagoni and smoya committed Feb 25, 2021
1 parent 5948aab commit 4d26d1a
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 70 deletions.
69 changes: 67 additions & 2 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<dd><p>This class is the wrapper for simplified models and the rest of the context needed for further generate typed models.</p>
</dd>
<dt><a href="#CommonModel">CommonModel</a> ⇐ <code><a href="#CommonModel">CommonSchema&lt;CommonModel&gt;</a></code></dt>
<dd><p>Common representation for the renderers.</p>
<dd><p>Common internal representation for a model.</p>
</dd>
<dt><a href="#CommonSchema">CommonSchema</a></dt>
<dd><p>CommonSchema which contains the common properties between Schema and CommonModel</p>
Expand All @@ -22,6 +22,9 @@
<dt><a href="#Schema">Schema</a> ⇐ <code><a href="#Schema">CommonSchema&lt;Schema&gt;</a></code></dt>
<dd><p>JSON Schema Draft 7 model</p>
</dd>
<dt><a href="#AsyncAPIInputProcessor">AsyncAPIInputProcessor</a></dt>
<dd><p>Class for processing AsyncAPI inputs</p>
</dd>
<dt><a href="#InputProcessor">InputProcessor</a></dt>
<dd><p>Main input processor which figures out the type of input it receives and delegates the processing into separate individual processors.</p>
</dd>
Expand Down Expand Up @@ -125,10 +128,25 @@ This class is the wrapper for simplified models and the rest of the context need
<a name="CommonModel"></a>

## CommonModel ⇐ [<code>CommonSchema&lt;CommonModel&gt;</code>](#CommonModel)
Common representation for the renderers.
Common internal representation for a model.

**Kind**: global class
**Extends**: [<code>CommonSchema&lt;CommonModel&gt;</code>](#CommonModel)
**Properties**

| Name | Type | Description |
| --- | --- | --- |
| $id | <code>string</code> | define the id/name of the model. |
| type | <code>string</code> \| <code>Array.&lt;string&gt;</code> | this is the different types for the model. All types from JSON Schema are used with no custom ones added. |
| enum | <code>Array.&lt;any&gt;</code> | defines the different enums for the model, constant values are included here |
| items | [<code>CommonModel</code>](#CommonModel) \| [<code>Array.&lt;CommonModel&gt;</code>](#CommonModel) | defines the type for `array` models as `CommonModel`. |
| properties | <code>Record.&lt;string, CommonModel&gt;</code> | defines the properties and its expected types as `CommonModel`. |
| additionalProperties | [<code>CommonModel</code>](#CommonModel) | are used to define if any extra properties are allowed, also defined as a `CommonModel`. |
| $ref | <code>string</code> | is a reference to another `CommonModel` by using`$id` as a simple string. |
| required | <code>Array.&lt;string&gt;</code> | list of required properties. |
| extend | <code>Array.&lt;string&gt;</code> | list of other `CommonModel`s this model extends, is an array of `$id` strings. |
| originalSchema | [<code>Schema</code>](#Schema) \| <code>boolean</code> | the actual input for which this model represent. |


* [CommonModel](#CommonModel)[<code>CommonSchema&lt;CommonModel&gt;</code>](#CommonModel)
* _instance_
Expand Down Expand Up @@ -265,6 +283,53 @@ Transform object into a type of Schema.
| --- | --- |
| object | to transform |

<a name="AsyncAPIInputProcessor"></a>

## AsyncAPIInputProcessor
Class for processing AsyncAPI inputs

**Kind**: global class

* [AsyncAPIInputProcessor](#AsyncAPIInputProcessor)
* _instance_
* [.process(input)](#AsyncAPIInputProcessor+process)
* [.shouldProcess(input)](#AsyncAPIInputProcessor+shouldProcess)
* _static_
* [.isFromParser(input)](#AsyncAPIInputProcessor.isFromParser)

<a name="AsyncAPIInputProcessor+process"></a>

### asyncAPIInputProcessor.process(input)
Process the input as an AsyncAPI document

**Kind**: instance method of [<code>AsyncAPIInputProcessor</code>](#AsyncAPIInputProcessor)

| Param |
| --- |
| input |

<a name="AsyncAPIInputProcessor+shouldProcess"></a>

### asyncAPIInputProcessor.shouldProcess(input)
Figures out if an object is of type AsyncAPI document

**Kind**: instance method of [<code>AsyncAPIInputProcessor</code>](#AsyncAPIInputProcessor)

| Param |
| --- |
| input |

<a name="AsyncAPIInputProcessor.isFromParser"></a>

### AsyncAPIInputProcessor.isFromParser(input)
Figure out if input is from our parser.

**Kind**: static method of [<code>AsyncAPIInputProcessor</code>](#AsyncAPIInputProcessor)

| Param |
| --- |
| input |

<a name="InputProcessor"></a>

## InputProcessor
Expand Down
40 changes: 28 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,21 @@ Run this command to install the SDK in your project:
```bash
npm install --save @asyncapi/generator-model-sdk
```

## How it works

The process of creating data models from input data consists of three main process: transformation input data to JSON Schema, simplification and generation.

### The transformation process

The transformation process starts from checking what type of input data is provided and based on that it is processed differently. Currently JSON Schema Draft 7 and AsyncAPI version 2.0.0 are supported. It is also in this stage that references are resolved and schemas are named. Each input data must be converted to JSON Schema or be a subset of JSON Schema (like AsyncAPI spec) to go to the next process - the simplification stage.
The process of creating data models from input data consists of 2 processes, the input and generation process.

### The simplification process
### The input process

In order to simplify the model generation process as much as possible, AsyncAPI Model SDK simplifies the transformed JSON schema into `CommonModel`s, which contain information about the type of model it is, what properties it might have, etc, so finally we have a bare minimum schema. Think of it like converting data validation rules (JSON Schema) to a data definition.
The input process ensures that any supported input is handled correctly, the basics are that any input needs to be converted into our internal model representation `CommonModel`. The following inputs are supported:
- [JSON Schema Draft 7](#JSON-Schema-input), this is the default inferred input if we cannot find a another input processor.
- [AsyncAPI version 2.0.0](#AsyncAPI-input)

Read [this](./docs/simplification.md) document for more information.
Read more about the input process [here](./docs/input_processing.md).

### The generatiion process
### The generation process

The generation process uses the predefined `CommonModel`s from the simplification stage to easily generate models. The AsyncAPI Model SDK generator support the following languages:
The generation process uses the predefined `CommonModel`s from the input stage to easily generate models regardless of input. The generator support the following output languages:

- JavaScript
- TypeScript
Expand Down Expand Up @@ -91,7 +88,7 @@ const doc = {

const interfaceModel = await generator.generate(doc);

// interfaceModel should have the shape:
// interfaceModel[0] should have the shape:
interface Address {
streetName: string;
city: string;
Expand All @@ -102,6 +99,25 @@ interface Address {
state?: "Texas" | "Alabama" | "California" | "other";
}
```
## Supported input
These are the supported inputs.
### AsyncAPI input
The library expects the `asyncapi` property for the document to be sat in order to understand the input format.
- Generate from a [parsed AsyncAPI document](https://github.com/asyncapi/parser-js)
```js
const parser = require('@asyncapi/parser');
const doc = await parser.parse(`{asyncapi: '2.0.0'}`);
generator.generate(doc);
```
- Generate from a pure JS object
```js
generator.generate({asyncapi: '2.0.0'});
```
### JSON Schema input
- Generate from a pure JS object
```js
generator.generate({$schema: 'http://json-schema.org/draft-07/schema#'});
```

## Development

Expand Down
Binary file added docs/assets/class_diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions docs/input_processing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Input processing
The input process is about processing any input into our internal model representation `CommonInputModel`. This most likely have to be done using different methods based on which kind of input it is.

As of now two inputs are supported:
- JSON Schema Draft 7
- AsyncAPI version 2.0.0

## Internal model representation

![Class diagram](./assets/class_diagram.png)

As seen on the class diagram the `InputProcessor` is our main point of entry for processing input data.

It uses the defined input processors (`AsyncAPIInputProcessor`, `JsonSchemaInputProcessor`, ...) by first calling `shouldProcess` function of each and if the function returns true it calls the `process` function.

If no processes returns true it defaults to `JsonSchemaInputProcessor`.

The `process` function are expected to return `CommonInputModel` which is a wrapper for the core data representation of `CommonModel`.

This is done to ensure we can return multiple models for any input to allow for references, inheritance etc.

As said the core internal representation of a data model is `CommonModel`. This contains the data definition by using known keywords from JSON Schema, but instead of it representing a validation rules it represent data definition. The explanation for the `CommonModel` properties can be found [here](../API.md#CommonModel).
## AsyncAPI
At the moment the library only supports the whole AsyncAPI file as input where it generates models for all defined message payloads. If any other kind of AsyncAPI input is wanted please create a [feature request](https://github.com/asyncapi/generator-model-sdk/issues/new?assignees=&labels=enhancement&template=enhancement.md)!

The AsyncAPI input processor expects that the property `asyncapi` is defined in order to know it should be processed using this.

The payload, since it is of type JSON Schema, is then passed to the [JSON Schema processor](#JSON-Schema) which handle the rest of the processing.


## JSON Schema
For us to convert JSON Schema into `CommonInputModel` we use a process we call the simplification process. This means that we simplify data validation rules (`Schema` or Boolean) into data definitions (`CommonModel`). This process is quite complex and needs it own section for explaining how it works.

Read [this](./docs/simplification.md) document for more information.
85 changes: 43 additions & 42 deletions docs/simplification.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# The simplification process
# Simplifying JSON Schema to CommonModel

In order to simplify the model rendering process as much as possible, AsyncAPI Model SDK simplify transformed JSON schema into CommonModels, which contain information what type of model is, what properties single model has, etc, so finally we have a bare minimum schema.
The library simplify the JSON Schema from data validation rules to data definitions (`CommonModel`(s)). We do this because JSON Schema files can be extremely complex and can in many cases be simplified down to a bare minimum representation. Now keep in mind that there is a difference between validating input and the underlying structure of the data. In our case we do not care directly about the validation of input but rather how we display the underlying structure correctly.

The AsyncAPI Model SDK supports simplification of:
This document is expected to be a supplement to the code to better understand how the simplifier works.

## Simplifier
The main functionality is located in the `Simplifier` class. This class ensures to recursively create (or retrieve from a cache) a `CommonModel` representation of a Schema. We have tried to keep the functionality split out into separate functions to reduce complexity and ensure it is easier to maintain. This main function also ensures to split any created models into separate ones if needed.


To determine the different properties of `CommonModel` each property are split into separate functions:

- [Types](#determining-the-type-for-the-model)
- [Enums](#determining-the-enums-for–the-model)
Expand All @@ -14,87 +20,79 @@ The AsyncAPI Model SDK supports simplification of:

## Determining the type for the model

In order to determine all the possible types a schema can be, we both infer and use existing definitions of types, however we need to define a precedence for JSON Schema keywords for in which order they types are inferred or determined.
In order to determine all the possible types for a model, we both infer and use existing definitions of types.
Precedence for JSON Schema keywords needs to be predetermined to ensure consistency.
### Precedence of JSON Schema keywords

### Precedence
Notice it goes from left to right meaning first we set the type using the `type` keyword and then it moves right applying them one by one. The following are the precedence for determining types:

The precedence of keywords are in which order we infer or determine types in. Notice it goes form left to right meaning first we set the type using the `type` keyword and then it moves right applying them one by one. The following are the precedence for determining types:
<p align="center">type --> allOf --> oneOf --> anyOf --> then --> else --> enum --> const --> not</p>

`type` --> `allOf` --> `oneOf` --> `anyOf` --> `then` --> `else` --> `enum` --> `const` --> `not`
### Absence of data type

#### Absence of data type
If a schema is defined as `true` or `false` we infer all possible JSON Schema types.

If a schema is defined as `true` or `false` we infer all possible JSON types.

#### Enum and const
### Enum and const

`enum` and `const` are ONLY used to infer the type if it has not already been defined. Type is inferred from the provided value(s).

#### Not

`not` if defined always overwrites any existing inference of types.
### Not

#### Non-mentioned keywords
If defined, removes any defined/inferred types defined in `not`.

Any keywords not mentioned as a title are all cumulating the type.
<br/>

## Determining the enums for the model

In order to determine all the possible enums a schema can be we both infer and use existing definitions of types, however we need to define a precedence for JSON Schema keywords and in which order they are applied.
In order to determine the possible enums a model can be we both infer and use existing definitions, however we need to define a precedence for JSON Schema keywords and in which order they are applied.

### Precedence
### Precedence of JSON Schema keywords

The precedence of keywords are in which order we infer or determine the `enum` value in, the following are the precedence for determining enums:
The following are precedence of keywords in which order we infer or determine the `enum` in:

`enum` --> `allOf` --> `oneOf` --> `anyOf` --> `then` --> `else` --> `const` --> `not`
<p align="center">enum --> allOf --> oneOf --> anyOf --> then --> else --> const --> not</p>

#### Const
### Const

`const` if defined it always overwrites any already determined enums.

#### Not
### Not

`not` if defined it removes any matching enums.

#### Non-mentioned keywords

Any keywords not mentioned as a title are all cumulating the enum values.
<br/>

## Determining the items for the model

In order to determine all the possible items a schema can be, we both infer and use existing definitions, however we need to define a precedence for JSON Schema keywords for in which order the items are inferred or determined.

### Precedence
### Precedence of JSON Schema keywords

The precedence of keywords are in which order we infer or determine items in. The following are the precedence for determining types:

`items` --> `allOf` --> `oneOf` --> `anyOf` --> `then` --> `else`
<p align="center">items --> allOf --> oneOf --> anyOf --> then --> else</p>

#### Items
### Items

All items are recursively simplified using the main simplification wrapper `simplifyRecursive` which ensures if it come across a schema of type object it splits them out into a reference and new instance of the common model.

#### Non-mentioned keywords

Any keywords not mentioned as a title are all cumulating the items.
<br/>

## Determining the properties for the model

In order to determine all the possible properties a schema can be, we both infer and use existing definitions, however we need to define a precedence for JSON Schema keywords for in which order the properties are inferred or determined.

### Precedence
### Precedence of JSON Schema keywords

The precedence of keywords are in which order we infer or determine properties in. The following are the precedence for determining types:

`properties` --> `allOf` --> `oneOf` --> `anyOf` --> `then` --> `else`
<p align="center">properties --> allOf (Only if we don't want inheritance) --> oneOf --> anyOf --> then --> else</p>

#### Properties
### Properties

All properties are recursively simplified using the main simplification wrapper `simplifyRecursive` which ensures if it come across a schema of type object it splits them out into a reference and new instance of the common model.

#### Non-mentioned keywords

Any keywords not mentioned as a title are all cumulating the properties.
<br/>

## Determining the additionalProperties for the model

Expand All @@ -103,18 +101,21 @@ Additional properties are determined by the following form:
1. Incase it is `undefined` or `false` the `additionalProperties` is set to `undefined`. This is because undefined are easier to handle in the rendering phase then if `additionalProperties` could be `undefined` or `false`.
2. Otherwise recursively simplify the `additionalProperties` schema/boolean.

<br/>

## Determining the required properties for the model

In order to determine all the possible required properties a schema can have, we both merge and use existing definitions, however we need to define a precedence for JSON Schema keywords for in which order the required are merged or determined.

### Precedence
### Precedence of JSON Schema keywords

The precedence of keywords are in which order we merge or determine `required` in. The following are the precedence for determining the array of required:

`required` --> `allOf` --> `oneOf` --> `anyOf` --> `then` --> `else`
<p align="center">required --> allOf --> oneOf --> anyOf --> then --> else</p>

## Determining the extend for the model
<br/>

The `extend` keyword is one of the few keywords not originally from the JSON Schema specification. This keyword is used for when an object needs to extend another, where the name of the other `CommonModel` is used. Because of the nature of JSON Schema (`allOf` being an array) this extend keyword is an array of strings.
## Determining the extend for the model
This simplifier is only used to inheritance is wanted.

The simplification process determines the `extend` keyword based on the `allOf` keyword, where it iterates over all schemas and recursively simplifies each. If iterated simplified schema is of type object we add it to the `extend` list.
The simplification process determines the `extend` keyword based on the `allOf` keyword, where it iterates over the schemas and recursively simplifies each. If simplified model is of type object we add it to the `extend` list.
12 changes: 11 additions & 1 deletion src/models/CommonModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,19 @@ import { CommonSchema } from './CommonSchema';
import { Schema } from './Schema';

/**
* Common representation for the renderers.
* Common internal representation for a model.
*
* @extends CommonSchema<CommonModel>
* @property {string} $id define the id/name of the model.
* @property {string | string[]} type this is the different types for the model. All types from JSON Schema are used with no custom ones added.
* @property {any[]} enum defines the different enums for the model, constant values are included here
* @property {CommonModel | CommonModel[]} items defines the type for `array` models as `CommonModel`.
* @property {Record<string, CommonModel>} properties defines the properties and its expected types as `CommonModel`.
* @property {CommonModel} additionalProperties are used to define if any extra properties are allowed, also defined as a `CommonModel`.
* @property {string} $ref is a reference to another `CommonModel` by using`$id` as a simple string.
* @property {string[]} required list of required properties.
* @property {string[]} extend list of other `CommonModel`s this model extends, is an array of `$id` strings.
* @property {Schema | boolean} originalSchema the actual input for which this model represent.
*/
export class CommonModel extends CommonSchema<CommonModel> {
extend?: string[];
Expand Down
Loading

0 comments on commit 4d26d1a

Please sign in to comment.