From 14e9d19ae69d01f6bac3f92999734e5f02ac7732 Mon Sep 17 00:00:00 2001 From: Rishabh Agarwal Date: Fri, 15 Apr 2022 12:06:51 +0530 Subject: [PATCH] feat: v3 requests example working --- .../import-export/import/openapi/exampleV3.ts | 62 ++++++++++++++ .../import/openapi/exampleV31.ts | 84 +++++++++++++++++++ .../import/{openapi.ts => openapi/index.ts} | 29 +++++-- 3 files changed, 170 insertions(+), 5 deletions(-) create mode 100644 packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts create mode 100644 packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV31.ts rename packages/hoppscotch-app/helpers/import-export/import/{openapi.ts => openapi/index.ts} (95%) diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts new file mode 100644 index 000000000..b94c9edf3 --- /dev/null +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts @@ -0,0 +1,62 @@ +import { OpenAPIV3 } from "openapi-types" +import { pipe } from "fp-ts/function" +import * as A from "fp-ts/Array" +import * as O from "fp-ts/Option" + +type SchemaType = OpenAPIV3.ArraySchemaObjectType | OpenAPIV3.NonArraySchemaObjectType + +type PrimitiveSchemaType = Exclude + +type PrimitiveRequestBodyExampleType = string | number | boolean | null + +type RequestBodyExampleType = PrimitiveRequestBodyExampleType | Array | { [name: string]: RequestBodyExampleType } + +const isSchemaTypePrimitive = (schemaType: SchemaType) : schemaType is PrimitiveSchemaType => !["array", "object"].includes(schemaType) + +const getPrimitiveTypePlaceholder = (primitiveType: PrimitiveSchemaType) : PrimitiveRequestBodyExampleType => { + 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) : RequestBodyExampleType => + getPrimitiveTypePlaceholder(schemaObject.type as PrimitiveSchemaType) + +// Use carefully, call only when type is object +const generateObjectRequestBodyExample = (schemaObject: OpenAPIV3.NonArraySchemaObject) : RequestBodyExampleType => + pipe( + schemaObject.properties, + O.fromNullable, + O.map((properties) => Object.entries(properties) as [string, OpenAPIV3.SchemaObject][]), + O.getOrElseW(() => [] as [string, OpenAPIV3.SchemaObject][]), + A.reduce( + {} as {[name: string]: RequestBodyExampleType}, + (aggregatedExample, property) => { + aggregatedExample[property[0]] = generateRequestBodyExampleFromSchemaObject(property[1]) + return aggregatedExample + } + ) + ) + +const generateArrayRequestBodyExample = (schemaObject: OpenAPIV3.ArraySchemaObject) : RequestBodyExampleType => + Array.of(generateRequestBodyExampleFromSchemaObject(schemaObject.items as OpenAPIV3.SchemaObject)) + +const generateRequestBodyExampleFromSchemaObject = (schemaObject: OpenAPIV3.SchemaObject) : RequestBodyExampleType => { + // TODO: Handle schema objects with oneof or anyof + if(schemaObject.example) return schemaObject.example as RequestBodyExampleType + 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) : RequestBodyExampleType => { + if(mediaObject.example) return mediaObject.example as RequestBodyExampleType + if(mediaObject.examples) return mediaObject.examples[0] as RequestBodyExampleType + return mediaObject.schema ? generateRequestBodyExampleFromSchemaObject(mediaObject.schema as OpenAPIV3.SchemaObject) : "" +} \ No newline at end of file diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV31.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV31.ts new file mode 100644 index 000000000..f918d49be --- /dev/null +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV31.ts @@ -0,0 +1,84 @@ +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 + +type PrimitiveRequestBodyExampleType = string | number | boolean | null + +type RequestBodyExampleType = PrimitiveRequestBodyExampleType | Array | { [name: string]: RequestBodyExampleType } + +const isSchemaTypePrimitive = (schemaType: SchemaType) : schemaType is PrimitiveSchemaType => !Array.isArray(schemaType) && !["array", "object"].includes(schemaType) + +const getPrimitiveTypePlaceholder = (primitiveType: PrimitiveSchemaType) : PrimitiveRequestBodyExampleType => { + 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) : RequestBodyExampleType => + getPrimitiveTypePlaceholder(schemaObject.type as PrimitiveSchemaType) + +// Use carefully, the schema type should necessarily be object +const generateObjectRequestBodyExample = (schemaObject: OpenAPIV31.NonArraySchemaObject) : RequestBodyExampleType => + 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]: RequestBodyExampleType}, + (aggregatedExample, property) => { + aggregatedExample[property[0]] = generateRequestBodyExampleFromSchemaObject(property[1]) + return aggregatedExample + } + ) + ) + +// Use carefully, the schema type should necessarily be mixed array +const generateMixedArrayRequestBodyEcample = (schemaObject: OpenAPIV31.SchemaObject) : RequestBodyExampleType => + pipe( + schemaObject, + schemaObject => schemaObject.type as MixedArraySchemaType, + A.reduce( + [] as Array, + (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) : RequestBodyExampleType => + Array.of(generateRequestBodyExampleFromSchemaObject(schemaObject.items as OpenAPIV31.SchemaObject)) + +const generateRequestBodyExampleFromSchemaObject = (schemaObject: OpenAPIV31.SchemaObject) : RequestBodyExampleType => { + // TODO: Handle schema objects with oneof or anyof + if(schemaObject.example) return schemaObject.example as RequestBodyExampleType + if(schemaObject.examples) return schemaObject.examples[0] as RequestBodyExampleType + 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 generateMixedArrayRequestBodyEcample(schemaObject) +} + +export const generateRequestBodyExampleFromMediaObject = (mediaObject: OpenAPIV31.MediaTypeObject) : RequestBodyExampleType => { + if(mediaObject.example) return mediaObject.example as RequestBodyExampleType + if(mediaObject.examples) return mediaObject.examples[0] as RequestBodyExampleType + return mediaObject.schema ? generateRequestBodyExampleFromSchemaObject(mediaObject.schema) : "" +} \ No newline at end of file diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts similarity index 95% rename from packages/hoppscotch-app/helpers/import-export/import/openapi.ts rename to packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts index 7217ead0b..328ae3414 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts @@ -24,8 +24,10 @@ import * as S from "fp-ts/string" import * as O from "fp-ts/Option" import * as TE from "fp-ts/TaskEither" import * as RA from "fp-ts/ReadonlyArray" -import { step } from "../steps" -import { defineImporter, IMPORTER_INVALID_FILE_FORMAT } from "." +import { step } from "../../steps" +import { defineImporter, IMPORTER_INVALID_FILE_FORMAT } from "../" +import { generateRequestBodyExampleFromMediaObject as generateExampleV31 } from "./exampleV31" +import { generateRequestBodyExampleFromMediaObject as generateExampleV3 } from "./exampleV3" const OPENAPI_DEREF_ERROR = "openapi/deref_error" as const @@ -366,7 +368,8 @@ const generateRequestBodyExampleFromOpenAPIV2Body = ( ) const parseOpenAPIV3Body = ( - op: OpenAPIV3.OperationObject | OpenAPIV31.OperationObject + op: OpenAPIV3.OperationObject | OpenAPIV31.OperationObject, + isV31Request: boolean ): HoppRESTReqBody => { const objs = Object.entries( ( @@ -385,11 +388,19 @@ const parseOpenAPIV3Body = ( OpenAPIV3.MediaTypeObject | OpenAPIV31.MediaTypeObject ] = objs[0] + const exampleBody = JSON.stringify( + isV31Request + ? generateExampleV31(media as OpenAPIV31.MediaTypeObject) + : generateExampleV3(media as OpenAPIV3.MediaTypeObject), + null, + "\t" + ) + return contentType in knownContentTypes ? contentType === "multipart/form-data" || contentType === "application/x-www-form-urlencoded" ? parseOpenAPIV3BodyFormData(contentType, media) - : { contentType: contentType as any, body: "" } + : { contentType: contentType as any, body: exampleBody } : { contentType: null, body: null } } @@ -401,12 +412,20 @@ const isOpenAPIV3Operation = ( typeof doc.openapi === "string" && doc.openapi.startsWith("3.") +const isOpenAPIV31Operation = ( + doc: OpenAPI.Document, + op: OpenAPIOperationType +): op is OpenAPIV31.OperationObject => + objectHasProperty(doc, "openapi") && + typeof doc.openapi === "string" && + doc.openapi.startsWith("3.1") + const parseOpenAPIBody = ( doc: OpenAPI.Document, op: OpenAPIOperationType ): HoppRESTReqBody => isOpenAPIV3Operation(doc, op) - ? parseOpenAPIV3Body(op) + ? parseOpenAPIV3Body(op, isOpenAPIV31Operation(doc, op)) : parseOpenAPIV2Body(op) const resolveOpenAPIV3SecurityObj = (