Skip to content

Commit 7b2c139

Browse files
authored
feat: optional primitive types limits (#115)
1 parent 981b514 commit 7b2c139

File tree

5 files changed

+257
-241
lines changed

5 files changed

+257
-241
lines changed

README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,35 @@ message Point {
143143

144144
### Per message annotation
145145

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

150+
### Head annotation
151+
152+
| annotation | description |
153+
|------------|:----------------------------------------------------------|
154+
| @Option | In head of your file you can place options for the parser |
155+
156+
157+
### Head annotation "Option"
158+
159+
The `@Option` have to follow by space separated options key and another space separated value
160+
161+
```
162+
// @Option primitiveTypesWithLimits false
163+
164+
message Point {
165+
166+
}
167+
```
168+
169+
Possible options are:
170+
171+
172+
| option | description | def |
173+
|--------------------------|:-----------------------------------------------------------------------------------------------------------|:-----|
174+
| primitiveTypesWithLimits | If you dont like to get default Min and Max limits for primitive types, you can set this option to `false` | true |
175+
176+
151177

src/primitive-types.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export class PrimitiveTypes {
1111
private static readonly MAX_SAFE_INTEGER = Math.pow(2, 53) - 1;
1212
private static readonly MIN_SAFE_INTEGER = -this.MAX_SAFE_INTEGER;
1313

14-
public static readonly PRIMITIVE_TYPES: AsyncApiTypeMap = {
14+
public static readonly PRIMITIVE_TYPES_WITH_LIMITS: AsyncApiTypeMap = {
1515
bytes: {
1616
type: 'string',
1717
'x-primitive': 'bytes',
@@ -85,4 +85,67 @@ export class PrimitiveTypes {
8585
'x-primitive': 'double',
8686
},
8787
};
88+
89+
public static readonly PRIMITIVE_TYPES_MINIMAL: AsyncApiTypeMap = {
90+
bytes: {
91+
type: 'string',
92+
'x-primitive': 'bytes',
93+
},
94+
string: {
95+
type: 'string',
96+
'x-primitive': 'string',
97+
},
98+
bool: {
99+
type: 'boolean',
100+
'x-primitive': 'bool',
101+
},
102+
int32: {
103+
type: 'integer',
104+
'x-primitive': 'int32',
105+
},
106+
sint32: {
107+
type: 'integer',
108+
'x-primitive': 'sint32',
109+
},
110+
uint32: {
111+
type: 'integer',
112+
'x-primitive': 'uint32',
113+
},
114+
int64: {
115+
type: 'integer',
116+
'x-primitive': 'int64',
117+
},
118+
sint64: {
119+
type: 'integer',
120+
'x-primitive': 'sint64',
121+
},
122+
uint64: {
123+
type: 'integer',
124+
'x-primitive': 'uint64',
125+
},
126+
fixed32: {
127+
type: 'number',
128+
'x-primitive': 'fixed32',
129+
},
130+
fixed64: {
131+
type: 'number',
132+
'x-primitive': 'fixed64',
133+
},
134+
sfixed32: {
135+
type: 'number',
136+
'x-primitive': 'sfixed32',
137+
},
138+
sfixed64: {
139+
type: 'number',
140+
'x-primitive': 'sfixed64',
141+
},
142+
float: {
143+
type: 'number',
144+
'x-primitive': 'float',
145+
},
146+
double: {
147+
type: 'number',
148+
'x-primitive': 'double',
149+
},
150+
};
88151
}

src/protoj2jsonSchema.ts

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {AsyncAPISchemaDefinition} from '@asyncapi/parser/esm/spec-types/v3';
88

99
const ROOT_FILENAME = 'root';
1010
const COMMENT_ROOT_NODE = '@RootNode';
11+
const COMMENT_OPTION = '@Option';
1112
const COMMENT_EXAMPLE = '@Example';
1213
const COMMENT_DEFAULT = '@Default';
1314

@@ -18,11 +19,39 @@ class Proto2JsonSchema {
1819
keepCase: true,
1920
alternateCommentMode: true
2021
};
22+
private mapperOptions: { [key: string]: string | boolean } = {
23+
primitiveTypesWithLimits: true
24+
};
2125

2226
constructor(rawSchema: string) {
27+
this.parseOptionsAnnotation(rawSchema);
28+
2329
this.process(ROOT_FILENAME, rawSchema);
2430
}
2531

32+
private parseOptionsAnnotation(rawSchema: string) {
33+
const regex = /\s*(\/\/|\*)\s*@Option\s+(?<key>\w{1,50})\s+(?<value>[^\r\n]{1,200})/g;
34+
let m: RegExpExecArray | null;
35+
while ((m = regex.exec(rawSchema)) !== null) {
36+
// This is necessary to avoid infinite loops with zero-width matches
37+
if (m.index === regex.lastIndex) {
38+
regex.lastIndex++;
39+
}
40+
41+
if (m.groups === undefined) {
42+
break;
43+
}
44+
45+
if (m.groups.value === 'true') {
46+
this.mapperOptions[m.groups.key] = true;
47+
} else if (m.groups.value === 'false') {
48+
this.mapperOptions[m.groups.key] = false;
49+
} else {
50+
this.mapperOptions[m.groups.key] = m.groups.value;
51+
}
52+
}
53+
}
54+
2655
private process(filename: string, source: string | ProtoAsJson) {
2756
if (!isString(source)) {
2857
const srcObject = source as ProtoAsJson;
@@ -182,7 +211,7 @@ class Proto2JsonSchema {
182211
*/
183212
// eslint-disable-next-line sonarjs/cognitive-complexity
184213
private compileMessage(item: protobuf.Type, stack: string[]): AsyncAPISchemaDefinition {
185-
const properties: {[key: string]: AsyncAPISchemaDefinition} = {};
214+
const properties: { [key: string]: AsyncAPISchemaDefinition } = {};
186215

187216
const obj: v3.AsyncAPISchemaDefinition = {
188217
title: item.name,
@@ -228,7 +257,7 @@ class Proto2JsonSchema {
228257
}
229258

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

297-
if (PrimitiveTypes.PRIMITIVE_TYPES[field.type.toLowerCase()]) {
298-
obj = Object.assign(obj, PrimitiveTypes.PRIMITIVE_TYPES[field.type.toLowerCase()]);
326+
if (PrimitiveTypes.PRIMITIVE_TYPES_WITH_LIMITS[field.type.toLowerCase()]) {
327+
obj = (this.mapperOptions.primitiveTypesWithLimits) ?
328+
Object.assign(obj, PrimitiveTypes.PRIMITIVE_TYPES_WITH_LIMITS[field.type.toLowerCase()]) :
329+
Object.assign(obj, PrimitiveTypes.PRIMITIVE_TYPES_MINIMAL[field.type.toLowerCase()]);
299330
obj['x-primitive'] = field.type;
300331
} else {
301332
const item = parentItem.lookupTypeOrEnum(field.type);
@@ -339,6 +370,7 @@ class Proto2JsonSchema {
339370
comment = comment
340371
.replace(new RegExp(`\\s{0,15}${COMMENT_EXAMPLE}\\s{0,15}(.+)`, 'ig'), '')
341372
.replace(new RegExp(`\\s{0,15}${COMMENT_DEFAULT}\\s{0,15}(.+)`, 'ig'), '')
373+
.replace(new RegExp(`\\s{0,15}${COMMENT_OPTION}\\s{0,15}(.+)`, 'ig'), '')
342374
.replace(new RegExp(`\\s{0,15}${COMMENT_ROOT_NODE}`, 'ig'), '')
343375
.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'), '')
344376
.trim();
@@ -350,12 +382,12 @@ class Proto2JsonSchema {
350382
return comment;
351383
}
352384

353-
private extractExamples(comment: string | null): (string|ProtoAsJson)[] | null {
385+
private extractExamples(comment: string | null): (string | ProtoAsJson)[] | null {
354386
if (!comment) {
355387
return null;
356388
}
357389

358-
const examples: (string|ProtoAsJson)[] = [];
390+
const examples: (string | ProtoAsJson)[] = [];
359391

360392
let m: RegExpExecArray | null;
361393
const examplePattern = new RegExp(`\\s*${COMMENT_EXAMPLE}\\s(.+)$`, 'i');
@@ -413,6 +445,7 @@ class Proto2JsonSchema {
413445
}
414446
}
415447
}
448+
416449
/* eslint-enable security/detect-unsafe-regex */
417450

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

0 commit comments

Comments
 (0)