-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(openapi): DRY and reorg (#556)
- Loading branch information
1 parent
cf02b5c
commit 9ec4ad6
Showing
3 changed files
with
164 additions
and
188 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,6 @@ const APIError = require('../lib/apiError'); | |
const chalk = require('chalk'); | ||
const { cleanHeaders } = require('../lib/fetch'); | ||
const config = require('config'); | ||
const fs = require('fs'); | ||
const { debug, oraOptions } = require('../lib/logger'); | ||
const fetch = require('../lib/fetch'); | ||
const { handleRes } = require('../lib/fetch'); | ||
|
@@ -83,168 +82,142 @@ module.exports = class OpenAPICommand { | |
return Promise.reject(new Error('No project API key provided. Please use `--key`.')); | ||
} | ||
|
||
async function callApi(specPath, versionCleaned) { | ||
const { bundledSpec, specType } = await prepareOas(specPath, true); | ||
|
||
async function success(data) { | ||
const message = !isUpdate | ||
? `You've successfully uploaded a new ${specType} file to your ReadMe project!` | ||
: `You've successfully updated an existing ${specType} file on your ReadMe project!`; | ||
|
||
debug(`successful ${data.status} response`); | ||
const body = await data.json(); | ||
debug(`successful response payload: ${JSON.stringify(body)}`); | ||
|
||
return Promise.resolve( | ||
[ | ||
message, | ||
'', | ||
`\t${chalk.green(`${data.headers.get('location')}`)}`, | ||
'', | ||
`To update your ${specType} definition, run the following:`, | ||
'', | ||
// eslint-disable-next-line no-underscore-dangle | ||
`\t${chalk.green(`rdme openapi ${specPath} --key=<key> --id=${body._id}`)}`, | ||
].join('\n') | ||
); | ||
} | ||
|
||
async function error(res) { | ||
return handleRes(res).catch(err => { | ||
// If we receive an APIError, no changes needed! Throw it as is. | ||
if (err instanceof APIError) { | ||
throw err; | ||
} | ||
// If we receive certain text responses, it's likely a 5xx error from our server. | ||
if ( | ||
typeof err === 'string' && | ||
(err.includes('<title>Application Error</title>') || // Heroku error | ||
err.includes('520: Web server is returning an unknown error</title>')) // Cloudflare error | ||
) { | ||
throw new Error( | ||
"We're sorry, your upload request timed out. Please try again or split your file up into smaller chunks." | ||
); | ||
} | ||
// As a fallback, we throw a more generic error. | ||
if (!id) { | ||
selectedVersion = await getProjectVersion(version, key, true); | ||
} | ||
|
||
debug(`selectedVersion: ${selectedVersion}`); | ||
|
||
// Reason we're hardcoding in command here is because `swagger` command | ||
// relies on this and we don't want to use `swagger` in this function | ||
const { bundledSpec, specPath, specType } = await prepareOas(spec, 'openapi'); | ||
|
||
async function success(data) { | ||
const message = !isUpdate | ||
? `You've successfully uploaded a new ${specType} file to your ReadMe project!` | ||
: `You've successfully updated an existing ${specType} file on your ReadMe project!`; | ||
|
||
debug(`successful ${data.status} response`); | ||
const body = await data.json(); | ||
debug(`successful response payload: ${JSON.stringify(body)}`); | ||
|
||
return Promise.resolve( | ||
[ | ||
message, | ||
'', | ||
`\t${chalk.green(`${data.headers.get('location')}`)}`, | ||
'', | ||
`To update your ${specType} definition, run the following:`, | ||
'', | ||
// eslint-disable-next-line no-underscore-dangle | ||
`\t${chalk.green(`rdme openapi ${specPath} --key=<key> --id=${body._id}`)}`, | ||
].join('\n') | ||
); | ||
} | ||
|
||
async function error(res) { | ||
return handleRes(res).catch(err => { | ||
// If we receive an APIError, no changes needed! Throw it as is. | ||
if (err instanceof APIError) { | ||
throw err; | ||
} | ||
// If we receive certain text responses, it's likely a 5xx error from our server. | ||
if ( | ||
typeof err === 'string' && | ||
(err.includes('<title>Application Error</title>') || // Heroku error | ||
err.includes('520: Web server is returning an unknown error</title>')) // Cloudflare error | ||
) { | ||
throw new Error( | ||
`Yikes, something went wrong! Please try uploading your spec again and if the problem persists, get in touch with our support team at ${chalk.underline( | ||
'[email protected]' | ||
)}.` | ||
"We're sorry, your upload request timed out. Please try again or split your file up into smaller chunks." | ||
); | ||
}); | ||
} | ||
} | ||
// As a fallback, we throw a more generic error. | ||
throw new Error( | ||
`Yikes, something went wrong! Please try uploading your spec again and if the problem persists, get in touch with our support team at ${chalk.underline( | ||
'[email protected]' | ||
)}.` | ||
); | ||
}); | ||
} | ||
|
||
const registryUUID = await streamSpecToRegistry(bundledSpec); | ||
const registryUUID = await streamSpecToRegistry(bundledSpec); | ||
|
||
const options = { | ||
headers: cleanHeaders(key, { | ||
Accept: 'application/json', | ||
'Content-Type': 'application/json', | ||
'x-readme-version': selectedVersion, | ||
}), | ||
body: JSON.stringify({ registryUUID }), | ||
}; | ||
|
||
function createSpec() { | ||
options.method = 'post'; | ||
spinner.start('Creating your API docs in ReadMe...'); | ||
return fetch(`${config.get('host')}/api/v1/api-specification`, options).then(res => { | ||
if (res.ok) { | ||
spinner.succeed(`${spinner.text} done! 🦉`); | ||
return success(res); | ||
} | ||
spinner.fail(); | ||
return error(res); | ||
}); | ||
} | ||
|
||
const options = { | ||
headers: cleanHeaders(key, { | ||
Accept: 'application/json', | ||
'Content-Type': 'application/json', | ||
'x-readme-version': versionCleaned, | ||
}), | ||
body: JSON.stringify({ registryUUID }), | ||
}; | ||
|
||
function createSpec() { | ||
options.method = 'post'; | ||
spinner.start('Creating your API docs in ReadMe...'); | ||
return fetch(`${config.get('host')}/api/v1/api-specification`, options).then(res => { | ||
if (res.ok) { | ||
spinner.succeed(`${spinner.text} done! 🦉`); | ||
return success(res); | ||
} | ||
spinner.fail(); | ||
return error(res); | ||
}); | ||
} | ||
|
||
function updateSpec(specId) { | ||
isUpdate = true; | ||
options.method = 'put'; | ||
spinner.start('Updating your API docs in ReadMe...'); | ||
return fetch(`${config.get('host')}/api/v1/api-specification/${specId}`, options).then(res => { | ||
if (res.ok) { | ||
spinner.succeed(`${spinner.text} done! 🦉`); | ||
return success(res); | ||
} | ||
spinner.fail(); | ||
return error(res); | ||
}); | ||
} | ||
|
||
/* | ||
function updateSpec(specId) { | ||
isUpdate = true; | ||
options.method = 'put'; | ||
spinner.start('Updating your API docs in ReadMe...'); | ||
return fetch(`${config.get('host')}/api/v1/api-specification/${specId}`, options).then(res => { | ||
if (res.ok) { | ||
spinner.succeed(`${spinner.text} done! 🦉`); | ||
return success(res); | ||
} | ||
spinner.fail(); | ||
return error(res); | ||
}); | ||
} | ||
|
||
/* | ||
Create a new OAS file in Readme: | ||
- Enter flow if user does not pass an id as cli arg | ||
- Check to see if any existing files exist with a specific version | ||
- If none exist, default to creating a new instance of a spec | ||
- If found, prompt user to either create a new spec or update an existing one | ||
*/ | ||
|
||
function getSpecs(url) { | ||
return fetch(`${config.get('host')}${url}`, { | ||
method: 'get', | ||
headers: cleanHeaders(key, { | ||
'x-readme-version': versionCleaned, | ||
}), | ||
}); | ||
} | ||
|
||
if (!id) { | ||
debug('no id parameter, retrieving list of API specs'); | ||
const apiSettings = await getSpecs('/api/v1/api-specification'); | ||
|
||
const totalPages = Math.ceil(apiSettings.headers.get('x-total-count') / 10); | ||
const parsedDocs = parse(apiSettings.headers.get('link')); | ||
debug(`total pages: ${totalPages}`); | ||
debug(`pagination result: ${JSON.stringify(parsedDocs)}`); | ||
|
||
const apiSettingsBody = await apiSettings.json(); | ||
debug(`api settings list response payload: ${JSON.stringify(apiSettingsBody)}`); | ||
if (!apiSettingsBody.length) return createSpec(); | ||
|
||
const { option } = await prompt(promptOpts.createOasPrompt(apiSettingsBody, parsedDocs, totalPages, getSpecs)); | ||
debug(`selection result: ${option}`); | ||
if (!option) return null; | ||
return option === 'create' ? createSpec() : updateSpec(option); | ||
} | ||
|
||
/* | ||
Update an existing OAS file in Readme: | ||
- Enter flow if user passes an id as cli arg | ||
*/ | ||
return updateSpec(id); | ||
function getSpecs(url) { | ||
return fetch(`${config.get('host')}${url}`, { | ||
method: 'get', | ||
headers: cleanHeaders(key, { | ||
'x-readme-version': selectedVersion, | ||
}), | ||
}); | ||
} | ||
|
||
if (!id) { | ||
selectedVersion = await getProjectVersion(version, key, true); | ||
} | ||
|
||
debug(`selectedVersion: ${selectedVersion}`); | ||
|
||
if (spec) { | ||
return callApi(spec, selectedVersion); | ||
debug('no id parameter, retrieving list of API specs'); | ||
const apiSettings = await getSpecs('/api/v1/api-specification'); | ||
|
||
const totalPages = Math.ceil(apiSettings.headers.get('x-total-count') / 10); | ||
const parsedDocs = parse(apiSettings.headers.get('link')); | ||
debug(`total pages: ${totalPages}`); | ||
debug(`pagination result: ${JSON.stringify(parsedDocs)}`); | ||
|
||
const apiSettingsBody = await apiSettings.json(); | ||
debug(`api settings list response payload: ${JSON.stringify(apiSettingsBody)}`); | ||
if (!apiSettingsBody.length) return createSpec(); | ||
|
||
const { option } = await prompt(promptOpts.createOasPrompt(apiSettingsBody, parsedDocs, totalPages, getSpecs)); | ||
debug(`selection result: ${option}`); | ||
if (!option) return null; | ||
return option === 'create' ? createSpec() : updateSpec(option); | ||
} | ||
|
||
// If the user didn't supply an API specification, let's try to locate what they've got, and upload | ||
// that. If they don't have any, let's let the user know how they can get one going. | ||
return new Promise((resolve, reject) => { | ||
['swagger.json', 'swagger.yaml', 'swagger.yml', 'openapi.json', 'openapi.yaml', 'openapi.yml'].forEach(file => { | ||
debug(`looking for definition with filename: ${file}`); | ||
if (!fs.existsSync(file)) { | ||
debug(`${file} not found`); | ||
return; | ||
} | ||
|
||
console.info(chalk.yellow(`We found ${file} and are attempting to upload it.`)); | ||
resolve(callApi(file, selectedVersion)); | ||
}); | ||
|
||
reject( | ||
new Error( | ||
"We couldn't find an OpenAPI or Swagger definition.\n\n" + | ||
'Please specify the path to your definition with `rdme openapi ./path/to/api/definition`.' | ||
) | ||
); | ||
}); | ||
/* | ||
Update an existing OAS file in Readme: | ||
- Enter flow if user passes an id as cli arg | ||
*/ | ||
return updateSpec(id); | ||
} | ||
}; |
This file contains 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
Oops, something went wrong.