Skip to content

Commit

Permalink
fix: add more tests and fix recursion handling (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
GreenRover authored Mar 11, 2024
1 parent be1fea4 commit c270f35
Show file tree
Hide file tree
Showing 12 changed files with 1,757 additions and 937 deletions.
6 changes: 2 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,10 @@ async function validate(input: ValidateSchemaInput<unknown, unknown>): Promise<S
message = error.message;
}

const validateResult: SchemaValidateResult[] = [{
return [{
message,
path: input.path, // protobuff parser doesn't provide a path to the error.
path: input.path, // protobuf parser doesn't provide a path to the error.
}];

return validateResult;
}
}

Expand Down
88 changes: 60 additions & 28 deletions src/protoj2jsonSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,20 +93,6 @@ class Proto2JsonSchema {
if (!weak) {
throw new Error('Imports are currently not implemented');
}
/*
// Otherwise fetch from disk or network
let source: string;
try {
source = util.fs.readFileSync(filename).toString('utf8');
} catch (err) {
if (!weak) {
throw err;
}
return;
}
this.process(filename, source);
*/
}

public compile(): AsyncAPISchema {
Expand All @@ -115,7 +101,7 @@ class Proto2JsonSchema {
const rootItemCandidates = this.resolveByFilename(ROOT_FILENAME, this.root.nested as ProtoItems);
const rootItem = this.findRootItem(rootItemCandidates);

return this.compileMessage(rootItem);
return this.compileMessage(rootItem, []);
}

private resolveByFilename(filename: string, items: ProtoItems) {
Expand Down Expand Up @@ -186,28 +172,52 @@ class Proto2JsonSchema {
* Compiles a protobuf message to JSON schema
*/
// eslint-disable-next-line sonarjs/cognitive-complexity
private compileMessage(item: protobuf.Type): AsyncAPISchemaDefinition {
private compileMessage(item: protobuf.Type, stack: string[]): AsyncAPISchemaDefinition {
const properties: {[key: string]: AsyncAPISchemaDefinition} = {};

const obj: v3.AsyncAPISchemaDefinition = {
title: item.name,
type: 'object',
required: [],
properties: {},
properties,
};

const desc = this.extractDescription(item.comment);
if (desc !== null && desc.length > 0) {
obj.description = desc;
}

const timesSeenThisClassInStack = stack.filter(x => x === item.name).length;
if (timesSeenThisClassInStack >= 2) {
// Detected a recursion.
return obj;
}

stack.push(item.name);

for (const fieldName in item.fields) {
const field = item.fields[fieldName];

if (field.partOf && field.partOf.oneof.length > 1) {
// Filter only real oneof. Don't do for false positives optionals (oneof starting with _ and contain only one entry)
continue;
}

if (field.required) {
obj.required?.push(fieldName);
}

if (field.repeated) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
obj.properties![field.name] = {
description: this.extractDescription(field.comment) || '',
properties[field.name] = {
type: 'array',
items: this.compileField(field, item),
items: this.compileField(field, item, stack.slice()),
};

const desc = this.extractDescription(field.comment);
if (desc !== null && desc.length > 0) {
properties[field.name].description = desc;
}

if (field.comment) {
const minItemsPattern = /@maxItems\\s(\\d+?)/i;
const maxItemsPattern = /@maxItems\\s(\\d+?)/i;
Expand All @@ -220,8 +230,26 @@ class Proto2JsonSchema {
}
}
} else {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
obj.properties![field.name] = this.compileField(field, item);
properties[field.name] = this.compileField(field, item, stack.slice());
}
}

for (const oneOff of item.oneofsArray) {
if (oneOff.fieldsArray.length < 2) {
// Filter optionals (oneof starting with _ and contain only one entry)
continue;
}

if (!properties[oneOff.name]) {
properties[oneOff.name] = {
oneOf: []
};
}
const oneOf = (properties[oneOff.name] as any)['oneOf'] as any[];

for (const fieldName of oneOff.oneof) {
const field = item.fields[fieldName];
oneOf.push(this.compileField(field, item, stack.slice()));
}
}

Expand Down Expand Up @@ -254,7 +282,7 @@ class Proto2JsonSchema {
return obj;
}

private compileField(field: protobuf.Field, parentItem: protobuf.Type): v3.AsyncAPISchemaDefinition {
private compileField(field: protobuf.Field, parentItem: protobuf.Type, stack: string[]): v3.AsyncAPISchemaDefinition {
let obj: v3.AsyncAPISchemaDefinition = {};

if (PrimitiveTypes.PRIMITIVE_TYPES[field.type.toLowerCase()]) {
Expand All @@ -270,16 +298,20 @@ class Proto2JsonSchema {
if (item instanceof protobuf.Enum) {
obj = Object.assign(obj, this.compileEnum(item));
} else {
obj = Object.assign(obj, this.compileMessage(item));
obj = Object.assign(obj, this.compileMessage(item, stack));
}
}

this.addValidatorFromCommentAnnotations(obj, field.comment);
this.addDefaultFromCommentAnnotations(obj, field.comment);

const desc = this.extractDescription(field.comment);
if (desc !== null) {
obj.description = desc;
if (desc !== null && desc.length > 0) {
if (obj.description) {
obj.description = (`${desc}\n${obj.description}`).trim();
} else {
obj.description = desc;
}
}

const examples = this.extractExamples(field.comment);
Expand Down Expand Up @@ -404,7 +436,7 @@ function tryParseToObject(value: string): string | ProtoAsJson {
return json;
}
} catch (_) {
// Ignored error, seams not to be a valid json. Maybe just an example starting with an { but is not a json.
// Ignored error, seams not to be a valid json. Maybe just an example starting with an "{" but is not a json.
}
}

Expand Down
100 changes: 57 additions & 43 deletions test/documents/comments.proto.result.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"type": "object",
"required": [
"start",
"end"
"end",
"line_size"
],
"properties": {
"start": {
Expand All @@ -29,23 +30,20 @@
"type": "integer",
"minimum": -2147483648,
"maximum": 2147483647,
"x-primitive": "int32",

"x-primitive": "int32"
},
"y": {
"type": "integer",
"minimum": -2147483648,
"maximum": 2147483647,
"x-primitive": "int32",

"x-primitive": "int32"
},
"label": {
"type": "string",
"x-primitive": "string",

"x-primitive": "string"
}
},

"description": "Single-line comment"
},
"end": {
"title": "Point",
Expand All @@ -59,34 +57,44 @@
"type": "integer",
"minimum": -2147483648,
"maximum": 2147483647,
"x-primitive": "int32",

"x-primitive": "int32"
},
"y": {
"type": "integer",
"minimum": -2147483648,
"maximum": 2147483647,
"x-primitive": "int32",

"x-primitive": "int32"
},
"label": {
"type": "string",
"x-primitive": "string",

"x-primitive": "string"
}
},

"description": "field comment A\nSingle-line comment"
},
"label": {
"type": "string",
"x-primitive": "string",

"description": "field comment B"
},
"line_size": {
"title": "LineSize",
"type": "string",
"enum": [
"TST_A",
"TST_B",
"TST_C"
],
"x-enum-mapping": {
"TST_A": 1,
"TST_B": 2,
"TST_C": 3
},
"description": "Multi line\nfield comment B"
}
},

},


"description": "Multi-line comment"
}
}
}
}
Expand All @@ -100,7 +108,8 @@
"type": "object",
"required": [
"start",
"end"
"end",
"line_size"
],
"properties": {
"start": {
Expand All @@ -115,23 +124,20 @@
"type": "integer",
"minimum": -2147483648,
"maximum": 2147483647,
"x-primitive": "int32",

"x-primitive": "int32"
},
"y": {
"type": "integer",
"minimum": -2147483648,
"maximum": 2147483647,
"x-primitive": "int32",

"x-primitive": "int32"
},
"label": {
"type": "string",
"x-primitive": "string",

"x-primitive": "string"
}
},

"description": "Single-line comment"
},
"end": {
"title": "Point",
Expand All @@ -145,37 +151,45 @@
"type": "integer",
"minimum": -2147483648,
"maximum": 2147483647,
"x-primitive": "int32",

"x-primitive": "int32"
},
"y": {
"type": "integer",
"minimum": -2147483648,
"maximum": 2147483647,
"x-primitive": "int32",

"x-primitive": "int32"
},
"label": {
"type": "string",
"x-primitive": "string",

"x-primitive": "string"
}
},

"description": "field comment A\nSingle-line comment"
},
"label": {
"type": "string",
"x-primitive": "string",

"description": "field comment B"
},
"line_size": {
"title": "LineSize",
"type": "string",
"enum": [
"TST_A",
"TST_B",
"TST_C"
],
"x-enum-mapping": {
"TST_A": 1,
"TST_B": 2,
"TST_C": 3
},
"description": "Multi line\nfield comment B"
}
},

},


"description": "Multi-line comment"
}
}
}
},


}
}
Loading

0 comments on commit c270f35

Please sign in to comment.