feat(common): add support for post/put body schemas in openapi import (#5322)
Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
parent
3994d9e9a0
commit
bb25c59942
6 changed files with 639 additions and 34 deletions
|
|
@ -26,6 +26,14 @@ export const safeParseJSON: SafeParseJSON = (str, convertToArray = false) =>
|
|||
return data
|
||||
})
|
||||
|
||||
/**
|
||||
* Generates a prettified JSON representation of an object
|
||||
* @param obj The object to get the representation of
|
||||
* @returns The prettified JSON string of the object
|
||||
*/
|
||||
export const prettyPrintJSON = (obj: unknown): O.Option<string> =>
|
||||
O.tryCatch(() => JSON.stringify(obj, null, "\t"))
|
||||
|
||||
/**
|
||||
* Checks if given string is a JSON string
|
||||
* @param str Raw string to be checked
|
||||
|
|
|
|||
|
|
@ -0,0 +1,189 @@
|
|||
import { OpenAPIV2 } from "openapi-types"
|
||||
import * as O from "fp-ts/Option"
|
||||
import { pipe, flow } from "fp-ts/function"
|
||||
import * as A from "fp-ts/Array"
|
||||
import { prettyPrintJSON } from "~/helpers/functional/json"
|
||||
|
||||
type PrimitiveSchemaType = "string" | "integer" | "number" | "boolean"
|
||||
|
||||
type SchemaType = "array" | "object" | PrimitiveSchemaType
|
||||
|
||||
type PrimitiveRequestBodyExample = number | string | boolean
|
||||
|
||||
type RequestBodyExample =
|
||||
| { [name: string]: RequestBodyExample }
|
||||
| Array<RequestBodyExample>
|
||||
| PrimitiveRequestBodyExample
|
||||
|
||||
const getPrimitiveTypePlaceholder = (
|
||||
schemaType: PrimitiveSchemaType
|
||||
): PrimitiveRequestBodyExample => {
|
||||
switch (schemaType) {
|
||||
case "string":
|
||||
return "string"
|
||||
case "integer":
|
||||
case "number":
|
||||
return 1
|
||||
case "boolean":
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const getSchemaTypeFromSchemaObject = (
|
||||
schema: OpenAPIV2.SchemaObject
|
||||
): O.Option<SchemaType> =>
|
||||
pipe(
|
||||
schema.type,
|
||||
O.fromNullable,
|
||||
O.map(
|
||||
(schemaType) =>
|
||||
(Array.isArray(schemaType) ? schemaType[0] : schemaType) as SchemaType
|
||||
)
|
||||
)
|
||||
|
||||
const isSchemaTypePrimitive = (
|
||||
schemaType: string
|
||||
): schemaType is PrimitiveSchemaType =>
|
||||
["string", "integer", "number", "boolean"].includes(schemaType)
|
||||
|
||||
const isSchemaTypeArray = (schemaType: string): schemaType is "array" =>
|
||||
schemaType === "array"
|
||||
|
||||
const isSchemaTypeObject = (schemaType: string): schemaType is "object" =>
|
||||
schemaType === "object"
|
||||
|
||||
const getSampleEnumValueOrPlaceholder = (
|
||||
schema: OpenAPIV2.SchemaObject
|
||||
): RequestBodyExample =>
|
||||
pipe(
|
||||
schema.enum,
|
||||
O.fromNullable,
|
||||
O.map((enums) => enums[0] as RequestBodyExample),
|
||||
O.altW(() =>
|
||||
pipe(
|
||||
schema,
|
||||
getSchemaTypeFromSchemaObject,
|
||||
O.filter(isSchemaTypePrimitive),
|
||||
O.map(getPrimitiveTypePlaceholder)
|
||||
)
|
||||
),
|
||||
O.getOrElseW(() => "")
|
||||
)
|
||||
|
||||
const generateExampleArrayFromOpenAPIV2ItemsObject = (
|
||||
items: OpenAPIV2.ItemsObject
|
||||
): RequestBodyExample => {
|
||||
// Guard against undefined items
|
||||
if (!items || !items.type) {
|
||||
return []
|
||||
}
|
||||
|
||||
// ItemsObject can not hold type "object"
|
||||
// https://swagger.io/specification/v2/#itemsObject
|
||||
|
||||
// TODO : Handle array of objects
|
||||
// https://stackoverflow.com/questions/60490974/how-to-define-an-array-of-objects-in-openapi-2-0
|
||||
|
||||
return pipe(
|
||||
items,
|
||||
O.fromPredicate(
|
||||
flow((items) => items.type as SchemaType, isSchemaTypePrimitive)
|
||||
),
|
||||
O.map(flow(getSampleEnumValueOrPlaceholder, (arrayItem) => [arrayItem])),
|
||||
O.getOrElse(() =>
|
||||
// If the type is not primitive, it is "array"
|
||||
// items property is required if type is array
|
||||
items.items
|
||||
? [
|
||||
generateExampleArrayFromOpenAPIV2ItemsObject(
|
||||
items.items as OpenAPIV2.ItemsObject
|
||||
),
|
||||
]
|
||||
: []
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const generateRequestBodyExampleFromOpenAPIV2BodySchema = (
|
||||
schema: OpenAPIV2.SchemaObject
|
||||
): RequestBodyExample => {
|
||||
if (schema.example) return schema.example as RequestBodyExample
|
||||
|
||||
const primitiveTypeExample = pipe(
|
||||
schema,
|
||||
O.fromPredicate(
|
||||
flow(
|
||||
getSchemaTypeFromSchemaObject,
|
||||
O.map(isSchemaTypePrimitive),
|
||||
O.getOrElseW(() => false) // No schema type found in the schema object, assume non-primitive
|
||||
)
|
||||
),
|
||||
O.map(getSampleEnumValueOrPlaceholder) // Use enum or placeholder to populate primitive field
|
||||
)
|
||||
|
||||
if (O.isSome(primitiveTypeExample)) return primitiveTypeExample.value
|
||||
|
||||
const arrayTypeExample = pipe(
|
||||
schema,
|
||||
O.fromPredicate(
|
||||
flow(
|
||||
getSchemaTypeFromSchemaObject,
|
||||
O.map(isSchemaTypeArray),
|
||||
O.getOrElseW(() => false) // No schema type found in the schema object, assume type to be different from array
|
||||
)
|
||||
),
|
||||
O.map((schema) => schema.items as OpenAPIV2.ItemsObject),
|
||||
O.filter((items) => items != null), // Filter out null/undefined items
|
||||
O.map(generateExampleArrayFromOpenAPIV2ItemsObject)
|
||||
)
|
||||
|
||||
if (O.isSome(arrayTypeExample)) return arrayTypeExample.value
|
||||
|
||||
return pipe(
|
||||
schema,
|
||||
O.fromPredicate(
|
||||
flow(
|
||||
getSchemaTypeFromSchemaObject,
|
||||
O.map(isSchemaTypeObject),
|
||||
O.getOrElseW(() => false)
|
||||
)
|
||||
),
|
||||
O.chain((schema) =>
|
||||
pipe(
|
||||
schema.properties,
|
||||
O.fromNullable,
|
||||
O.map(
|
||||
(properties) =>
|
||||
Object.entries(properties) as [string, OpenAPIV2.SchemaObject][]
|
||||
)
|
||||
)
|
||||
),
|
||||
O.getOrElseW(() => [] as [string, OpenAPIV2.SchemaObject][]),
|
||||
A.reduce(
|
||||
{} as { [name: string]: RequestBodyExample },
|
||||
(aggregatedExample, property) => {
|
||||
const example = generateRequestBodyExampleFromOpenAPIV2BodySchema(
|
||||
property[1]
|
||||
)
|
||||
aggregatedExample[property[0]] = example
|
||||
return aggregatedExample
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export const generateRequestBodyExampleFromOpenAPIV2Body = (
|
||||
op: OpenAPIV2.OperationObject
|
||||
): string =>
|
||||
pipe(
|
||||
(op.parameters ?? []) as OpenAPIV2.Parameter[],
|
||||
A.findFirst((param) => param.in === "body"),
|
||||
O.map(
|
||||
flow(
|
||||
(parameter) => parameter.schema,
|
||||
generateRequestBodyExampleFromOpenAPIV2BodySchema
|
||||
)
|
||||
),
|
||||
O.chain(prettyPrintJSON),
|
||||
O.getOrElse(() => "")
|
||||
)
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
import { OpenAPIV3 } from "openapi-types"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as O from "fp-ts/Option"
|
||||
|
||||
type SchemaType =
|
||||
| OpenAPIV3.ArraySchemaObjectType
|
||||
| OpenAPIV3.NonArraySchemaObjectType
|
||||
|
||||
type PrimitiveSchemaType = Exclude<SchemaType, "array" | "object">
|
||||
|
||||
type PrimitiveRequestBodyExample = string | number | boolean | null
|
||||
|
||||
type RequestBodyExample =
|
||||
| PrimitiveRequestBodyExample
|
||||
| Array<RequestBodyExample>
|
||||
| { [name: string]: RequestBodyExample }
|
||||
|
||||
const isSchemaTypePrimitive = (
|
||||
schemaType: SchemaType
|
||||
): schemaType is PrimitiveSchemaType =>
|
||||
!["array", "object"].includes(schemaType)
|
||||
|
||||
const getPrimitiveTypePlaceholder = (
|
||||
primitiveType: PrimitiveSchemaType
|
||||
): PrimitiveRequestBodyExample => {
|
||||
switch (primitiveType) {
|
||||
case "number":
|
||||
return 0.0
|
||||
case "integer":
|
||||
return 0
|
||||
case "string":
|
||||
return "string"
|
||||
case "boolean":
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Use carefully, call only when type is primitive
|
||||
// TODO(agarwal): Use Enum values, if any
|
||||
const generatePrimitiveRequestBodyExample = (
|
||||
schemaObject: OpenAPIV3.NonArraySchemaObject
|
||||
): RequestBodyExample =>
|
||||
getPrimitiveTypePlaceholder(schemaObject.type as PrimitiveSchemaType)
|
||||
|
||||
// Use carefully, call only when type is object
|
||||
const generateObjectRequestBodyExample = (
|
||||
schemaObject: OpenAPIV3.NonArraySchemaObject
|
||||
): RequestBodyExample =>
|
||||
pipe(
|
||||
schemaObject.properties,
|
||||
O.fromNullable,
|
||||
O.map(Object.entries),
|
||||
O.getOrElseW(() => [] as [string, OpenAPIV3.SchemaObject][]),
|
||||
(entries) =>
|
||||
entries.reduce(
|
||||
(acc, [key, propSchema]) => ({
|
||||
...acc,
|
||||
[key]: generateRequestBodyExampleFromSchemaObject(
|
||||
propSchema as OpenAPIV3.SchemaObject
|
||||
),
|
||||
}),
|
||||
{} as Record<string, RequestBodyExample>
|
||||
)
|
||||
)
|
||||
|
||||
const generateArrayRequestBodyExample = (
|
||||
schemaObject: OpenAPIV3.ArraySchemaObject
|
||||
): RequestBodyExample => [
|
||||
generateRequestBodyExampleFromSchemaObject(
|
||||
schemaObject.items as OpenAPIV3.SchemaObject
|
||||
),
|
||||
]
|
||||
|
||||
const generateRequestBodyExampleFromSchemaObject = (
|
||||
schemaObject: OpenAPIV3.SchemaObject
|
||||
): RequestBodyExample => {
|
||||
// TODO: Handle schema objects with allof
|
||||
if (schemaObject.example) return schemaObject.example as RequestBodyExample
|
||||
|
||||
// If request body can be oneof or allof several schema, choose the first schema to generate an example
|
||||
if (schemaObject.oneOf)
|
||||
return generateRequestBodyExampleFromSchemaObject(
|
||||
schemaObject.oneOf[0] as OpenAPIV3.SchemaObject
|
||||
)
|
||||
if (schemaObject.anyOf)
|
||||
return generateRequestBodyExampleFromSchemaObject(
|
||||
schemaObject.anyOf[0] as OpenAPIV3.SchemaObject
|
||||
)
|
||||
|
||||
if (!schemaObject.type) return ""
|
||||
|
||||
if (isSchemaTypePrimitive(schemaObject.type))
|
||||
return generatePrimitiveRequestBodyExample(
|
||||
schemaObject as OpenAPIV3.NonArraySchemaObject
|
||||
)
|
||||
|
||||
if (schemaObject.type === "object")
|
||||
return generateObjectRequestBodyExample(
|
||||
schemaObject as OpenAPIV3.NonArraySchemaObject
|
||||
)
|
||||
|
||||
return generateArrayRequestBodyExample(
|
||||
schemaObject as OpenAPIV3.ArraySchemaObject
|
||||
)
|
||||
}
|
||||
|
||||
export const generateRequestBodyExampleFromMediaObject = (
|
||||
mediaObject: OpenAPIV3.MediaTypeObject
|
||||
): RequestBodyExample => {
|
||||
// First check for direct example
|
||||
if (mediaObject.example) return mediaObject.example as RequestBodyExample
|
||||
|
||||
// Then check for examples object (OpenAPI v3 format)
|
||||
if (mediaObject.examples) {
|
||||
const firstExample = Object.values(mediaObject.examples)[0]
|
||||
if (
|
||||
firstExample &&
|
||||
typeof firstExample === "object" &&
|
||||
"value" in firstExample
|
||||
) {
|
||||
return firstExample.value as RequestBodyExample
|
||||
}
|
||||
// Fallback if examples doesn't have the expected structure
|
||||
return Object.values(mediaObject.examples)[0] as RequestBodyExample
|
||||
}
|
||||
|
||||
// Fallback to generating from schema
|
||||
return mediaObject.schema
|
||||
? generateRequestBodyExampleFromSchemaObject(
|
||||
mediaObject.schema as OpenAPIV3.SchemaObject
|
||||
)
|
||||
: ""
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
import { OpenAPIV3_1 as OpenAPIV31 } from "openapi-types"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as A from "fp-ts/Array"
|
||||
|
||||
type MixedArraySchemaType = (
|
||||
| OpenAPIV31.ArraySchemaObjectType
|
||||
| OpenAPIV31.NonArraySchemaObjectType
|
||||
)[]
|
||||
|
||||
type SchemaType =
|
||||
| OpenAPIV31.ArraySchemaObjectType
|
||||
| OpenAPIV31.NonArraySchemaObjectType
|
||||
| MixedArraySchemaType
|
||||
|
||||
type PrimitiveSchemaType = Exclude<
|
||||
OpenAPIV31.NonArraySchemaObjectType,
|
||||
"object"
|
||||
>
|
||||
|
||||
type PrimitiveRequestBodyExample = string | number | boolean | null
|
||||
|
||||
type RequestBodyExample =
|
||||
| PrimitiveRequestBodyExample
|
||||
| Array<RequestBodyExample>
|
||||
| { [name: string]: RequestBodyExample }
|
||||
|
||||
const isSchemaTypePrimitive = (
|
||||
schemaType: SchemaType
|
||||
): schemaType is PrimitiveSchemaType =>
|
||||
!Array.isArray(schemaType) && !["array", "object"].includes(schemaType)
|
||||
|
||||
const getPrimitiveTypePlaceholder = (
|
||||
primitiveType: PrimitiveSchemaType
|
||||
): PrimitiveRequestBodyExample => {
|
||||
switch (primitiveType) {
|
||||
case "number":
|
||||
return 0.0
|
||||
case "integer":
|
||||
return 0
|
||||
case "string":
|
||||
return "string"
|
||||
case "boolean":
|
||||
return true
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Use carefully, the schema type should necessarily be primitive
|
||||
// TODO(agarwal): Use Enum values, if any
|
||||
const generatePrimitiveRequestBodyExample = (
|
||||
schemaObject: OpenAPIV31.NonArraySchemaObject
|
||||
): RequestBodyExample =>
|
||||
getPrimitiveTypePlaceholder(schemaObject.type as PrimitiveSchemaType)
|
||||
|
||||
// Use carefully, the schema type should necessarily be object
|
||||
const generateObjectRequestBodyExample = (
|
||||
schemaObject: OpenAPIV31.NonArraySchemaObject
|
||||
): RequestBodyExample =>
|
||||
pipe(
|
||||
schemaObject.properties,
|
||||
O.fromNullable,
|
||||
O.map(
|
||||
(properties) =>
|
||||
Object.entries(properties) as [string, OpenAPIV31.SchemaObject][]
|
||||
),
|
||||
O.getOrElseW(() => [] as [string, OpenAPIV31.SchemaObject][]),
|
||||
A.reduce(
|
||||
{} as { [name: string]: RequestBodyExample },
|
||||
(aggregatedExample, property) => {
|
||||
aggregatedExample[property[0]] =
|
||||
generateRequestBodyExampleFromSchemaObject(property[1])
|
||||
return aggregatedExample
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
// Use carefully, the schema type should necessarily be mixed array
|
||||
const generateMixedArrayRequestBodyExample = (
|
||||
schemaObject: OpenAPIV31.SchemaObject
|
||||
): RequestBodyExample =>
|
||||
pipe(
|
||||
schemaObject,
|
||||
(schemaObject) => schemaObject.type as MixedArraySchemaType,
|
||||
A.reduce([] as Array<RequestBodyExample>, (aggregatedExample, itemType) => {
|
||||
// TODO: Figure out how to include non-primitive types as well
|
||||
if (isSchemaTypePrimitive(itemType)) {
|
||||
aggregatedExample.push(getPrimitiveTypePlaceholder(itemType))
|
||||
}
|
||||
return aggregatedExample
|
||||
})
|
||||
)
|
||||
|
||||
const generateArrayRequestBodyExample = (
|
||||
schemaObject: OpenAPIV31.ArraySchemaObject
|
||||
): RequestBodyExample => [
|
||||
generateRequestBodyExampleFromSchemaObject(
|
||||
schemaObject.items as OpenAPIV31.SchemaObject
|
||||
),
|
||||
]
|
||||
|
||||
const generateRequestBodyExampleFromSchemaObject = (
|
||||
schemaObject: OpenAPIV31.SchemaObject
|
||||
): RequestBodyExample => {
|
||||
// TODO: Handle schema objects with oneof or anyof
|
||||
if (schemaObject.example) return schemaObject.example as RequestBodyExample
|
||||
if (schemaObject.examples)
|
||||
return schemaObject.examples[0] as RequestBodyExample
|
||||
if (!schemaObject.type) return ""
|
||||
if (isSchemaTypePrimitive(schemaObject.type))
|
||||
return generatePrimitiveRequestBodyExample(
|
||||
schemaObject as OpenAPIV31.NonArraySchemaObject
|
||||
)
|
||||
if (schemaObject.type === "object")
|
||||
return generateObjectRequestBodyExample(schemaObject)
|
||||
if (schemaObject.type === "array")
|
||||
return generateArrayRequestBodyExample(schemaObject)
|
||||
return generateMixedArrayRequestBodyExample(schemaObject)
|
||||
}
|
||||
|
||||
export const generateRequestBodyExampleFromMediaObject = (
|
||||
mediaObject: OpenAPIV31.MediaTypeObject
|
||||
): RequestBodyExample => {
|
||||
// First check for direct example
|
||||
if (mediaObject.example) return mediaObject.example as RequestBodyExample
|
||||
|
||||
// Then check for examples object (OpenAPI v3.1 format)
|
||||
if (mediaObject.examples) {
|
||||
const firstExample = Object.values(mediaObject.examples)[0]
|
||||
if (
|
||||
firstExample &&
|
||||
typeof firstExample === "object" &&
|
||||
"value" in firstExample
|
||||
) {
|
||||
return firstExample.value as RequestBodyExample
|
||||
}
|
||||
// Fallback if examples doesn't have the expected structure
|
||||
return Object.values(mediaObject.examples)[0] as RequestBodyExample
|
||||
}
|
||||
|
||||
// Fallback to generating from schema
|
||||
return mediaObject.schema
|
||||
? generateRequestBodyExampleFromSchemaObject(mediaObject.schema)
|
||||
: ""
|
||||
}
|
||||
|
|
@ -29,15 +29,18 @@ import * as O from "fp-ts/Option"
|
|||
import * as TE from "fp-ts/TaskEither"
|
||||
import * as RA from "fp-ts/ReadonlyArray"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||
import { IMPORTER_INVALID_FILE_FORMAT } from ".."
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import { getStatusCodeReasonPhrase } from "~/helpers/utils/statusCodes"
|
||||
import { isNumeric } from "~/helpers/utils/number"
|
||||
import { generateRequestBodyExampleFromOpenAPIV2Body } from "./example-generators/v2"
|
||||
import { generateRequestBodyExampleFromMediaObject as generateV3Example } from "./example-generators/v3"
|
||||
import { generateRequestBodyExampleFromMediaObject as generateV31Example } from "./example-generators/v31"
|
||||
|
||||
export const OPENAPI_DEREF_ERROR = "openapi/deref_error" as const
|
||||
|
||||
const worker = new Worker(
|
||||
new URL("./workers/openapi-import-worker.ts", import.meta.url),
|
||||
new URL("../workers/openapi-import-worker.ts", import.meta.url),
|
||||
{
|
||||
type: "module",
|
||||
}
|
||||
|
|
@ -299,38 +302,55 @@ const parseOpenAPIV2Body = (op: OpenAPIV2.OperationObject): HoppRESTReqBody => {
|
|||
if (!obj || !(obj in knownContentTypes))
|
||||
return { contentType: null, body: null }
|
||||
|
||||
// Textual Content Types, so we just parse it and keep
|
||||
// For form data types, extract form fields
|
||||
if (
|
||||
obj !== "multipart/form-data" &&
|
||||
obj !== "application/x-www-form-urlencoded"
|
||||
)
|
||||
return { contentType: obj as any, body: "" }
|
||||
obj === "multipart/form-data" ||
|
||||
obj === "application/x-www-form-urlencoded"
|
||||
) {
|
||||
const formDataValues = pipe(
|
||||
(op.parameters ?? []) as OpenAPIV2.Parameter[],
|
||||
|
||||
const formDataValues = pipe(
|
||||
(op.parameters ?? []) as OpenAPIV2.Parameter[],
|
||||
|
||||
A.filterMap(
|
||||
flow(
|
||||
O.fromPredicate((param) => param.in === "body"),
|
||||
O.map(
|
||||
(param) =>
|
||||
<FormDataKeyValue>{
|
||||
key: param.name,
|
||||
isFile: false,
|
||||
value: "",
|
||||
active: true,
|
||||
}
|
||||
A.filterMap(
|
||||
flow(
|
||||
O.fromPredicate((param) => param.in === "formData"),
|
||||
O.map(
|
||||
(param) =>
|
||||
<FormDataKeyValue>{
|
||||
key: param.name,
|
||||
isFile: param.type === "file",
|
||||
value: "",
|
||||
active: true,
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return obj === "application/x-www-form-urlencoded"
|
||||
? {
|
||||
contentType: obj,
|
||||
body: formDataValues.map(({ key }) => `${key}: `).join("\n"),
|
||||
return obj === "application/x-www-form-urlencoded"
|
||||
? {
|
||||
contentType: obj,
|
||||
body: formDataValues.map(({ key }) => `${key}: `).join("\n"),
|
||||
}
|
||||
: { contentType: obj, body: formDataValues }
|
||||
}
|
||||
|
||||
// For other content types (JSON, XML, etc.)
|
||||
const bodyParam = (op.parameters ?? []).find(
|
||||
(param) => (param as OpenAPIV2.Parameter).in === "body"
|
||||
) as OpenAPIV2.InBodyParameterObject | undefined
|
||||
|
||||
if (bodyParam) {
|
||||
const result = generateRequestBodyExampleFromOpenAPIV2Body(op)
|
||||
if (result) {
|
||||
return {
|
||||
contentType: obj as any,
|
||||
body: result,
|
||||
}
|
||||
: { contentType: obj, body: formDataValues }
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to empty body for textual content types
|
||||
return { contentType: obj as any, body: "" }
|
||||
}
|
||||
|
||||
const parseOpenAPIV3BodyFormData = (
|
||||
|
|
@ -365,6 +385,7 @@ const parseOpenAPIV3BodyFormData = (
|
|||
}
|
||||
|
||||
const parseOpenAPIV3Body = (
|
||||
doc: OpenAPI.Document,
|
||||
op: OpenAPIV3.OperationObject | OpenAPIV31.OperationObject
|
||||
): HoppRESTReqBody => {
|
||||
const objs = Object.entries(
|
||||
|
|
@ -384,12 +405,83 @@ const parseOpenAPIV3Body = (
|
|||
OpenAPIV3.MediaTypeObject | OpenAPIV31.MediaTypeObject,
|
||||
] = objs[0]
|
||||
|
||||
return contentType in knownContentTypes
|
||||
? contentType === "multipart/form-data" ||
|
||||
contentType === "application/x-www-form-urlencoded"
|
||||
? parseOpenAPIV3BodyFormData(contentType, media)
|
||||
: { contentType: contentType as any, body: "" }
|
||||
: { contentType: null, body: null }
|
||||
if (!(contentType in knownContentTypes))
|
||||
return { contentType: null, body: null }
|
||||
|
||||
// Handle form data types
|
||||
if (
|
||||
contentType === "multipart/form-data" ||
|
||||
contentType === "application/x-www-form-urlencoded"
|
||||
)
|
||||
return parseOpenAPIV3BodyFormData(contentType, media)
|
||||
|
||||
// For other content types (JSON, XML, etc.), try to generate sample from schema
|
||||
if (media.schema) {
|
||||
try {
|
||||
const docAny = doc as any
|
||||
const isV31 = docAny.openapi && docAny.openapi.startsWith("3.1")
|
||||
|
||||
let sampleBody: any
|
||||
if (isV31) {
|
||||
sampleBody = generateV31Example(media as any)
|
||||
} else {
|
||||
sampleBody = generateV3Example(media as any)
|
||||
}
|
||||
|
||||
return {
|
||||
contentType: contentType as any,
|
||||
body:
|
||||
typeof sampleBody === "string"
|
||||
? sampleBody
|
||||
: JSON.stringify(sampleBody, null, 2),
|
||||
}
|
||||
} catch (e) {
|
||||
// If we can't generate a sample, check for examples
|
||||
if (media.example !== undefined) {
|
||||
return {
|
||||
contentType: contentType as any,
|
||||
body:
|
||||
typeof media.example === "string"
|
||||
? media.example
|
||||
: JSON.stringify(media.example, null, 2),
|
||||
}
|
||||
}
|
||||
// Fallback to empty body
|
||||
return { contentType: contentType as any, body: "" }
|
||||
}
|
||||
}
|
||||
|
||||
// Check for examples if no schema
|
||||
if (media.example !== undefined) {
|
||||
return {
|
||||
contentType: contentType as any,
|
||||
body:
|
||||
typeof media.example === "string"
|
||||
? media.example
|
||||
: JSON.stringify(media.example, null, 2),
|
||||
}
|
||||
}
|
||||
|
||||
// Check for examples array (OpenAPI v3 supports multiple examples)
|
||||
if (media.examples && Object.keys(media.examples).length > 0) {
|
||||
const firstExampleKey = Object.keys(media.examples)[0]
|
||||
const firstExample = media.examples[firstExampleKey]
|
||||
|
||||
// Handle both Example Object and Reference Object
|
||||
const exampleValue =
|
||||
"value" in firstExample ? firstExample.value : firstExample
|
||||
|
||||
return {
|
||||
contentType: contentType as any,
|
||||
body:
|
||||
typeof exampleValue === "string"
|
||||
? exampleValue
|
||||
: JSON.stringify(exampleValue, null, 2),
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to empty body for textual content types
|
||||
return { contentType: contentType as any, body: "" }
|
||||
}
|
||||
|
||||
const isOpenAPIV3Operation = (
|
||||
|
|
@ -405,7 +497,7 @@ const parseOpenAPIBody = (
|
|||
op: OpenAPIOperationType
|
||||
): HoppRESTReqBody =>
|
||||
isOpenAPIV3Operation(doc, op)
|
||||
? parseOpenAPIV3Body(op)
|
||||
? parseOpenAPIV3Body(doc, op)
|
||||
: parseOpenAPIV2Body(op)
|
||||
|
||||
const resolveOpenAPIV3SecurityObj = (
|
||||
|
|
@ -455,6 +547,9 @@ const resolveOpenAPIV3SecurityObj = (
|
|||
isPKCE: false,
|
||||
tokenEndpoint: scheme.flows.authorizationCode.tokenUrl ?? "",
|
||||
clientSecret: "",
|
||||
authRequestParams: [],
|
||||
refreshRequestParams: [],
|
||||
tokenRequestParams: [],
|
||||
},
|
||||
addTo: "HEADERS",
|
||||
}
|
||||
|
|
@ -468,6 +563,8 @@ const resolveOpenAPIV3SecurityObj = (
|
|||
clientID: "",
|
||||
token: "",
|
||||
scopes: _schemeData.join(" "),
|
||||
authRequestParams: [],
|
||||
refreshRequestParams: [],
|
||||
},
|
||||
addTo: "HEADERS",
|
||||
}
|
||||
|
|
@ -484,6 +581,8 @@ const resolveOpenAPIV3SecurityObj = (
|
|||
username: "",
|
||||
token: "",
|
||||
scopes: _schemeData.join(" "),
|
||||
refreshRequestParams: [],
|
||||
tokenRequestParams: [],
|
||||
},
|
||||
addTo: "HEADERS",
|
||||
}
|
||||
|
|
@ -499,6 +598,8 @@ const resolveOpenAPIV3SecurityObj = (
|
|||
scopes: _schemeData.join(" "),
|
||||
token: "",
|
||||
clientAuthentication: "IN_BODY",
|
||||
refreshRequestParams: [],
|
||||
tokenRequestParams: [],
|
||||
},
|
||||
addTo: "HEADERS",
|
||||
}
|
||||
|
|
@ -515,6 +616,9 @@ const resolveOpenAPIV3SecurityObj = (
|
|||
isPKCE: false,
|
||||
tokenEndpoint: "",
|
||||
clientSecret: "",
|
||||
authRequestParams: [],
|
||||
refreshRequestParams: [],
|
||||
tokenRequestParams: [],
|
||||
},
|
||||
addTo: "HEADERS",
|
||||
}
|
||||
|
|
@ -531,6 +635,9 @@ const resolveOpenAPIV3SecurityObj = (
|
|||
isPKCE: false,
|
||||
tokenEndpoint: "",
|
||||
clientSecret: "",
|
||||
authRequestParams: [],
|
||||
refreshRequestParams: [],
|
||||
tokenRequestParams: [],
|
||||
},
|
||||
addTo: "HEADERS",
|
||||
}
|
||||
|
|
@ -618,6 +725,9 @@ const resolveOpenAPIV2SecurityScheme = (
|
|||
token: "",
|
||||
isPKCE: false,
|
||||
tokenEndpoint: scheme.tokenUrl ?? "",
|
||||
authRequestParams: [],
|
||||
refreshRequestParams: [],
|
||||
tokenRequestParams: [],
|
||||
},
|
||||
addTo: "HEADERS",
|
||||
}
|
||||
|
|
@ -631,6 +741,8 @@ const resolveOpenAPIV2SecurityScheme = (
|
|||
grantType: "IMPLICIT",
|
||||
scopes: _schemeData.join(" "),
|
||||
token: "",
|
||||
authRequestParams: [],
|
||||
refreshRequestParams: [],
|
||||
},
|
||||
addTo: "HEADERS",
|
||||
}
|
||||
|
|
@ -646,6 +758,8 @@ const resolveOpenAPIV2SecurityScheme = (
|
|||
scopes: _schemeData.join(" "),
|
||||
token: "",
|
||||
clientAuthentication: "IN_BODY",
|
||||
refreshRequestParams: [],
|
||||
tokenRequestParams: [],
|
||||
},
|
||||
addTo: "HEADERS",
|
||||
}
|
||||
|
|
@ -662,6 +776,8 @@ const resolveOpenAPIV2SecurityScheme = (
|
|||
scopes: _schemeData.join(" "),
|
||||
token: "",
|
||||
username: "",
|
||||
refreshRequestParams: [],
|
||||
tokenRequestParams: [],
|
||||
},
|
||||
addTo: "HEADERS",
|
||||
}
|
||||
|
|
@ -678,6 +794,9 @@ const resolveOpenAPIV2SecurityScheme = (
|
|||
token: "",
|
||||
isPKCE: false,
|
||||
tokenEndpoint: "",
|
||||
authRequestParams: [],
|
||||
refreshRequestParams: [],
|
||||
tokenRequestParams: [],
|
||||
},
|
||||
addTo: "HEADERS",
|
||||
}
|
||||
11
packages/hoppscotch-sh-admin/src/components.d.ts
vendored
11
packages/hoppscotch-sh-admin/src/components.d.ts
vendored
|
|
@ -33,8 +33,10 @@ declare module 'vue' {
|
|||
HoppSmartTable: typeof import('@hoppscotch/ui')['HoppSmartTable']
|
||||
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
|
||||
HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle']
|
||||
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
|
||||
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
||||
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
|
||||
IconLucideBadgeCheck: typeof import('~icons/lucide/badge-check')['default']
|
||||
IconLucideCheck: typeof import('~icons/lucide/check')['default']
|
||||
IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default']
|
||||
IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default']
|
||||
|
|
@ -42,13 +44,21 @@ declare module 'vue' {
|
|||
IconLucideInfo: typeof import('~icons/lucide/info')['default']
|
||||
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
||||
IconLucideUser: typeof import('~icons/lucide/user')['default']
|
||||
OnboardingAuthProviderCard: typeof import('./components/onboarding/AuthProviderCard.vue')['default']
|
||||
OnboardingAuthSetup: typeof import('./components/onboarding/AuthSetup.vue')['default']
|
||||
OnboardingCompleteScreen: typeof import('./components/onboarding/CompleteScreen.vue')['default']
|
||||
OnboardingOAuthSetup: typeof import('./components/onboarding/OAuthSetup.vue')['default']
|
||||
OnboardingSmtpSetup: typeof import('./components/onboarding/SmtpSetup.vue')['default']
|
||||
OnboardingWelcomeScreen: typeof import('./components/onboarding/WelcomeScreen.vue')['default']
|
||||
SettingsAuthConfiguration: typeof import('./components/settings/AuthConfiguration.vue')['default']
|
||||
SettingsAuthConfigurations: typeof import('./components/settings/AuthConfigurations.vue')['default']
|
||||
SettingsAuthProvider: typeof import('./components/settings/AuthProvider.vue')['default']
|
||||
SettingsAuthToken: typeof import('./components/settings/AuthToken.vue')['default']
|
||||
SettingsConfigurations: typeof import('./components/settings/Configurations.vue')['default']
|
||||
SettingsDataSharing: typeof import('./components/settings/DataSharing.vue')['default']
|
||||
SettingsHistoryConfiguration: typeof import('./components/settings/HistoryConfiguration.vue')['default']
|
||||
SettingsOAuthProviderConfigurations: typeof import('./components/settings/OAuthProviderConfigurations.vue')['default']
|
||||
SettingsRateLimit: typeof import('./components/settings/RateLimit.vue')['default']
|
||||
SettingsReset: typeof import('./components/settings/Reset.vue')['default']
|
||||
SettingsServerRestart: typeof import('./components/settings/ServerRestart.vue')['default']
|
||||
SettingsSmtpConfiguration: typeof import('./components/settings/SmtpConfiguration.vue')['default']
|
||||
|
|
@ -63,6 +73,7 @@ declare module 'vue' {
|
|||
TokensGenerateModal: typeof import('./components/tokens/GenerateModal.vue')['default']
|
||||
TokensList: typeof import('./components/tokens/List.vue')['default']
|
||||
TokensOverview: typeof import('./components/tokens/Overview.vue')['default']
|
||||
UiAccordion: typeof import('./components/ui/Accordion.vue')['default']
|
||||
UiAutoResetIcon: typeof import('./components/ui/AutoResetIcon.vue')['default']
|
||||
UsersDetails: typeof import('./components/users/Details.vue')['default']
|
||||
UsersInviteModal: typeof import('./components/users/InviteModal.vue')['default']
|
||||
|
|
|
|||
Loading…
Reference in a new issue