Skip to content

Commit d7bc0c8

Browse files
committed
Generate example request body when parsing openapi spec
1 parent 67064c3 commit d7bc0c8

4 files changed

Lines changed: 285 additions & 14 deletions

File tree

packages/flowtest-electron/src/ipc/collection.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const createDirectory = require('../utils/filemanager/createdirectory');
88
const deleteDirectory = require('../utils/filemanager/deletedirectory');
99
const uuidv4 = require('uuid').v4;
1010
const Collections = require('../store/collection');
11-
const parseOpenAPISpec = require('../utils/collection');
11+
const { parseOpenAPISpec } = require('../utils/collection');
1212
const { isDirectory, pathExists } = require('../utils/filemanager/filesystem');
1313
const createFile = require('../utils/filemanager/createfile');
1414
const updateFile = require('../utils/filemanager/updatefile');

packages/flowtest-electron/src/utils/collection.js

Lines changed: 145 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,127 @@ const replaceSingleToDoubleCurlyBraces = (str) => {
1616
return str;
1717
};
1818

19+
const generateExample = (schema) => {
20+
if (!schema) return {};
21+
22+
if (schema.enum) {
23+
return schema.example || schema.enum[0];
24+
}
25+
26+
if (schema.oneOf) {
27+
return generateExample(schema.oneOf[0]);
28+
}
29+
30+
if (schema.anyOf) {
31+
return generateExample(schema.anyOf[0]);
32+
}
33+
34+
if (schema.allOf) {
35+
return generateAllOfExample(schema.allOf);
36+
}
37+
38+
switch (schema.type) {
39+
case 'object':
40+
return generateObjectExample(schema);
41+
case 'array':
42+
return generateArrayExample(schema);
43+
case 'string':
44+
return generateStringExample(schema);
45+
case 'integer':
46+
return generateIntegerExample(schema);
47+
case 'number':
48+
return generateNumberExample(schema);
49+
case 'boolean':
50+
return schema.example || true;
51+
default:
52+
return schema.example || null;
53+
}
54+
};
55+
56+
const generateAllOfExample = (schemas) => {
57+
const example = {};
58+
schemas.forEach((subSchema) => {
59+
const subExample = generateExample(subSchema);
60+
Object.assign(example, subExample);
61+
});
62+
return example;
63+
};
64+
65+
const generateObjectExample = (schema) => {
66+
const example = {};
67+
const properties = schema.properties || {};
68+
69+
for (const [key, propertySchema] of Object.entries(properties)) {
70+
example[key] = generateExample(propertySchema);
71+
}
72+
73+
return example;
74+
};
75+
76+
const generateArrayExample = (schema) => {
77+
const itemsSchema = schema.items || {};
78+
return [generateExample(itemsSchema)];
79+
};
80+
81+
const generateStringExample = (schema) => {
82+
let example = schema.example || 'string';
83+
84+
if (schema.minLength || schema.maxLength) {
85+
example = generateStringWithLengthConstraints(example, schema.minLength, schema.maxLength);
86+
}
87+
88+
switch (schema.format) {
89+
case 'date-time':
90+
return schema.example || new Date().toISOString();
91+
case 'date':
92+
return schema.example || new Date().toISOString().split('T')[0];
93+
case 'email':
94+
return schema.example || 'example@example.com';
95+
case 'uuid':
96+
return schema.example || '123e4567-e89b-12d3-a456-426614174000';
97+
case 'uri':
98+
return schema.example || 'https://example.com';
99+
case 'hostname':
100+
return schema.example || 'example.com';
101+
case 'ipv4':
102+
return schema.example || '192.168.0.1';
103+
case 'ipv6':
104+
return schema.example || '2001:0db8:85a3:0000:0000:8a2e:0370:7334';
105+
case 'byte':
106+
return schema.example || btoa('example');
107+
case 'binary':
108+
return schema.example || 'binary data';
109+
case 'password':
110+
return schema.example || 'password';
111+
default:
112+
return example;
113+
}
114+
};
115+
116+
const generateStringWithLengthConstraints = (str, minLength, maxLength) => {
117+
if (minLength) {
118+
while (str.length < minLength) {
119+
str += 'a';
120+
}
121+
}
122+
if (maxLength) {
123+
str = str.substring(0, maxLength);
124+
}
125+
return str;
126+
};
127+
128+
const generateIntegerExample = (schema) => {
129+
const min = schema.minimum || 0;
130+
const max = schema.maximum || min + 100;
131+
return schema.example || Math.floor(Math.random() * (max - min + 1)) + min;
132+
};
133+
134+
const generateNumberExample = (schema) => {
135+
const min = schema.minimum || 0.0;
136+
const max = schema.maximum || min + 100.0;
137+
return schema.example || Math.random() * (max - min) + min;
138+
};
139+
19140
const parseOpenAPISpec = (collection) => {
20141
let parsedNodes = [];
21142
try {
@@ -28,10 +149,9 @@ const parseOpenAPISpec = (collection) => {
28149
const tags = request['tags'];
29150
var url = replaceSingleToDoubleCurlyBraces(computeUrl(baseUrl, path));
30151
var variables = {};
152+
var requestBody = {};
31153

32-
// console.log(operationId)
33-
// Get is easy, others are hard
34-
if (requestType.toUpperCase() === 'GET' && request['parameters']) {
154+
if (request['parameters']) {
35155
let firstQueryParam = true;
36156
request['parameters'].map((value, _) => {
37157
// path parameters are included in url
@@ -49,9 +169,22 @@ const parseOpenAPISpec = (collection) => {
49169
}
50170

51171
if (request['requestBody']) {
52-
if (request['requestBody']['application/json']) {
53-
// console.log('requestBody: ', request["requestBody"]["content"]["schema"])
54-
// generate an example to be used in request body
172+
if (request['requestBody']['content']['application/json']) {
173+
requestBody = {
174+
type: 'raw-json',
175+
body: JSON.stringify(generateExample(request['requestBody']['content']['application/json']['schema'])),
176+
};
177+
}
178+
179+
if (request['requestBody']['content']['multipart/form-data']) {
180+
requestBody = {
181+
type: 'form-data',
182+
body: {
183+
key: '',
184+
value: '',
185+
name: '',
186+
},
187+
};
55188
}
56189
}
57190

@@ -61,8 +194,9 @@ const parseOpenAPISpec = (collection) => {
61194
operationId: operationId,
62195
requestType: requestType.toUpperCase(),
63196
tags: tags,
197+
requestBody,
64198
};
65-
// console.log(finalNode);
199+
66200
parsedNodes.push(finalNode);
67201
});
68202
});
@@ -72,4 +206,7 @@ const parseOpenAPISpec = (collection) => {
72206
return parsedNodes;
73207
};
74208

75-
module.exports = parseOpenAPISpec;
209+
module.exports = {
210+
parseOpenAPISpec,
211+
generateExample,
212+
};
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
const { generateExample } = require('./collection.js');
2+
3+
describe('collection parser', () => {
4+
it('should generate request body example', () => {
5+
console.log(JSON.stringify(generateExample(userSchema), null, 2));
6+
console.log(JSON.stringify(generateExample(productSchema), null, 2));
7+
console.log(JSON.stringify(generateExample(complexSchema), null, 2));
8+
});
9+
});
10+
11+
const userSchema = {
12+
type: 'object',
13+
properties: {
14+
id: {
15+
type: 'integer',
16+
format: 'int64',
17+
example: 1,
18+
minimum: 1,
19+
},
20+
name: {
21+
type: 'string',
22+
example: 'John Doe',
23+
minLength: 3,
24+
maxLength: 20,
25+
},
26+
email: {
27+
type: 'string',
28+
format: 'email',
29+
example: 'john.doe@example.com',
30+
},
31+
birthdate: {
32+
type: 'string',
33+
format: 'date',
34+
example: '1990-01-01',
35+
},
36+
website: {
37+
type: 'string',
38+
format: 'uri',
39+
example: 'https://johndoe.com',
40+
},
41+
role: {
42+
type: 'string',
43+
enum: ['admin', 'user', 'guest'],
44+
example: 'user',
45+
},
46+
username: {
47+
type: 'string',
48+
pattern: '^[a-zA-Z0-9]{3,}$',
49+
example: 'user123',
50+
},
51+
},
52+
};
53+
54+
const productSchema = {
55+
type: 'object',
56+
properties: {
57+
id: {
58+
type: 'integer',
59+
format: 'int32',
60+
example: 101,
61+
minimum: 1,
62+
maximum: 1000,
63+
},
64+
name: {
65+
type: 'string',
66+
example: 'Sample Product',
67+
minLength: 3,
68+
},
69+
price: {
70+
type: 'number',
71+
format: 'double',
72+
example: 19.99,
73+
minimum: 0,
74+
maximum: 1000,
75+
},
76+
tags: {
77+
type: 'array',
78+
items: {
79+
type: 'string',
80+
example: 'tag1',
81+
},
82+
},
83+
status: {
84+
type: 'string',
85+
enum: ['available', 'out of stock', 'discontinued'],
86+
example: 'available',
87+
},
88+
releaseDate: {
89+
type: 'string',
90+
format: 'date-time',
91+
example: '2023-01-01T00:00:00Z',
92+
},
93+
},
94+
};
95+
96+
const complexSchema = {
97+
type: 'object',
98+
properties: {
99+
name: {
100+
type: 'string',
101+
example: 'Complex Example',
102+
},
103+
detail: {
104+
oneOf: [
105+
{ type: 'string', example: 'OneOf String' },
106+
{ type: 'integer', example: 42 },
107+
],
108+
},
109+
options: {
110+
anyOf: [
111+
{ type: 'boolean', example: true },
112+
{ type: 'string', example: 'AnyOf String' },
113+
],
114+
},
115+
allDetails: {
116+
allOf: [
117+
{
118+
type: 'object',
119+
properties: {
120+
part1: {
121+
type: 'string',
122+
example: 'Part 1',
123+
},
124+
},
125+
},
126+
{
127+
type: 'object',
128+
properties: {
129+
part2: {
130+
type: 'number',
131+
example: 123.45,
132+
},
133+
},
134+
},
135+
],
136+
},
137+
},
138+
};

src/components/molecules/flow/nodes/AuthNode.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,12 @@ import TextInput from 'components/atoms/common/TextInput';
99
const AuthNode = ({ id, data }) => {
1010
const setAuthNodeType = useCanvasStore((state) => state.setAuthNodeType);
1111
const setBasicAuthValues = useCanvasStore((state) => state.setBasicAuthValues);
12-
const [selected, setSelected] = useState('no-auth');
12+
const [selected, setSelected] = useState(data.type ? data.type : 'no-auth');
1313

1414
const handleChange = (value, option) => {
1515
setBasicAuthValues(id, option, value);
1616
};
1717

18-
const handleSelection = (event) => {
19-
setAuthNodeType(id, event.target?.value);
20-
};
21-
2218
return (
2319
<>
2420
<FlowNode

0 commit comments

Comments
 (0)