Last active
March 1, 2024 08:34
-
-
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…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// $ 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