Skip to content
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

fix: disclose limitation about parameter schema reference #192

Merged
merged 11 commits into from
Nov 7, 2023
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ try {

> **NOTE**: This feature is still WIP, and is until the final release of `3.0.0`.

Conversion to version `3.x.x` from `2.x.x` has several assumptions that should be know before converting:
Conversion to version `3.x.x` from `2.x.x` has several assumptions that should be known before converting:

- The input must be valid AsyncAPI document.
- External references are not resolved and converted, they remain untouched, even if they are incorrect.
Expand Down Expand Up @@ -164,6 +164,35 @@ Conversion to version `3.x.x` from `2.x.x` has several assumptions that should b
## Known missing features

* When converting from 1.x to 2.x, Streaming APIs (those using `stream` instead of `topics` or `events`) are converted correctly but information about framing type and delimiter is missing until a [protocolInfo](https://github.com/asyncapi/extensions-catalog/issues/1) for that purpose is created.
* When converting from 2.x to 3.x, and `parameter.schema` is defined with a reference, it will NOT look into the schema reference and include any relevant keywords for the v3 parameter. It will just create an empty parameter but leave the schema in the components section as is.
```yaml
# 2.x.x
channels:
"{myParameter}":
parameters:
myParameter:
schema:
$ref: "#/components/schemas/mySchema"
components:
schemas:
mySchema:
enum: ["test"]
default: "test"
examples: ["test"]

# 3.0.0
channels:
"{myParameter}":
parameters:
myParameter: {}

components:
schemas:
mySchema:
enum: ["test"]
default: "test"
examples: ["test"]
```

## Development

Expand Down
41 changes: 34 additions & 7 deletions src/third-version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ function from__2_6_0__to__3_0_0(asyncapi: AsyncAPIDocument, options: ConvertOpti
useChannelIdExtension: true,
convertServerComponents: true,
convertChannelComponents: true,
failOnParameterReference: false,
...(options.v2tov3 ?? {}),
} as RequiredConvertV2ToV3Options;
v2tov3Options.idGenerator = v2tov3Options.idGenerator || idGeneratorFactory(v2tov3Options);
Expand Down Expand Up @@ -376,6 +377,7 @@ function convertParameters(parameters: Record<string, any>): Record<string, any>
});
return newParameters;
}

/**
* Convert the old v2 parameter object to v3.
*
Expand All @@ -391,11 +393,18 @@ function convertParameter(parameter: any): any {
}
}

if(parameter.schema?.$ref) {
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved
console.warn('Could not convert parameter object because the `.schema` property was a reference.\nThis have to be changed manually if you want any of the properties included, it will be converted to a default parameter. The reference was ' + parameter.schema?.$ref);
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved
}

const enumValues = parameter.schema?.enum ?? null;
const constValue = parameter.schema?.const ?? null;
const defaultValues = parameter.schema?.default ?? null;
const description = parameter.description ?? parameter.schema?.description ?? null;
const examples = parameter.schema?.examples ?? null;
const location = parameter.location ?? null;

reportUnsupportedParameterValues(parameter.schema);

//Make sure we keep parameter extensions
const v2ParameterObjectProperties = ["location", "schema", "description"];
Expand All @@ -404,14 +413,32 @@ function convertParameter(parameter: any): any {
});

//Return the new v3 parameter object
return Object.assign({...v2ParameterObjectExtensions},
enumValues === null ? null : {enum: enumValues},
defaultValues === null ? null : {default: defaultValues},
description === null ? null : {description},
examples === null ? null : {examples},
location === null ? null : {location}
);
return {...v2ParameterObjectExtensions,
...(enumValues === null ? null : {enum: enumValues}),
...(constValue === null ? null : {enum: [constValue]}),
...(defaultValues === null ? null : {default: defaultValues}),
...(description === null ? null : {description}),
...(examples === null ? null : {examples}),
...(location === null ? null : {location})
};
}

/**
* This function makes sure we complain if a parameter schema uses now unsupported properties
*/
function reportUnsupportedParameterValues(schema: any) {
if(schema === undefined) return;
const excessProperties = Object.entries(schema).filter((([propertyName,]) => {
return !['$ref', 'enum', 'const', 'default', 'examples', 'description'].includes(propertyName);
}));
if(excessProperties.length > 0) {
const listOfProperties = excessProperties.map(([propertyName, property]) => {
return `- schema.${propertyName} with value: ${JSON.stringify(property)} are no longer supported`
})
console.warn(`Found properties in parameter schema that are no longer supported and will be ignored by the converter.\n${listOfProperties.join('\n')}`);
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved
}
}

/**
* Convert `channels`, `servers` and `securitySchemes` in components.
*/
Expand Down
7 changes: 7 additions & 0 deletions test/input/2.6.0/for-3.0.0-with-mixed-parameters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ channels:
$ref: '#/components/parameters/location'
mixed:
$ref: '#/components/parameters/mixed'
schemaRef:
schema:
$ref: '#/components/schemas/schemaParameter'
components:
schemas:
schemaParameter:
type: string
enum: ["test"]
parameters:
enum:
schema:
Expand Down
30 changes: 30 additions & 0 deletions test/input/2.6.0/for-3.0.0-with-reference-parameter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
asyncapi: 2.6.0

info:
title: AsyncAPI Sample App
version: 1.0.1

channels:
'lightingMeasured/{parameter}/{parameter2}':
parameters:
parameter:
schema:
$ref: '#/components/schemas/sentAt'
parameter2:
schema:
pattern: test
publish:
operationId: lightMeasured
message:
payload:
type: object
properties:
someProperty:
type: string

components:
schemas:
sentAt:
type: string
format: date-time
description: Date and time when the message was sent.
5 changes: 5 additions & 0 deletions test/output/3.0.0/from-2.6.0-with-mixed-parameters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ channels:
$ref: '#/components/parameters/location'
mixed:
$ref: '#/components/parameters/mixed'
schemaRef: {}
components:
schemas:
schemaParameter:
type: string
enum: ["test"]
parameters:
enum:
enum: ["test"]
Expand Down
31 changes: 31 additions & 0 deletions test/output/3.0.0/from-2.6.0-with-reference-parameter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
asyncapi: 3.0.0
info:
title: AsyncAPI Sample App
version: 1.0.1
channels:
'lightingMeasured/{parameter}/{parameter2}':
address: 'lightingMeasured/{parameter}/{parameter2}'
messages:
lightMeasured.message:
payload:
type: object
properties:
someProperty:
type: string
parameters:
parameter: {}
parameter2: {}
operations:
lightMeasured:
action: receive
channel:
$ref: '#/channels/lightingMeasured~1{parameter}~1{parameter2}'
messages:
- $ref: >-
#/channels/lightingMeasured~1{parameter}~1{parameter2}/messages/lightMeasured.message
components:
schemas:
sentAt:
type: string
format: date-time
description: Date and time when the message was sent.
7 changes: 7 additions & 0 deletions test/second-to-third-version.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,11 @@ describe('convert() - 2.X.X to 3.X.X versions', () => {
const result = convert(input, '3.0.0');
assertResults(output, result);
});

it('should handle parameter object', () => {
const input = fs.readFileSync(path.resolve(__dirname, 'input', '2.6.0', 'for-3.0.0-with-reference-parameter.yml'), 'utf8');
const output = fs.readFileSync(path.resolve(__dirname, 'output', '3.0.0', 'from-2.6.0-with-reference-parameter.yml'), 'utf8');
const result = convert(input, '3.0.0');
assertResults(output, result);
});
});