Skip to content

Instantly share code, notes, and snippets.

@wouter-veeken
Last active March 1, 2024 08:34
Show Gist options
  • Save wouter-veeken/9518650367d2fa9188faeef4fa544b59 to your computer and use it in GitHub Desktop.
Save wouter-veeken/9518650367d2fa9188faeef4fa544b59 to your computer and use it in GitHub Desktop.
Node JS script that accepts a swagger.json URL passed on the command line, extracts human-readable descriptions, and passes them to Vale for grammar/style checks. Requires: Node, Vale. DISCLAIMER: ChatGPT wrote most of this for me, with a fair amount of tweaking and iteration, and it works to my satisfaction. Approach with a healthy degree of sk…
// $ node lint-openapi-with-vale.js https://link-to-my/swagger.json
const axios = require('axios');
const { exec } = require('child_process');
async function fetchSwaggerJson(remoteUrl) {
try {
const response = await axios.get(remoteUrl);
return response.data;
} catch (error) {
console.error('Error fetching Swagger JSON:', error);
process.exit(1);
}
}
function extractInformation(swaggerJson) {
const extractedData = [];
// Extract general API description
if (swaggerJson.info && swaggerJson.info.description) {
extractedData.push({
type: 'apiDescription',
name: 'General API Description',
description: swaggerJson.info.description,
});
}
// Extract information for operations
for (const path in swaggerJson.paths) {
for (const method in swaggerJson.paths[path]) {
const operation = swaggerJson.paths[path][method];
const operationInfo = {
type: 'operation',
name: operation.operationId,
summary: operation.summary || '',
description: operation.description || '',
parameters: operation.parameters
? operation.parameters.map(param => `\`${param.name}\`: ${param.description || ''}`)
: [],
responses: Object.entries(operation.responses).map(([code, response]) => `${code}: ${response.description || ''}`),
};
extractedData.push(operationInfo);
}
}
// Extract information for object models (OAS2 or OAS3)
if (swaggerJson.definitions) {
for (const modelName in swaggerJson.definitions) {
const model = swaggerJson.definitions[modelName];
const modelInfo = {
type: 'model',
name: modelName,
description: model.description || '',
properties: [],
};
// Extract model properties and their descriptions (OAS2)
if (model.properties) {
for (const propName in model.properties) {
const prop = model.properties[propName];
const propDescription = prop.description || '';
// Include property name and description
modelInfo.properties.push(`\`${propName}\`: ${propDescription}`);
}
}
extractedData.push(modelInfo);
}
} else if (swaggerJson.components && swaggerJson.components.schemas) {
for (const modelName in swaggerJson.components.schemas) {
const model = swaggerJson.components.schemas[modelName];
const modelInfo = {
type: 'model',
name: modelName,
description: model.description || '',
properties: [],
};
// Extract model properties and their descriptions (OAS3)
if (model.properties) {
for (const propName in model.properties) {
const prop = model.properties[propName];
const propDescription = prop.description || '';
// Include property name and description
modelInfo.properties.push(`\`${propName}\`: ${propDescription}`);
}
}
extractedData.push(modelInfo);
}
}
return extractedData;
}
function lintWithVale(extractedData) {
const feedbackByType = {};
extractedData.forEach(item => {
if (!feedbackByType[item.type]) {
feedbackByType[item.type] = {};
}
if (!feedbackByType[item.type][item.name]) {
feedbackByType[item.type][item.name] = [];
}
feedbackByType[item.type][item.name].push(item.summary);
feedbackByType[item.type][item.name].push(item.description);
if (item.parameters) {
feedbackByType[item.type][item.name].push(...item.parameters);
}
if (item.type === 'model') {
feedbackByType[item.type][item.name].push(...item.properties);
}
if (item.responses) {
feedbackByType[item.type][item.name].push(...item.responses);
}
});
for (const type in feedbackByType) {
for (const name in feedbackByType[type]) {
const feedback = feedbackByType[type][name].filter(Boolean).join('\n\n');
const escapedFeedback = feedback.replace(/[\(\)\<\>\{\}]/g, '\\$&');
exec(`echo "${escapedFeedback}" | vale --config=vale.ini --output=line --ext=.md --no-exit`, (error, stdout, stderr) => {
if (error) {
console.error(`There was a problem linting this chunk: ${error.message}`);
return; // Move on to the next feedback item
}
console.log(`\n[${name} (${type})]\n`);
console.log(feedback);
console.log(`\n[Feedback]\n`);
console.log(stdout);
console.log(`--------`)
});
}
}
}
async function main() {
const remoteUrl = process.argv[2];
if (!remoteUrl) {
console.error('Please provide a remote URL for Swagger JSON as a command-line argument.');
process.exit(1);
}
const swaggerJson = await fetchSwaggerJson(remoteUrl);
const extractedData = extractInformation(swaggerJson);
lintWithVale(extractedData);
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment