@scalar/oas-utils
Advanced tools
Comparing version 0.1.0 to 0.1.1
# @scalar/oas-utils | ||
## 0.1.1 | ||
### Patch Changes | ||
- 31aae5e: chore: moved shared types and methods into oas-utils | ||
## 0.1.0 | ||
@@ -4,0 +10,0 @@ |
export { fetchSpecFromUrl } from './fetch-spec'; | ||
export * from './parse'; | ||
export { getExampleFromSchema } from './getExampleFromSchema'; | ||
export { getHarRequest } from './getHarRequest'; | ||
export { getParametersFromOperation } from './getParametersFromOperation'; | ||
export { getRequestBodyFromOperation } from './getRequestBodyFromOperation'; | ||
export { getRequestFromOperation } from './getRequestFromOperation'; | ||
export { json2xml } from './json2xml'; | ||
export { formatJsonOrYamlString, isJsonString, json, parseJsonOrYaml, transformToJson, yaml, } from './parse'; | ||
export { prettyPrintJson } from './prettyPrintJson'; | ||
export * from './types'; | ||
//# sourceMappingURL=index.d.ts.map |
import { parse, stringify } from 'yaml'; | ||
import { AxiosHeaders } from 'axios'; | ||
@@ -92,2 +93,451 @@ const yaml = { | ||
export { fetchSpecFromUrl, formatJsonOrYamlString, isJsonString, json, parseJsonOrYaml, transformToJson, yaml }; | ||
const getExampleFromSchema = (schema, options, level = 0) => { | ||
if (level > 5) { | ||
return null; | ||
} | ||
if (options?.mode === "write" && schema.readOnly) { | ||
return void 0; | ||
} | ||
if (options?.mode === "read" && schema.writeOnly) { | ||
return void 0; | ||
} | ||
if (Array.isArray(schema.examples) && schema.examples.length > 0) { | ||
return schema.examples[0]; | ||
} | ||
if (schema.example !== void 0) { | ||
return schema.example; | ||
} | ||
if (schema.default !== void 0) { | ||
return schema.default; | ||
} | ||
if (schema.enum !== void 0) { | ||
return schema.enum[0]; | ||
} | ||
if (schema.type === "object" || schema.properties !== void 0) { | ||
const response = {}; | ||
if (schema.properties !== void 0) { | ||
Object.keys(schema.properties).forEach((name) => { | ||
const property = schema.properties[name]; | ||
const propertyXmlTagName = options?.xml ? property.xml?.name : void 0; | ||
response[propertyXmlTagName ?? name] = getExampleFromSchema( | ||
property, | ||
options, | ||
level + 1 | ||
); | ||
}); | ||
} | ||
if (schema.anyOf !== void 0) { | ||
Object.assign( | ||
response, | ||
getExampleFromSchema(schema.anyOf[0]), | ||
options, | ||
level + 1 | ||
); | ||
} else if (schema.oneOf !== void 0) { | ||
Object.assign( | ||
response, | ||
getExampleFromSchema(schema.oneOf[0]), | ||
options, | ||
level + 1 | ||
); | ||
} else if (schema.allOf !== void 0) { | ||
Object.assign( | ||
response, | ||
...schema.allOf.map( | ||
(item) => getExampleFromSchema(item, options, level + 1) | ||
) | ||
); | ||
} | ||
if (schema.additionalProperties !== void 0 && schema.additionalProperties !== false) { | ||
const additionalSchema = getExampleFromSchema( | ||
schema.additionalProperties, | ||
options, | ||
level + 1 | ||
); | ||
if (additionalSchema && typeof additionalSchema === "object" && !Array.isArray(additionalSchema)) { | ||
return { | ||
...response, | ||
...getExampleFromSchema( | ||
schema.additionalProperties, | ||
options, | ||
level + 1 | ||
) | ||
}; | ||
} | ||
if (additionalSchema === null) { | ||
return null; | ||
} | ||
return { | ||
...response, | ||
someKey: getExampleFromSchema( | ||
schema.additionalProperties, | ||
options, | ||
level + 1 | ||
) | ||
}; | ||
} | ||
return response; | ||
} | ||
if (schema.type === "array" || schema.items !== void 0) { | ||
const itemsXmlTagName = schema?.items?.xml?.name; | ||
const wrapItems = !!(options?.xml && schema.xml?.wrapped && itemsXmlTagName); | ||
if (schema.example !== void 0) { | ||
return wrapItems ? { [itemsXmlTagName]: schema.example } : schema.example; | ||
} | ||
if (schema.items) { | ||
const rules = ["anyOf", "oneOf", "allOf"]; | ||
for (const rule of rules) { | ||
if (!schema.items[rule]) { | ||
continue; | ||
} | ||
const schemas = ["anyOf", "oneOf"].includes(rule) ? ( | ||
// Use the first item only | ||
schema.items[rule].slice(0, 1) | ||
) : ( | ||
// Use all items | ||
schema.items[rule] | ||
); | ||
const exampleFromRule = schemas.map( | ||
(item) => getExampleFromSchema(item, options, level + 1) | ||
); | ||
return wrapItems ? [{ [itemsXmlTagName]: exampleFromRule }] : exampleFromRule; | ||
} | ||
} | ||
if (schema.items?.type) { | ||
const exampleFromSchema = getExampleFromSchema( | ||
schema.items, | ||
options, | ||
level + 1 | ||
); | ||
return wrapItems ? [{ [itemsXmlTagName]: exampleFromSchema }] : [exampleFromSchema]; | ||
} | ||
return []; | ||
} | ||
const exampleValues = { | ||
string: options?.emptyString ?? "", | ||
boolean: true, | ||
integer: schema.min ?? 1, | ||
number: schema.min ?? 1, | ||
array: [] | ||
}; | ||
if (schema.type !== void 0 && exampleValues[schema.type] !== void 0) { | ||
return exampleValues[schema.type]; | ||
} | ||
if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) { | ||
const firstOneOfItem = schema.oneOf[0]; | ||
return getExampleFromSchema(firstOneOfItem, options, level + 1); | ||
} | ||
if (Array.isArray(schema.allOf)) { | ||
let example = null; | ||
schema.allOf.forEach((allOfItem) => { | ||
const newExample = getExampleFromSchema(allOfItem, options, level + 1); | ||
example = typeof newExample === "object" && typeof example === "object" ? { | ||
...example ?? {}, | ||
...newExample | ||
} : Array.isArray(newExample) && Array.isArray(example) ? [...example ?? {}, ...newExample] : newExample; | ||
}); | ||
return example; | ||
} | ||
console.warn(`[getExampleFromSchema] Unknown property type "${schema.type}".`); | ||
return null; | ||
}; | ||
const getHarRequest = (...requests) => { | ||
let mergedRequests = { | ||
httpVersion: "1.1", | ||
method: "GET", | ||
url: "", | ||
path: "", | ||
headers: [], | ||
headersSize: -1, | ||
queryString: [], | ||
cookies: [], | ||
bodySize: -1 | ||
}; | ||
requests.forEach((request) => { | ||
mergedRequests = { | ||
...mergedRequests, | ||
...request, | ||
headers: [...mergedRequests.headers, ...request.headers ?? []], | ||
queryString: [ | ||
...mergedRequests.queryString, | ||
...request.queryString ?? [] | ||
], | ||
cookies: [...mergedRequests.cookies, ...request.cookies ?? []] | ||
}; | ||
}); | ||
const headersObj = mergedRequests.headers.reduce( | ||
(obj, { name, value }) => { | ||
obj[name] = value; | ||
return obj; | ||
}, | ||
{} | ||
); | ||
const normalizedAxiosHeaders = AxiosHeaders.from(headersObj).normalize(true); | ||
mergedRequests.headers = Object.entries(normalizedAxiosHeaders).map( | ||
([name, value]) => ({ name, value }) | ||
); | ||
const { path, ...result } = mergedRequests; | ||
if (path) { | ||
return { | ||
...result, | ||
url: `${mergedRequests.url}${path}` | ||
}; | ||
} | ||
return result; | ||
}; | ||
function getParametersFromOperation(operation, where, requiredOnly = true) { | ||
const parameters = [ | ||
...operation.pathParameters || [], | ||
...operation.information?.parameters || [] | ||
]; | ||
const params = parameters.filter((parameter) => parameter.in === where).filter( | ||
(parameter) => requiredOnly && parameter.required || !requiredOnly | ||
).map((parameter) => ({ | ||
name: parameter.name, | ||
description: parameter.description ?? null, | ||
value: parameter.example ? parameter.example : parameter.schema ? getExampleFromSchema(parameter.schema, { mode: "write" }) : "", | ||
required: parameter.required ?? false, | ||
enabled: parameter.required ?? false | ||
})); | ||
return params.sort((a, b) => { | ||
if (a.required && !b.required) { | ||
return -1; | ||
} else if (!a.required && b.required) { | ||
return 1; | ||
} | ||
return 0; | ||
}); | ||
} | ||
function json2xml(data, tab) { | ||
const toXml = function(value, key, indentation) { | ||
let xml2 = ""; | ||
if (value instanceof Array) { | ||
for (let i = 0, n = value.length; i < n; i++) { | ||
xml2 += indentation + toXml(value[i], key, indentation + " ") + "\n"; | ||
} | ||
} else if (typeof value == "object") { | ||
let hasChild = false; | ||
xml2 += indentation + "<" + key; | ||
for (const m in value) { | ||
if (m.charAt(0) == "@") | ||
xml2 += " " + m.substr(1) + '="' + value[m].toString() + '"'; | ||
else | ||
hasChild = true; | ||
} | ||
xml2 += hasChild ? ">" : "/>"; | ||
if (hasChild) { | ||
for (const m in value) { | ||
if (m == "#text") | ||
xml2 += value[m]; | ||
else if (m == "#cdata") | ||
xml2 += "<![CDATA[" + value[m] + "]]>"; | ||
else if (m.charAt(0) != "@") | ||
xml2 += toXml(value[m], m, indentation + " "); | ||
} | ||
xml2 += (xml2.charAt(xml2.length - 1) == "\n" ? indentation : "") + "</" + key + ">"; | ||
} | ||
} else { | ||
xml2 += indentation + "<" + key + ">" + value.toString() + "</" + key + ">"; | ||
} | ||
return xml2; | ||
}; | ||
let xml = ""; | ||
for (const key in data) { | ||
xml += toXml(data[key], key, ""); | ||
} | ||
return tab ? xml.replace(/\t/g, tab) : xml.replace(/\t|\n/g, ""); | ||
} | ||
const prettyPrintJson = (value) => { | ||
try { | ||
if (typeof value === "string") { | ||
return JSON.stringify(JSON.parse(value), null, 2); | ||
} else { | ||
return JSON.stringify(value, null, 2); | ||
} | ||
} catch { | ||
console.log("[prettyPrintJson] Error parsing JSON", value); | ||
return value; | ||
} | ||
}; | ||
function getRequestBodyFromOperation(operation, selectedExampleKey) { | ||
const mimeTypes = [ | ||
"application/json", | ||
"application/octet-stream", | ||
"application/x-www-form-urlencoded", | ||
"application/xml", | ||
"multipart/form-data", | ||
"text/plain" | ||
]; | ||
const mimeType = mimeTypes.find( | ||
(currentMimeType) => !!operation.information?.requestBody?.content?.[currentMimeType] | ||
); | ||
const examples = operation.information?.requestBody?.content?.["application/json"]?.examples; | ||
const selectedExample = (examples ?? {})?.[selectedExampleKey ?? Object.keys(examples ?? {})[0]]; | ||
if (selectedExample) { | ||
return { | ||
postData: { | ||
mimeType: "application/json", | ||
text: prettyPrintJson(selectedExample?.value) | ||
} | ||
}; | ||
} | ||
const bodyParameters = getParametersFromOperation(operation, "body", false); | ||
if (bodyParameters.length > 0) { | ||
return { | ||
postData: { | ||
mimeType: "application/json", | ||
text: prettyPrintJson(bodyParameters[0].value) | ||
} | ||
}; | ||
} | ||
const formDataParameters = getParametersFromOperation( | ||
operation, | ||
"formData", | ||
false | ||
); | ||
if (formDataParameters.length > 0) { | ||
return { | ||
postData: { | ||
mimeType: "application/x-www-form-urlencoded", | ||
params: formDataParameters.map((parameter) => ({ | ||
name: parameter.name, | ||
value: parameter.value | ||
})) | ||
} | ||
}; | ||
} | ||
if (!mimeType) { | ||
return { | ||
postData: void 0 | ||
}; | ||
} | ||
const requestBodyObject = operation.information?.requestBody?.content?.[mimeType]; | ||
const headers = [ | ||
{ | ||
name: "Content-Type", | ||
value: mimeType | ||
} | ||
]; | ||
const example = requestBodyObject?.example ? requestBodyObject?.example : void 0; | ||
if (mimeType === "application/json") { | ||
const exampleFromSchema = requestBodyObject?.schema ? getExampleFromSchema(requestBodyObject?.schema, { mode: "write" }) : null; | ||
const body = example ?? exampleFromSchema; | ||
return { | ||
headers, | ||
postData: { | ||
mimeType, | ||
text: typeof body === "string" ? body : JSON.stringify(body, null, 2) | ||
} | ||
}; | ||
} | ||
if (mimeType === "application/xml") { | ||
const exampleFromSchema = requestBodyObject?.schema ? getExampleFromSchema(requestBodyObject?.schema, { | ||
xml: true, | ||
mode: "write" | ||
}) : null; | ||
return { | ||
headers, | ||
postData: { | ||
mimeType, | ||
text: example ?? json2xml(exampleFromSchema, " ") | ||
} | ||
}; | ||
} | ||
if (mimeType === "application/octet-stream") { | ||
return { | ||
headers, | ||
postData: { | ||
mimeType, | ||
text: "BINARY" | ||
} | ||
}; | ||
} | ||
if (mimeType === "text/plain") { | ||
const exampleFromSchema = requestBodyObject?.schema ? getExampleFromSchema(requestBodyObject?.schema, { | ||
xml: true, | ||
mode: "write" | ||
}) : null; | ||
return { | ||
headers, | ||
postData: { | ||
mimeType, | ||
text: example ?? exampleFromSchema ?? "" | ||
} | ||
}; | ||
} | ||
if (mimeType === "application/x-www-form-urlencoded") { | ||
return { | ||
headers, | ||
postData: { | ||
mimeType | ||
// TODO: We have an object, but how do we get that kind of array from the object? | ||
// Don’t forget to include nested properties … :| | ||
// params: [ | ||
// { | ||
// name: 'foo', | ||
// value: 'bar', | ||
// }, | ||
// ], | ||
} | ||
}; | ||
} | ||
if (mimeType === "multipart/form-data") { | ||
return { | ||
headers, | ||
postData: { | ||
mimeType | ||
// TODO: We have an object, but how do we get that kind of array from the object? | ||
// Don’t forget to include nested properties … :| | ||
// params: [ | ||
// { | ||
// name: 'foo', | ||
// value: 'bar', | ||
// }, | ||
// ], | ||
} | ||
}; | ||
} | ||
return void 0; | ||
} | ||
const getRequestFromOperation = (operation, options, selectedExampleKey) => { | ||
let path = operation.path; | ||
if (options?.replaceVariables === true) { | ||
const pathVariables = path.match(/{(.*?)}/g); | ||
if (pathVariables) { | ||
pathVariables.forEach((variable) => { | ||
const variableName = variable.replace(/{|}/g, ""); | ||
path = path.replace(variable, `__${variableName.toUpperCase()}__`); | ||
}); | ||
} | ||
} | ||
const requestBody = getRequestBodyFromOperation(operation, selectedExampleKey); | ||
return { | ||
method: operation.httpVerb.toUpperCase(), | ||
path, | ||
headers: [ | ||
...getParametersFromOperation(operation, "header", options?.requiredOnly), | ||
...requestBody?.headers ?? [] | ||
], | ||
// TODO: Sorry, something is off here and I don’t get it. | ||
// @ts-ignore | ||
postData: requestBody?.postData, | ||
queryString: getParametersFromOperation( | ||
operation, | ||
"query", | ||
options?.requiredOnly | ||
), | ||
cookies: getParametersFromOperation( | ||
operation, | ||
"cookie", | ||
options?.requiredOnly | ||
) | ||
}; | ||
}; | ||
export { fetchSpecFromUrl, formatJsonOrYamlString, getExampleFromSchema, getHarRequest, getParametersFromOperation, getRequestBodyFromOperation, getRequestFromOperation, isJsonString, json, json2xml, parseJsonOrYaml, prettyPrintJson, transformToJson, yaml }; |
@@ -0,3 +1,106 @@ | ||
import { type OpenAPIV3 } from '@scalar/openapi-parser'; | ||
import type { HarRequest } from 'httpsnippet-lite'; | ||
export type AnyObject = Record<string, any>; | ||
export type AnyStringOrObject = string | Record<string, any>; | ||
export type BaseParameter = { | ||
name: string; | ||
description?: string | null; | ||
value: string | number | Record<string, any>; | ||
required?: boolean; | ||
enabled: boolean; | ||
}; | ||
export type ContentType = 'application/json' | 'application/xml' | 'text/plain' | 'text/html' | 'application/octet-stream' | 'application/x-www-form-urlencoded' | 'multipart/form-data'; | ||
export type Cookie = { | ||
name: string; | ||
value: string; | ||
}; | ||
export type CustomRequestExample = { | ||
lang: string; | ||
label: string; | ||
source: string; | ||
}; | ||
export type HarRequestWithPath = HarRequest & { | ||
path: string; | ||
}; | ||
export type Header = { | ||
name: string; | ||
value: string; | ||
}; | ||
export type Information = { | ||
'description'?: string; | ||
'operationId'?: string | number; | ||
'parameters'?: Parameters[]; | ||
'responses'?: Record<string, ScalarResponse>; | ||
'security'?: OpenAPIV3.SecurityRequirementObject[]; | ||
'requestBody'?: RequestBody; | ||
'summary'?: string; | ||
'tags'?: string[]; | ||
'deprecated'?: boolean; | ||
/** | ||
* Scalar | ||
**/ | ||
'x-custom-examples'?: CustomRequestExample[]; | ||
/** | ||
* Redocly, current | ||
**/ | ||
'x-codeSamples'?: CustomRequestExample[]; | ||
/** | ||
* Redocly, deprecated | ||
**/ | ||
'x-code-samples'?: CustomRequestExample[]; | ||
}; | ||
export type Operation = { | ||
httpVerb: string; | ||
path: string; | ||
operationId?: string; | ||
name?: string; | ||
description?: string; | ||
information?: Information; | ||
}; | ||
export type Parameters = { | ||
name: string; | ||
in?: string; | ||
description?: string; | ||
required?: boolean; | ||
deprecated?: boolean; | ||
allowEmptyValue?: boolean; | ||
style?: 'form' | 'simple'; | ||
explode?: boolean; | ||
allowReserved?: boolean; | ||
schema?: Schema; | ||
example?: any; | ||
examples?: Map<string, any>; | ||
}; | ||
export type Query = { | ||
name: string; | ||
value: string; | ||
}; | ||
export type RequestBodyMimeTypes = { | ||
[K in ContentType]?: { | ||
schema?: any; | ||
example?: any; | ||
examples?: any; | ||
}; | ||
}; | ||
export type ScalarResponse = { | ||
description: string; | ||
content: any; | ||
}; | ||
export type RequestBody = { | ||
description?: string; | ||
required?: boolean; | ||
content?: RequestBodyMimeTypes; | ||
}; | ||
export type Schema = { | ||
type: string; | ||
name?: string; | ||
example?: any; | ||
default?: any; | ||
format?: string; | ||
description?: string; | ||
properties?: Record<string, Schema>; | ||
}; | ||
export type TransformedOperation = Operation & { | ||
pathParameters?: Parameters[]; | ||
}; | ||
//# sourceMappingURL=types.d.ts.map |
@@ -14,3 +14,3 @@ { | ||
], | ||
"version": "0.1.0", | ||
"version": "0.1.1", | ||
"engines": { | ||
@@ -49,6 +49,9 @@ "node": ">=18" | ||
"devDependencies": { | ||
"@scalar/openapi-parser": "^0.3.2", | ||
"axios": "^1.6.7", | ||
"httpsnippet-lite": "^3.0.5", | ||
"tsc-alias": "^1.8.8", | ||
"vite": "^5.1.1", | ||
"vitest": "^1.2.2", | ||
"@scalar/build-tooling": "0.1.0" | ||
"@scalar/build-tooling": "0.1.1" | ||
}, | ||
@@ -55,0 +58,0 @@ "scripts": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
34784
26
812
7
3