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

feat: optional primitive types limits #115

Merged
merged 1 commit into from
Nov 29, 2024
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
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,35 @@ message Point {

### Per message annotation


| annotation | description |
|------------|:---------------------------------------------------------------------------------------------------------|
| @RootNode | If there are multiple types without an parent you can give a hint to the root node with this annotation. |

### Head annotation

| annotation | description |
|------------|:----------------------------------------------------------|
| @Option | In head of your file you can place options for the parser |


### Head annotation "Option"

The `@Option` have to follow by space separated options key and another space separated value

```
// @Option primitiveTypesWithLimits false

message Point {

}
```

Possible options are:


| option | description | def |
|--------------------------|:-----------------------------------------------------------------------------------------------------------|:-----|
| primitiveTypesWithLimits | If you dont like to get default Min and Max limits for primitive types, you can set this option to `false` | true |



65 changes: 64 additions & 1 deletion src/primitive-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class PrimitiveTypes {
private static readonly MAX_SAFE_INTEGER = Math.pow(2, 53) - 1;
private static readonly MIN_SAFE_INTEGER = -this.MAX_SAFE_INTEGER;

public static readonly PRIMITIVE_TYPES: AsyncApiTypeMap = {
public static readonly PRIMITIVE_TYPES_WITH_LIMITS: AsyncApiTypeMap = {
bytes: {
type: 'string',
'x-primitive': 'bytes',
Expand Down Expand Up @@ -85,4 +85,67 @@ export class PrimitiveTypes {
'x-primitive': 'double',
},
};

public static readonly PRIMITIVE_TYPES_MINIMAL: AsyncApiTypeMap = {
bytes: {
type: 'string',
'x-primitive': 'bytes',
},
string: {
type: 'string',
'x-primitive': 'string',
},
bool: {
type: 'boolean',
'x-primitive': 'bool',
},
int32: {
type: 'integer',
'x-primitive': 'int32',
},
sint32: {
type: 'integer',
'x-primitive': 'sint32',
},
uint32: {
type: 'integer',
'x-primitive': 'uint32',
},
int64: {
type: 'integer',
'x-primitive': 'int64',
},
sint64: {
type: 'integer',
'x-primitive': 'sint64',
},
uint64: {
type: 'integer',
'x-primitive': 'uint64',
},
fixed32: {
type: 'number',
'x-primitive': 'fixed32',
},
fixed64: {
type: 'number',
'x-primitive': 'fixed64',
},
sfixed32: {
type: 'number',
'x-primitive': 'sfixed32',
},
sfixed64: {
type: 'number',
'x-primitive': 'sfixed64',
},
float: {
type: 'number',
'x-primitive': 'float',
},
double: {
type: 'number',
'x-primitive': 'double',
},
};
}
47 changes: 40 additions & 7 deletions src/protoj2jsonSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {AsyncAPISchemaDefinition} from '@asyncapi/parser/esm/spec-types/v3';

const ROOT_FILENAME = 'root';
const COMMENT_ROOT_NODE = '@RootNode';
const COMMENT_OPTION = '@Option';
const COMMENT_EXAMPLE = '@Example';
const COMMENT_DEFAULT = '@Default';

Expand All @@ -18,11 +19,39 @@ class Proto2JsonSchema {
keepCase: true,
alternateCommentMode: true
};
private mapperOptions: { [key: string]: string | boolean } = {
primitiveTypesWithLimits: true
};

constructor(rawSchema: string) {
this.parseOptionsAnnotation(rawSchema);

this.process(ROOT_FILENAME, rawSchema);
}

private parseOptionsAnnotation(rawSchema: string) {
const regex = /\s*(\/\/|\*)\s*@Option\s+(?<key>\w{1,50})\s+(?<value>[^\r\n]{1,200})/g;
let m: RegExpExecArray | null;
while ((m = regex.exec(rawSchema)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}

if (m.groups === undefined) {
break;
}

if (m.groups.value === 'true') {
this.mapperOptions[m.groups.key] = true;
} else if (m.groups.value === 'false') {
this.mapperOptions[m.groups.key] = false;
} else {
this.mapperOptions[m.groups.key] = m.groups.value;
}
}
}

private process(filename: string, source: string | ProtoAsJson) {
if (!isString(source)) {
const srcObject = source as ProtoAsJson;
Expand Down Expand Up @@ -182,7 +211,7 @@ class Proto2JsonSchema {
*/
// eslint-disable-next-line sonarjs/cognitive-complexity
private compileMessage(item: protobuf.Type, stack: string[]): AsyncAPISchemaDefinition {
const properties: {[key: string]: AsyncAPISchemaDefinition} = {};
const properties: { [key: string]: AsyncAPISchemaDefinition } = {};

const obj: v3.AsyncAPISchemaDefinition = {
title: item.name,
Expand Down Expand Up @@ -228,7 +257,7 @@ class Proto2JsonSchema {
}

if (field.comment) {
const minItemsPattern = /@maxItems\\s(\\d+?)/i;
const minItemsPattern = /@minItems\\s(\\d+?)/i;
const maxItemsPattern = /@maxItems\\s(\\d+?)/i;
let m: RegExpExecArray | null;
if ((m = minItemsPattern.exec(field.comment)) !== null) {
Expand Down Expand Up @@ -294,8 +323,10 @@ class Proto2JsonSchema {
private compileField(field: protobuf.Field, parentItem: protobuf.Type, stack: string[]): v3.AsyncAPISchemaDefinition {
let obj: v3.AsyncAPISchemaDefinition = {};

if (PrimitiveTypes.PRIMITIVE_TYPES[field.type.toLowerCase()]) {
obj = Object.assign(obj, PrimitiveTypes.PRIMITIVE_TYPES[field.type.toLowerCase()]);
if (PrimitiveTypes.PRIMITIVE_TYPES_WITH_LIMITS[field.type.toLowerCase()]) {
obj = (this.mapperOptions.primitiveTypesWithLimits) ?
Object.assign(obj, PrimitiveTypes.PRIMITIVE_TYPES_WITH_LIMITS[field.type.toLowerCase()]) :
Object.assign(obj, PrimitiveTypes.PRIMITIVE_TYPES_MINIMAL[field.type.toLowerCase()]);
obj['x-primitive'] = field.type;
} else {
const item = parentItem.lookupTypeOrEnum(field.type);
Expand Down Expand Up @@ -339,6 +370,7 @@ class Proto2JsonSchema {
comment = comment
.replace(new RegExp(`\\s{0,15}${COMMENT_EXAMPLE}\\s{0,15}(.+)`, 'ig'), '')
.replace(new RegExp(`\\s{0,15}${COMMENT_DEFAULT}\\s{0,15}(.+)`, 'ig'), '')
.replace(new RegExp(`\\s{0,15}${COMMENT_OPTION}\\s{0,15}(.+)`, 'ig'), '')
.replace(new RegExp(`\\s{0,15}${COMMENT_ROOT_NODE}`, 'ig'), '')
.replace(new RegExp('\\s{0,15}@(Min|Max|Pattern|Minimum|Maximum|ExclusiveMinimum|ExclusiveMaximum|MultipleOf|MaxLength|MinLength|MaxItems|MinItems)\\s{0,15}[\\d.]{1,20}', 'ig'), '')
.trim();
Expand All @@ -350,12 +382,12 @@ class Proto2JsonSchema {
return comment;
}

private extractExamples(comment: string | null): (string|ProtoAsJson)[] | null {
private extractExamples(comment: string | null): (string | ProtoAsJson)[] | null {
if (!comment) {
return null;
}

const examples: (string|ProtoAsJson)[] = [];
const examples: (string | ProtoAsJson)[] = [];

let m: RegExpExecArray | null;
const examplePattern = new RegExp(`\\s*${COMMENT_EXAMPLE}\\s(.+)$`, 'i');
Expand Down Expand Up @@ -413,6 +445,7 @@ class Proto2JsonSchema {
}
}
}

/* eslint-enable security/detect-unsafe-regex */

private addDefaultFromCommentAnnotations(obj: AsyncAPISchemaDefinition, comment: string | null) {
Expand Down Expand Up @@ -442,7 +475,7 @@ function tryParseToObject(value: string): string | ProtoAsJson {
try {
const json = JSON.parse(value);
if (json) {
return json;
return json;
}
} catch (_) {
// Ignored error, seams not to be a valid json. Maybe just an example starting with an "{" but is not a json.
Expand Down
Loading