From 2244d38439e813faacb5b4f8ada80852e32c7ecc Mon Sep 17 00:00:00 2001 From: Rishabh Agarwal Date: Fri, 8 Apr 2022 11:06:25 +0530 Subject: [PATCH 01/14] feat: oasv2 example generation complete --- .../helpers/import-export/import/openapi.ts | 190 +++++++++++++++++- 1 file changed, 188 insertions(+), 2 deletions(-) diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi.ts index 2d25f09ba..7374c9b9a 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi.ts @@ -114,8 +114,10 @@ const parseOpenAPIV2Body = (op: OpenAPIV2.OperationObject): HoppRESTReqBody => { if ( obj !== "multipart/form-data" && obj !== "application/x-www-form-urlencoded" - ) - return { contentType: obj as any, body: "" } + ) { + const x = generateRequestBodyExampleFromOpenAPIV2Body(op) + return { contentType: obj as any, body: x } + } const formDataValues = pipe( (op.parameters ?? []) as OpenAPIV2.Parameter[], @@ -177,6 +179,190 @@ const parseOpenAPIV3BodyFormData = ( } } +type PrimitiveSchemaType = "string" | "integer" | "number" | "boolean" + +type SchemaType = "array" | "object" | PrimitiveSchemaType + +type PrimitiveRequestBodyExampleType = number | string | boolean + +type RequestBodyExampleType = + | { [name: string]: RequestBodyExampleType } + | Array + | PrimitiveRequestBodyExampleType + +const getPrimitiveTypePlaceholder = ( + schemaType: PrimitiveSchemaType +): PrimitiveRequestBodyExampleType => { + switch (schemaType) { + case "string": + return "string" + case "integer": + case "number": + return 1 + case "boolean": + return true + } +} + +const getSchemaTypeFromSchemaObject = ( + schema: OpenAPIV2.SchemaObject +): O.Option => + 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 +): RequestBodyExampleType => { + const enumValue = pipe( + schema.enum, + O.fromNullable, + O.map((enums) => enums[0] as RequestBodyExampleType) + ) + + if (O.isSome(enumValue)) return enumValue.value + + return pipe( + schema, + getSchemaTypeFromSchemaObject, + O.filter(isSchemaTypePrimitive), + O.map(getPrimitiveTypePlaceholder), + O.getOrElseW(() => "") + ) +} + +const generateExampleArrayFromOpenAPIV2ItemsObject = ( + items: OpenAPIV2.ItemsObject +): RequestBodyExampleType => { + // 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 + + const primitivePlaceholder = pipe( + items, + O.fromPredicate( + flow((items) => items.type as SchemaType, isSchemaTypePrimitive) + ), + O.map(getSampleEnumValueOrPlaceholder) + ) + + if (O.isSome(primitivePlaceholder)) + return Array.of(primitivePlaceholder.value, primitivePlaceholder.value) + + // If the type is not primitive, it is "array" + // items property is required if type is array + return Array.of( + generateExampleArrayFromOpenAPIV2ItemsObject( + items.items as OpenAPIV2.ItemsObject + ) + ) +} + +const generateRequestBodyExampleFromOpenAPIV2BodySchema = ( + schema: OpenAPIV2.SchemaObject +): RequestBodyExampleType => { + if (schema.example) return schema.example as RequestBodyExampleType + + 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.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]: RequestBodyExampleType }, + (aggregatedExample, property) => { + const example = generateRequestBodyExampleFromOpenAPIV2BodySchema( + property[1] + ) + aggregatedExample[property[0]] = example + return aggregatedExample + } + ) + ) +} + +const getSchemaFromOpenAPIV2Parameter = ( + parameter: OpenAPIV2.Parameter +): OpenAPIV2.SchemaObject => parameter.schema + +const generateRequestBodyExampleFromOpenAPIV2Body = ( + op: OpenAPIV2.OperationObject +): string => + pipe( + (op.parameters ?? []) as OpenAPIV2.Parameter[], + A.findFirst((param) => param.in === "body"), + O.map( + flow( + getSchemaFromOpenAPIV2Parameter, + generateRequestBodyExampleFromOpenAPIV2BodySchema + ) + ), + O.getOrElse(() => "" as RequestBodyExampleType), + (requestBodyExample) => JSON.stringify(requestBodyExample, null, "\t") // Using a tab character mimics standard pretty-print appearance + ) + const parseOpenAPIV3Body = ( op: OpenAPIV3.OperationObject | OpenAPIV31.OperationObject ): HoppRESTReqBody => { From a04b634089d0e3f3cb0dff3f78e078b06201ed23 Mon Sep 17 00:00:00 2001 From: Rishabh Agarwal Date: Fri, 8 Apr 2022 11:15:39 +0530 Subject: [PATCH 02/14] refactor: removed unecessary const --- .../hoppscotch-app/helpers/import-export/import/openapi.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi.ts index 7374c9b9a..7217ead0b 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi.ts @@ -115,8 +115,10 @@ const parseOpenAPIV2Body = (op: OpenAPIV2.OperationObject): HoppRESTReqBody => { obj !== "multipart/form-data" && obj !== "application/x-www-form-urlencoded" ) { - const x = generateRequestBodyExampleFromOpenAPIV2Body(op) - return { contentType: obj as any, body: x } + return { + contentType: obj as any, + body: generateRequestBodyExampleFromOpenAPIV2Body(op), + } } const formDataValues = pipe( From 14e9d19ae69d01f6bac3f92999734e5f02ac7732 Mon Sep 17 00:00:00 2001 From: Rishabh Agarwal Date: Fri, 15 Apr 2022 12:06:51 +0530 Subject: [PATCH 03/14] 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 = ( From 2aef9c56910cb323a8f19146cf985a12ce16a404 Mon Sep 17 00:00:00 2001 From: Rishabh Agarwal Date: Mon, 18 Apr 2022 15:45:52 +0530 Subject: [PATCH 04/14] refactor: examplev2 created and lintfix --- .../import-export/import/openapi/exampleV2.ts | 184 +++++++++++++++++ .../import-export/import/openapi/exampleV3.ts | 126 ++++++++---- .../import/openapi/exampleV31.ts | 167 ++++++++++------ .../import-export/import/openapi/index.ts | 185 +----------------- 4 files changed, 380 insertions(+), 282 deletions(-) create mode 100644 packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts new file mode 100644 index 000000000..eac44722c --- /dev/null +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts @@ -0,0 +1,184 @@ +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" + +type PrimitiveSchemaType = "string" | "integer" | "number" | "boolean" + +type SchemaType = "array" | "object" | PrimitiveSchemaType + +type PrimitiveRequestBodyExampleType = number | string | boolean + +type RequestBodyExampleType = + | { [name: string]: RequestBodyExampleType } + | Array + | PrimitiveRequestBodyExampleType + +const getPrimitiveTypePlaceholder = ( + schemaType: PrimitiveSchemaType +): PrimitiveRequestBodyExampleType => { + switch (schemaType) { + case "string": + return "string" + case "integer": + case "number": + return 1 + case "boolean": + return true + } +} + +const getSchemaTypeFromSchemaObject = ( + schema: OpenAPIV2.SchemaObject +): O.Option => + 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 +): RequestBodyExampleType => { + const enumValue = pipe( + schema.enum, + O.fromNullable, + O.map((enums) => enums[0] as RequestBodyExampleType) + ) + + if (O.isSome(enumValue)) return enumValue.value + + return pipe( + schema, + getSchemaTypeFromSchemaObject, + O.filter(isSchemaTypePrimitive), + O.map(getPrimitiveTypePlaceholder), + O.getOrElseW(() => "") + ) +} + +const generateExampleArrayFromOpenAPIV2ItemsObject = ( + items: OpenAPIV2.ItemsObject +): RequestBodyExampleType => { + // 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 + + const primitivePlaceholder = pipe( + items, + O.fromPredicate( + flow((items) => items.type as SchemaType, isSchemaTypePrimitive) + ), + O.map(getSampleEnumValueOrPlaceholder) + ) + + if (O.isSome(primitivePlaceholder)) + return Array.of(primitivePlaceholder.value, primitivePlaceholder.value) + + // If the type is not primitive, it is "array" + // items property is required if type is array + return Array.of( + generateExampleArrayFromOpenAPIV2ItemsObject( + items.items as OpenAPIV2.ItemsObject + ) + ) +} + +const generateRequestBodyExampleFromOpenAPIV2BodySchema = ( + schema: OpenAPIV2.SchemaObject +): RequestBodyExampleType => { + if (schema.example) return schema.example as RequestBodyExampleType + + 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.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]: RequestBodyExampleType }, + (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.getOrElse(() => "" as RequestBodyExampleType), + (requestBodyExample) => JSON.stringify(requestBodyExample, null, "\t") // Using a tab character mimics standard pretty-print appearance + ) diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts index b94c9edf3..19432ec3f 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts @@ -3,60 +3,106 @@ 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 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) +type RequestBodyExampleType = + | PrimitiveRequestBodyExampleType + | Array + | { [name: string]: RequestBodyExampleType } -const getPrimitiveTypePlaceholder = (primitiveType: PrimitiveSchemaType) : PrimitiveRequestBodyExampleType => { - switch(primitiveType) { - case "number": return 0.0 - case "integer": return 0 - case "string": return "string" - case "boolean": return true - } +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) +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 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 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) +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 +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 + ) + : "" +} diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV31.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV31.ts index f918d49be..0a71f1d57 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV31.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV31.ts @@ -3,82 +3,133 @@ 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 MixedArraySchemaType = ( + | OpenAPIV31.ArraySchemaObjectType + | OpenAPIV31.NonArraySchemaObjectType +)[] -type SchemaType = OpenAPIV31.ArraySchemaObjectType | OpenAPIV31.NonArraySchemaObjectType | MixedArraySchemaType +type SchemaType = + | OpenAPIV31.ArraySchemaObjectType + | OpenAPIV31.NonArraySchemaObjectType + | MixedArraySchemaType -type PrimitiveSchemaType = Exclude +type PrimitiveSchemaType = Exclude< + OpenAPIV31.NonArraySchemaObjectType, + "object" +> type PrimitiveRequestBodyExampleType = string | number | boolean | null -type RequestBodyExampleType = PrimitiveRequestBodyExampleType | Array | { [name: string]: RequestBodyExampleType } +type RequestBodyExampleType = + | PrimitiveRequestBodyExampleType + | Array + | { [name: string]: RequestBodyExampleType } -const isSchemaTypePrimitive = (schemaType: SchemaType) : schemaType is PrimitiveSchemaType => !Array.isArray(schemaType) && !["array", "object"].includes(schemaType) +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 +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) +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 - } - ) +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 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 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) +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 +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) + : "" +} diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts index 328ae3414..1ce452edd 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts @@ -28,6 +28,7 @@ import { step } from "../../steps" import { defineImporter, IMPORTER_INVALID_FILE_FORMAT } from "../" import { generateRequestBodyExampleFromMediaObject as generateExampleV31 } from "./exampleV31" import { generateRequestBodyExampleFromMediaObject as generateExampleV3 } from "./exampleV3" +import { generateRequestBodyExampleFromOpenAPIV2Body } from "./exampleV2" const OPENAPI_DEREF_ERROR = "openapi/deref_error" as const @@ -183,190 +184,6 @@ const parseOpenAPIV3BodyFormData = ( } } -type PrimitiveSchemaType = "string" | "integer" | "number" | "boolean" - -type SchemaType = "array" | "object" | PrimitiveSchemaType - -type PrimitiveRequestBodyExampleType = number | string | boolean - -type RequestBodyExampleType = - | { [name: string]: RequestBodyExampleType } - | Array - | PrimitiveRequestBodyExampleType - -const getPrimitiveTypePlaceholder = ( - schemaType: PrimitiveSchemaType -): PrimitiveRequestBodyExampleType => { - switch (schemaType) { - case "string": - return "string" - case "integer": - case "number": - return 1 - case "boolean": - return true - } -} - -const getSchemaTypeFromSchemaObject = ( - schema: OpenAPIV2.SchemaObject -): O.Option => - 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 -): RequestBodyExampleType => { - const enumValue = pipe( - schema.enum, - O.fromNullable, - O.map((enums) => enums[0] as RequestBodyExampleType) - ) - - if (O.isSome(enumValue)) return enumValue.value - - return pipe( - schema, - getSchemaTypeFromSchemaObject, - O.filter(isSchemaTypePrimitive), - O.map(getPrimitiveTypePlaceholder), - O.getOrElseW(() => "") - ) -} - -const generateExampleArrayFromOpenAPIV2ItemsObject = ( - items: OpenAPIV2.ItemsObject -): RequestBodyExampleType => { - // 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 - - const primitivePlaceholder = pipe( - items, - O.fromPredicate( - flow((items) => items.type as SchemaType, isSchemaTypePrimitive) - ), - O.map(getSampleEnumValueOrPlaceholder) - ) - - if (O.isSome(primitivePlaceholder)) - return Array.of(primitivePlaceholder.value, primitivePlaceholder.value) - - // If the type is not primitive, it is "array" - // items property is required if type is array - return Array.of( - generateExampleArrayFromOpenAPIV2ItemsObject( - items.items as OpenAPIV2.ItemsObject - ) - ) -} - -const generateRequestBodyExampleFromOpenAPIV2BodySchema = ( - schema: OpenAPIV2.SchemaObject -): RequestBodyExampleType => { - if (schema.example) return schema.example as RequestBodyExampleType - - 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.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]: RequestBodyExampleType }, - (aggregatedExample, property) => { - const example = generateRequestBodyExampleFromOpenAPIV2BodySchema( - property[1] - ) - aggregatedExample[property[0]] = example - return aggregatedExample - } - ) - ) -} - -const getSchemaFromOpenAPIV2Parameter = ( - parameter: OpenAPIV2.Parameter -): OpenAPIV2.SchemaObject => parameter.schema - -const generateRequestBodyExampleFromOpenAPIV2Body = ( - op: OpenAPIV2.OperationObject -): string => - pipe( - (op.parameters ?? []) as OpenAPIV2.Parameter[], - A.findFirst((param) => param.in === "body"), - O.map( - flow( - getSchemaFromOpenAPIV2Parameter, - generateRequestBodyExampleFromOpenAPIV2BodySchema - ) - ), - O.getOrElse(() => "" as RequestBodyExampleType), - (requestBodyExample) => JSON.stringify(requestBodyExample, null, "\t") // Using a tab character mimics standard pretty-print appearance - ) - const parseOpenAPIV3Body = ( op: OpenAPIV3.OperationObject | OpenAPIV31.OperationObject, isV31Request: boolean From 2fd9eb0767f22d358b5cdcc24960bc54ab3fb943 Mon Sep 17 00:00:00 2001 From: Rishabh Agarwal Date: Mon, 18 Apr 2022 19:29:45 +0530 Subject: [PATCH 05/14] refactor: address comments --- .../hoppscotch-app/helpers/functional/json.ts | 3 + .../import-export/import/openapi/exampleV2.ts | 60 ++++++++++--------- .../import-export/import/openapi/index.ts | 14 +++-- 3 files changed, 43 insertions(+), 34 deletions(-) diff --git a/packages/hoppscotch-app/helpers/functional/json.ts b/packages/hoppscotch-app/helpers/functional/json.ts index 2ba1317c2..a9cbaa7a5 100644 --- a/packages/hoppscotch-app/helpers/functional/json.ts +++ b/packages/hoppscotch-app/helpers/functional/json.ts @@ -7,3 +7,6 @@ import * as O from "fp-ts/Option" */ export const safeParseJSON = (str: string): O.Option => O.tryCatch(() => JSON.parse(str)) + +export const prettyPrintStringifyJSON = (jsonObject: any): O.Option => + O.tryCatch(() => JSON.stringify(jsonObject, null, "\t")) diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts index eac44722c..2923bc12f 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts @@ -2,6 +2,7 @@ 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 { prettyPrintStringifyJSON } from "~/helpers/functional/json" type PrimitiveSchemaType = "string" | "integer" | "number" | "boolean" @@ -53,52 +54,51 @@ const isSchemaTypeObject = (schemaType: string): schemaType is "object" => const getSampleEnumValueOrPlaceholder = ( schema: OpenAPIV2.SchemaObject -): RequestBodyExampleType => { - const enumValue = pipe( +): RequestBodyExampleType => + pipe( schema.enum, O.fromNullable, - O.map((enums) => enums[0] as RequestBodyExampleType) - ) - - if (O.isSome(enumValue)) return enumValue.value - - return pipe( - schema, - getSchemaTypeFromSchemaObject, - O.filter(isSchemaTypePrimitive), - O.map(getPrimitiveTypePlaceholder), + O.map((enums) => enums[0] as RequestBodyExampleType), + O.altW(() => + pipe( + schema, + getSchemaTypeFromSchemaObject, + O.filter(isSchemaTypePrimitive), + O.map(getPrimitiveTypePlaceholder) + ) + ), O.getOrElseW(() => "") ) -} const generateExampleArrayFromOpenAPIV2ItemsObject = ( items: OpenAPIV2.ItemsObject -): RequestBodyExampleType => { +): RequestBodyExampleType => // 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 - const primitivePlaceholder = pipe( + pipe( items, O.fromPredicate( flow((items) => items.type as SchemaType, isSchemaTypePrimitive) ), - O.map(getSampleEnumValueOrPlaceholder) - ) - - if (O.isSome(primitivePlaceholder)) - return Array.of(primitivePlaceholder.value, primitivePlaceholder.value) - - // If the type is not primitive, it is "array" - // items property is required if type is array - return Array.of( - generateExampleArrayFromOpenAPIV2ItemsObject( - items.items as OpenAPIV2.ItemsObject + O.map( + flow(getSampleEnumValueOrPlaceholder, (arrayItem) => + Array.of(arrayItem, arrayItem) + ) + ), + O.getOrElse(() => + // If the type is not primitive, it is "array" + // items property is required if type is array + Array.of( + generateExampleArrayFromOpenAPIV2ItemsObject( + items.items as OpenAPIV2.ItemsObject + ) + ) ) ) -} const generateRequestBodyExampleFromOpenAPIV2BodySchema = ( schema: OpenAPIV2.SchemaObject @@ -180,5 +180,9 @@ export const generateRequestBodyExampleFromOpenAPIV2Body = ( ) ), O.getOrElse(() => "" as RequestBodyExampleType), - (requestBodyExample) => JSON.stringify(requestBodyExample, null, "\t") // Using a tab character mimics standard pretty-print appearance + (requestBodyExample) => + pipe( + prettyPrintStringifyJSON(requestBodyExample), + O.getOrElse(() => "") + ) ) diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts index 1ce452edd..66525f9df 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts @@ -29,6 +29,7 @@ import { defineImporter, IMPORTER_INVALID_FILE_FORMAT } from "../" import { generateRequestBodyExampleFromMediaObject as generateExampleV31 } from "./exampleV31" import { generateRequestBodyExampleFromMediaObject as generateExampleV3 } from "./exampleV3" import { generateRequestBodyExampleFromOpenAPIV2Body } from "./exampleV2" +import { prettyPrintStringifyJSON } from "~/helpers/functional/json" const OPENAPI_DEREF_ERROR = "openapi/deref_error" as const @@ -205,12 +206,13 @@ 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" + const exampleBody = pipe( + prettyPrintStringifyJSON( + isV31Request + ? generateExampleV31(media as OpenAPIV31.MediaTypeObject) + : generateExampleV3(media as OpenAPIV3.MediaTypeObject) + ), + O.getOrElse(() => "") ) return contentType in knownContentTypes From 3a41d79c2f9c286dffb1df4a00d587974bae1c46 Mon Sep 17 00:00:00 2001 From: Rishabh Agarwal Date: Wed, 27 Apr 2022 23:48:26 +0530 Subject: [PATCH 06/14] feat: handle oneof and anyof --- .../import-export/import/openapi/exampleV3.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts index 19432ec3f..59571dfbc 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts @@ -77,18 +77,32 @@ const generateArrayRequestBodyExample = ( const generateRequestBodyExampleFromSchemaObject = ( schemaObject: OpenAPIV3.SchemaObject ): RequestBodyExampleType => { - // TODO: Handle schema objects with oneof or anyof + // TODO: Handle schema objects with allof if (schemaObject.example) return schemaObject.example as RequestBodyExampleType + + // 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 ) From b0071e6859ccea32cd6882d91ea777e905d30b45 Mon Sep 17 00:00:00 2001 From: Rishabh Agarwal Date: Fri, 8 Apr 2022 11:06:25 +0530 Subject: [PATCH 07/14] feat: oasv2 example generation complete --- .../helpers/import-export/import/openapi.ts | 190 +++++++++++++++++- 1 file changed, 188 insertions(+), 2 deletions(-) diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi.ts index cdef55b21..c96a1281c 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi.ts @@ -114,8 +114,10 @@ const parseOpenAPIV2Body = (op: OpenAPIV2.OperationObject): HoppRESTReqBody => { if ( obj !== "multipart/form-data" && obj !== "application/x-www-form-urlencoded" - ) - return { contentType: obj as any, body: "" } + ) { + const x = generateRequestBodyExampleFromOpenAPIV2Body(op) + return { contentType: obj as any, body: x } + } const formDataValues = pipe( (op.parameters ?? []) as OpenAPIV2.Parameter[], @@ -177,6 +179,190 @@ const parseOpenAPIV3BodyFormData = ( } } +type PrimitiveSchemaType = "string" | "integer" | "number" | "boolean" + +type SchemaType = "array" | "object" | PrimitiveSchemaType + +type PrimitiveRequestBodyExampleType = number | string | boolean + +type RequestBodyExampleType = + | { [name: string]: RequestBodyExampleType } + | Array + | PrimitiveRequestBodyExampleType + +const getPrimitiveTypePlaceholder = ( + schemaType: PrimitiveSchemaType +): PrimitiveRequestBodyExampleType => { + switch (schemaType) { + case "string": + return "string" + case "integer": + case "number": + return 1 + case "boolean": + return true + } +} + +const getSchemaTypeFromSchemaObject = ( + schema: OpenAPIV2.SchemaObject +): O.Option => + 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 +): RequestBodyExampleType => { + const enumValue = pipe( + schema.enum, + O.fromNullable, + O.map((enums) => enums[0] as RequestBodyExampleType) + ) + + if (O.isSome(enumValue)) return enumValue.value + + return pipe( + schema, + getSchemaTypeFromSchemaObject, + O.filter(isSchemaTypePrimitive), + O.map(getPrimitiveTypePlaceholder), + O.getOrElseW(() => "") + ) +} + +const generateExampleArrayFromOpenAPIV2ItemsObject = ( + items: OpenAPIV2.ItemsObject +): RequestBodyExampleType => { + // 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 + + const primitivePlaceholder = pipe( + items, + O.fromPredicate( + flow((items) => items.type as SchemaType, isSchemaTypePrimitive) + ), + O.map(getSampleEnumValueOrPlaceholder) + ) + + if (O.isSome(primitivePlaceholder)) + return Array.of(primitivePlaceholder.value, primitivePlaceholder.value) + + // If the type is not primitive, it is "array" + // items property is required if type is array + return Array.of( + generateExampleArrayFromOpenAPIV2ItemsObject( + items.items as OpenAPIV2.ItemsObject + ) + ) +} + +const generateRequestBodyExampleFromOpenAPIV2BodySchema = ( + schema: OpenAPIV2.SchemaObject +): RequestBodyExampleType => { + if (schema.example) return schema.example as RequestBodyExampleType + + 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.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]: RequestBodyExampleType }, + (aggregatedExample, property) => { + const example = generateRequestBodyExampleFromOpenAPIV2BodySchema( + property[1] + ) + aggregatedExample[property[0]] = example + return aggregatedExample + } + ) + ) +} + +const getSchemaFromOpenAPIV2Parameter = ( + parameter: OpenAPIV2.Parameter +): OpenAPIV2.SchemaObject => parameter.schema + +const generateRequestBodyExampleFromOpenAPIV2Body = ( + op: OpenAPIV2.OperationObject +): string => + pipe( + (op.parameters ?? []) as OpenAPIV2.Parameter[], + A.findFirst((param) => param.in === "body"), + O.map( + flow( + getSchemaFromOpenAPIV2Parameter, + generateRequestBodyExampleFromOpenAPIV2BodySchema + ) + ), + O.getOrElse(() => "" as RequestBodyExampleType), + (requestBodyExample) => JSON.stringify(requestBodyExample, null, "\t") // Using a tab character mimics standard pretty-print appearance + ) + const parseOpenAPIV3Body = ( op: OpenAPIV3.OperationObject | OpenAPIV31.OperationObject ): HoppRESTReqBody => { From 721a201e7adfdb08d96d55c3fbf51dfb311c7a58 Mon Sep 17 00:00:00 2001 From: Rishabh Agarwal Date: Fri, 8 Apr 2022 11:15:39 +0530 Subject: [PATCH 08/14] refactor: removed unecessary const --- .../hoppscotch-app/helpers/import-export/import/openapi.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi.ts index c96a1281c..19ed2bb0d 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi.ts @@ -115,8 +115,10 @@ const parseOpenAPIV2Body = (op: OpenAPIV2.OperationObject): HoppRESTReqBody => { obj !== "multipart/form-data" && obj !== "application/x-www-form-urlencoded" ) { - const x = generateRequestBodyExampleFromOpenAPIV2Body(op) - return { contentType: obj as any, body: x } + return { + contentType: obj as any, + body: generateRequestBodyExampleFromOpenAPIV2Body(op), + } } const formDataValues = pipe( From bc2f81ff2587eac9123fe6029d8618fbdb02ca8a Mon Sep 17 00:00:00 2001 From: Rishabh Agarwal Date: Fri, 15 Apr 2022 12:06:51 +0530 Subject: [PATCH 09/14] 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 19ed2bb0d..eeff84b48 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" export 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 = ( From ed49c0b72c20b2a7050b7b39b7b9d68c9df15636 Mon Sep 17 00:00:00 2001 From: Rishabh Agarwal Date: Mon, 18 Apr 2022 15:45:52 +0530 Subject: [PATCH 10/14] refactor: examplev2 created and lintfix --- .../import-export/import/openapi/exampleV2.ts | 184 +++++++++++++++++ .../import-export/import/openapi/exampleV3.ts | 126 ++++++++---- .../import/openapi/exampleV31.ts | 167 ++++++++++------ .../import-export/import/openapi/index.ts | 185 +----------------- 4 files changed, 380 insertions(+), 282 deletions(-) create mode 100644 packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts new file mode 100644 index 000000000..eac44722c --- /dev/null +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts @@ -0,0 +1,184 @@ +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" + +type PrimitiveSchemaType = "string" | "integer" | "number" | "boolean" + +type SchemaType = "array" | "object" | PrimitiveSchemaType + +type PrimitiveRequestBodyExampleType = number | string | boolean + +type RequestBodyExampleType = + | { [name: string]: RequestBodyExampleType } + | Array + | PrimitiveRequestBodyExampleType + +const getPrimitiveTypePlaceholder = ( + schemaType: PrimitiveSchemaType +): PrimitiveRequestBodyExampleType => { + switch (schemaType) { + case "string": + return "string" + case "integer": + case "number": + return 1 + case "boolean": + return true + } +} + +const getSchemaTypeFromSchemaObject = ( + schema: OpenAPIV2.SchemaObject +): O.Option => + 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 +): RequestBodyExampleType => { + const enumValue = pipe( + schema.enum, + O.fromNullable, + O.map((enums) => enums[0] as RequestBodyExampleType) + ) + + if (O.isSome(enumValue)) return enumValue.value + + return pipe( + schema, + getSchemaTypeFromSchemaObject, + O.filter(isSchemaTypePrimitive), + O.map(getPrimitiveTypePlaceholder), + O.getOrElseW(() => "") + ) +} + +const generateExampleArrayFromOpenAPIV2ItemsObject = ( + items: OpenAPIV2.ItemsObject +): RequestBodyExampleType => { + // 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 + + const primitivePlaceholder = pipe( + items, + O.fromPredicate( + flow((items) => items.type as SchemaType, isSchemaTypePrimitive) + ), + O.map(getSampleEnumValueOrPlaceholder) + ) + + if (O.isSome(primitivePlaceholder)) + return Array.of(primitivePlaceholder.value, primitivePlaceholder.value) + + // If the type is not primitive, it is "array" + // items property is required if type is array + return Array.of( + generateExampleArrayFromOpenAPIV2ItemsObject( + items.items as OpenAPIV2.ItemsObject + ) + ) +} + +const generateRequestBodyExampleFromOpenAPIV2BodySchema = ( + schema: OpenAPIV2.SchemaObject +): RequestBodyExampleType => { + if (schema.example) return schema.example as RequestBodyExampleType + + 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.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]: RequestBodyExampleType }, + (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.getOrElse(() => "" as RequestBodyExampleType), + (requestBodyExample) => JSON.stringify(requestBodyExample, null, "\t") // Using a tab character mimics standard pretty-print appearance + ) diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts index b94c9edf3..19432ec3f 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts @@ -3,60 +3,106 @@ 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 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) +type RequestBodyExampleType = + | PrimitiveRequestBodyExampleType + | Array + | { [name: string]: RequestBodyExampleType } -const getPrimitiveTypePlaceholder = (primitiveType: PrimitiveSchemaType) : PrimitiveRequestBodyExampleType => { - switch(primitiveType) { - case "number": return 0.0 - case "integer": return 0 - case "string": return "string" - case "boolean": return true - } +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) +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 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 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) +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 +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 + ) + : "" +} diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV31.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV31.ts index f918d49be..0a71f1d57 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV31.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV31.ts @@ -3,82 +3,133 @@ 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 MixedArraySchemaType = ( + | OpenAPIV31.ArraySchemaObjectType + | OpenAPIV31.NonArraySchemaObjectType +)[] -type SchemaType = OpenAPIV31.ArraySchemaObjectType | OpenAPIV31.NonArraySchemaObjectType | MixedArraySchemaType +type SchemaType = + | OpenAPIV31.ArraySchemaObjectType + | OpenAPIV31.NonArraySchemaObjectType + | MixedArraySchemaType -type PrimitiveSchemaType = Exclude +type PrimitiveSchemaType = Exclude< + OpenAPIV31.NonArraySchemaObjectType, + "object" +> type PrimitiveRequestBodyExampleType = string | number | boolean | null -type RequestBodyExampleType = PrimitiveRequestBodyExampleType | Array | { [name: string]: RequestBodyExampleType } +type RequestBodyExampleType = + | PrimitiveRequestBodyExampleType + | Array + | { [name: string]: RequestBodyExampleType } -const isSchemaTypePrimitive = (schemaType: SchemaType) : schemaType is PrimitiveSchemaType => !Array.isArray(schemaType) && !["array", "object"].includes(schemaType) +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 +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) +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 - } - ) +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 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 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) +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 +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) + : "" +} diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts index eeff84b48..d49a1f5d9 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts @@ -28,6 +28,7 @@ import { step } from "../../steps" import { defineImporter, IMPORTER_INVALID_FILE_FORMAT } from "../" import { generateRequestBodyExampleFromMediaObject as generateExampleV31 } from "./exampleV31" import { generateRequestBodyExampleFromMediaObject as generateExampleV3 } from "./exampleV3" +import { generateRequestBodyExampleFromOpenAPIV2Body } from "./exampleV2" export const OPENAPI_DEREF_ERROR = "openapi/deref_error" as const @@ -183,190 +184,6 @@ const parseOpenAPIV3BodyFormData = ( } } -type PrimitiveSchemaType = "string" | "integer" | "number" | "boolean" - -type SchemaType = "array" | "object" | PrimitiveSchemaType - -type PrimitiveRequestBodyExampleType = number | string | boolean - -type RequestBodyExampleType = - | { [name: string]: RequestBodyExampleType } - | Array - | PrimitiveRequestBodyExampleType - -const getPrimitiveTypePlaceholder = ( - schemaType: PrimitiveSchemaType -): PrimitiveRequestBodyExampleType => { - switch (schemaType) { - case "string": - return "string" - case "integer": - case "number": - return 1 - case "boolean": - return true - } -} - -const getSchemaTypeFromSchemaObject = ( - schema: OpenAPIV2.SchemaObject -): O.Option => - 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 -): RequestBodyExampleType => { - const enumValue = pipe( - schema.enum, - O.fromNullable, - O.map((enums) => enums[0] as RequestBodyExampleType) - ) - - if (O.isSome(enumValue)) return enumValue.value - - return pipe( - schema, - getSchemaTypeFromSchemaObject, - O.filter(isSchemaTypePrimitive), - O.map(getPrimitiveTypePlaceholder), - O.getOrElseW(() => "") - ) -} - -const generateExampleArrayFromOpenAPIV2ItemsObject = ( - items: OpenAPIV2.ItemsObject -): RequestBodyExampleType => { - // 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 - - const primitivePlaceholder = pipe( - items, - O.fromPredicate( - flow((items) => items.type as SchemaType, isSchemaTypePrimitive) - ), - O.map(getSampleEnumValueOrPlaceholder) - ) - - if (O.isSome(primitivePlaceholder)) - return Array.of(primitivePlaceholder.value, primitivePlaceholder.value) - - // If the type is not primitive, it is "array" - // items property is required if type is array - return Array.of( - generateExampleArrayFromOpenAPIV2ItemsObject( - items.items as OpenAPIV2.ItemsObject - ) - ) -} - -const generateRequestBodyExampleFromOpenAPIV2BodySchema = ( - schema: OpenAPIV2.SchemaObject -): RequestBodyExampleType => { - if (schema.example) return schema.example as RequestBodyExampleType - - 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.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]: RequestBodyExampleType }, - (aggregatedExample, property) => { - const example = generateRequestBodyExampleFromOpenAPIV2BodySchema( - property[1] - ) - aggregatedExample[property[0]] = example - return aggregatedExample - } - ) - ) -} - -const getSchemaFromOpenAPIV2Parameter = ( - parameter: OpenAPIV2.Parameter -): OpenAPIV2.SchemaObject => parameter.schema - -const generateRequestBodyExampleFromOpenAPIV2Body = ( - op: OpenAPIV2.OperationObject -): string => - pipe( - (op.parameters ?? []) as OpenAPIV2.Parameter[], - A.findFirst((param) => param.in === "body"), - O.map( - flow( - getSchemaFromOpenAPIV2Parameter, - generateRequestBodyExampleFromOpenAPIV2BodySchema - ) - ), - O.getOrElse(() => "" as RequestBodyExampleType), - (requestBodyExample) => JSON.stringify(requestBodyExample, null, "\t") // Using a tab character mimics standard pretty-print appearance - ) - const parseOpenAPIV3Body = ( op: OpenAPIV3.OperationObject | OpenAPIV31.OperationObject, isV31Request: boolean From 3afd2c1cf2d313752bcf70b7f129b7212973ea38 Mon Sep 17 00:00:00 2001 From: Rishabh Agarwal Date: Mon, 18 Apr 2022 19:29:45 +0530 Subject: [PATCH 11/14] refactor: address comments --- .../hoppscotch-app/helpers/functional/json.ts | 3 + .../import-export/import/openapi/exampleV2.ts | 60 ++++++++++--------- .../import-export/import/openapi/index.ts | 14 +++-- 3 files changed, 43 insertions(+), 34 deletions(-) diff --git a/packages/hoppscotch-app/helpers/functional/json.ts b/packages/hoppscotch-app/helpers/functional/json.ts index 2ba1317c2..a9cbaa7a5 100644 --- a/packages/hoppscotch-app/helpers/functional/json.ts +++ b/packages/hoppscotch-app/helpers/functional/json.ts @@ -7,3 +7,6 @@ import * as O from "fp-ts/Option" */ export const safeParseJSON = (str: string): O.Option => O.tryCatch(() => JSON.parse(str)) + +export const prettyPrintStringifyJSON = (jsonObject: any): O.Option => + O.tryCatch(() => JSON.stringify(jsonObject, null, "\t")) diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts index eac44722c..2923bc12f 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts @@ -2,6 +2,7 @@ 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 { prettyPrintStringifyJSON } from "~/helpers/functional/json" type PrimitiveSchemaType = "string" | "integer" | "number" | "boolean" @@ -53,52 +54,51 @@ const isSchemaTypeObject = (schemaType: string): schemaType is "object" => const getSampleEnumValueOrPlaceholder = ( schema: OpenAPIV2.SchemaObject -): RequestBodyExampleType => { - const enumValue = pipe( +): RequestBodyExampleType => + pipe( schema.enum, O.fromNullable, - O.map((enums) => enums[0] as RequestBodyExampleType) - ) - - if (O.isSome(enumValue)) return enumValue.value - - return pipe( - schema, - getSchemaTypeFromSchemaObject, - O.filter(isSchemaTypePrimitive), - O.map(getPrimitiveTypePlaceholder), + O.map((enums) => enums[0] as RequestBodyExampleType), + O.altW(() => + pipe( + schema, + getSchemaTypeFromSchemaObject, + O.filter(isSchemaTypePrimitive), + O.map(getPrimitiveTypePlaceholder) + ) + ), O.getOrElseW(() => "") ) -} const generateExampleArrayFromOpenAPIV2ItemsObject = ( items: OpenAPIV2.ItemsObject -): RequestBodyExampleType => { +): RequestBodyExampleType => // 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 - const primitivePlaceholder = pipe( + pipe( items, O.fromPredicate( flow((items) => items.type as SchemaType, isSchemaTypePrimitive) ), - O.map(getSampleEnumValueOrPlaceholder) - ) - - if (O.isSome(primitivePlaceholder)) - return Array.of(primitivePlaceholder.value, primitivePlaceholder.value) - - // If the type is not primitive, it is "array" - // items property is required if type is array - return Array.of( - generateExampleArrayFromOpenAPIV2ItemsObject( - items.items as OpenAPIV2.ItemsObject + O.map( + flow(getSampleEnumValueOrPlaceholder, (arrayItem) => + Array.of(arrayItem, arrayItem) + ) + ), + O.getOrElse(() => + // If the type is not primitive, it is "array" + // items property is required if type is array + Array.of( + generateExampleArrayFromOpenAPIV2ItemsObject( + items.items as OpenAPIV2.ItemsObject + ) + ) ) ) -} const generateRequestBodyExampleFromOpenAPIV2BodySchema = ( schema: OpenAPIV2.SchemaObject @@ -180,5 +180,9 @@ export const generateRequestBodyExampleFromOpenAPIV2Body = ( ) ), O.getOrElse(() => "" as RequestBodyExampleType), - (requestBodyExample) => JSON.stringify(requestBodyExample, null, "\t") // Using a tab character mimics standard pretty-print appearance + (requestBodyExample) => + pipe( + prettyPrintStringifyJSON(requestBodyExample), + O.getOrElse(() => "") + ) ) diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts index d49a1f5d9..37117930b 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts @@ -29,6 +29,7 @@ import { defineImporter, IMPORTER_INVALID_FILE_FORMAT } from "../" import { generateRequestBodyExampleFromMediaObject as generateExampleV31 } from "./exampleV31" import { generateRequestBodyExampleFromMediaObject as generateExampleV3 } from "./exampleV3" import { generateRequestBodyExampleFromOpenAPIV2Body } from "./exampleV2" +import { prettyPrintStringifyJSON } from "~/helpers/functional/json" export const OPENAPI_DEREF_ERROR = "openapi/deref_error" as const @@ -205,12 +206,13 @@ 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" + const exampleBody = pipe( + prettyPrintStringifyJSON( + isV31Request + ? generateExampleV31(media as OpenAPIV31.MediaTypeObject) + : generateExampleV3(media as OpenAPIV3.MediaTypeObject) + ), + O.getOrElse(() => "") ) return contentType in knownContentTypes From f84b67ec393440d77e6951b8f6c9a6ecd0058050 Mon Sep 17 00:00:00 2001 From: Rishabh Agarwal Date: Wed, 27 Apr 2022 23:48:26 +0530 Subject: [PATCH 12/14] feat: handle oneof and anyof --- .../import-export/import/openapi/exampleV3.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts index 19432ec3f..59571dfbc 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts @@ -77,18 +77,32 @@ const generateArrayRequestBodyExample = ( const generateRequestBodyExampleFromSchemaObject = ( schemaObject: OpenAPIV3.SchemaObject ): RequestBodyExampleType => { - // TODO: Handle schema objects with oneof or anyof + // TODO: Handle schema objects with allof if (schemaObject.example) return schemaObject.example as RequestBodyExampleType + + // 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 ) From a3d92c862c902f2f624545d0b383b1e0cbab15c8 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Tue, 3 May 2022 18:05:39 +0530 Subject: [PATCH 13/14] refactor: rename to prettyPrintJSON and add jsdoc --- packages/hoppscotch-app/helpers/functional/json.ts | 9 +++++++-- .../helpers/import-export/import/openapi/exampleV2.ts | 4 ++-- .../helpers/import-export/import/openapi/index.ts | 4 ++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/hoppscotch-app/helpers/functional/json.ts b/packages/hoppscotch-app/helpers/functional/json.ts index a9cbaa7a5..db5931932 100644 --- a/packages/hoppscotch-app/helpers/functional/json.ts +++ b/packages/hoppscotch-app/helpers/functional/json.ts @@ -8,5 +8,10 @@ import * as O from "fp-ts/Option" export const safeParseJSON = (str: string): O.Option => O.tryCatch(() => JSON.parse(str)) -export const prettyPrintStringifyJSON = (jsonObject: any): O.Option => - O.tryCatch(() => JSON.stringify(jsonObject, null, "\t")) +/** + * 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 => + O.tryCatch(() => JSON.stringify(obj, null, "\t")) diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts index 2923bc12f..63f36b052 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts @@ -2,7 +2,7 @@ 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 { prettyPrintStringifyJSON } from "~/helpers/functional/json" +import { prettyPrintJSON } from "~/helpers/functional/json" type PrimitiveSchemaType = "string" | "integer" | "number" | "boolean" @@ -182,7 +182,7 @@ export const generateRequestBodyExampleFromOpenAPIV2Body = ( O.getOrElse(() => "" as RequestBodyExampleType), (requestBodyExample) => pipe( - prettyPrintStringifyJSON(requestBodyExample), + prettyPrintJSON(requestBodyExample), O.getOrElse(() => "") ) ) diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts index 37117930b..ac5168922 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/index.ts @@ -29,7 +29,7 @@ import { defineImporter, IMPORTER_INVALID_FILE_FORMAT } from "../" import { generateRequestBodyExampleFromMediaObject as generateExampleV31 } from "./exampleV31" import { generateRequestBodyExampleFromMediaObject as generateExampleV3 } from "./exampleV3" import { generateRequestBodyExampleFromOpenAPIV2Body } from "./exampleV2" -import { prettyPrintStringifyJSON } from "~/helpers/functional/json" +import { prettyPrintJSON } from "~/helpers/functional/json" export const OPENAPI_DEREF_ERROR = "openapi/deref_error" as const @@ -207,7 +207,7 @@ const parseOpenAPIV3Body = ( ] = objs[0] const exampleBody = pipe( - prettyPrintStringifyJSON( + prettyPrintJSON( isV31Request ? generateExampleV31(media as OpenAPIV31.MediaTypeObject) : generateExampleV3(media as OpenAPIV3.MediaTypeObject) From 5233c36904f1ed092beeeb58e850aa8d28d2e2f8 Mon Sep 17 00:00:00 2001 From: Rishabh Agarwal Date: Tue, 17 May 2022 15:24:36 +0530 Subject: [PATCH 14/14] refactor: comments address --- .../import-export/import/openapi/exampleV2.ts | 45 +++++++------- .../import-export/import/openapi/exampleV3.ts | 55 +++++++---------- .../import/openapi/exampleV31.ts | 60 +++++++++---------- 3 files changed, 69 insertions(+), 91 deletions(-) diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts index 63f36b052..553519d38 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV2.ts @@ -8,16 +8,16 @@ type PrimitiveSchemaType = "string" | "integer" | "number" | "boolean" type SchemaType = "array" | "object" | PrimitiveSchemaType -type PrimitiveRequestBodyExampleType = number | string | boolean +type PrimitiveRequestBodyExample = number | string | boolean -type RequestBodyExampleType = - | { [name: string]: RequestBodyExampleType } - | Array - | PrimitiveRequestBodyExampleType +type RequestBodyExample = + | { [name: string]: RequestBodyExample } + | Array + | PrimitiveRequestBodyExample const getPrimitiveTypePlaceholder = ( schemaType: PrimitiveSchemaType -): PrimitiveRequestBodyExampleType => { +): PrimitiveRequestBodyExample => { switch (schemaType) { case "string": return "string" @@ -54,11 +54,11 @@ const isSchemaTypeObject = (schemaType: string): schemaType is "object" => const getSampleEnumValueOrPlaceholder = ( schema: OpenAPIV2.SchemaObject -): RequestBodyExampleType => +): RequestBodyExample => pipe( schema.enum, O.fromNullable, - O.map((enums) => enums[0] as RequestBodyExampleType), + O.map((enums) => enums[0] as RequestBodyExample), O.altW(() => pipe( schema, @@ -72,7 +72,7 @@ const getSampleEnumValueOrPlaceholder = ( const generateExampleArrayFromOpenAPIV2ItemsObject = ( items: OpenAPIV2.ItemsObject -): RequestBodyExampleType => +): RequestBodyExample => // ItemsObject can not hold type "object" // https://swagger.io/specification/v2/#itemsObject @@ -85,25 +85,26 @@ const generateExampleArrayFromOpenAPIV2ItemsObject = ( flow((items) => items.type as SchemaType, isSchemaTypePrimitive) ), O.map( - flow(getSampleEnumValueOrPlaceholder, (arrayItem) => - Array.of(arrayItem, arrayItem) - ) + flow(getSampleEnumValueOrPlaceholder, (arrayItem) => [ + arrayItem, + arrayItem, + ]) ), O.getOrElse(() => // If the type is not primitive, it is "array" // items property is required if type is array - Array.of( + [ generateExampleArrayFromOpenAPIV2ItemsObject( items.items as OpenAPIV2.ItemsObject - ) - ) + ), + ] ) ) const generateRequestBodyExampleFromOpenAPIV2BodySchema = ( schema: OpenAPIV2.SchemaObject -): RequestBodyExampleType => { - if (schema.example) return schema.example as RequestBodyExampleType +): RequestBodyExample => { + if (schema.example) return schema.example as RequestBodyExample const primitiveTypeExample = pipe( schema, @@ -155,7 +156,7 @@ const generateRequestBodyExampleFromOpenAPIV2BodySchema = ( ), O.getOrElseW(() => [] as [string, OpenAPIV2.SchemaObject][]), A.reduce( - {} as { [name: string]: RequestBodyExampleType }, + {} as { [name: string]: RequestBodyExample }, (aggregatedExample, property) => { const example = generateRequestBodyExampleFromOpenAPIV2BodySchema( property[1] @@ -179,10 +180,6 @@ export const generateRequestBodyExampleFromOpenAPIV2Body = ( generateRequestBodyExampleFromOpenAPIV2BodySchema ) ), - O.getOrElse(() => "" as RequestBodyExampleType), - (requestBodyExample) => - pipe( - prettyPrintJSON(requestBodyExample), - O.getOrElse(() => "") - ) + O.chain(prettyPrintJSON), + O.getOrElse(() => "") ) diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts index 59571dfbc..f750377e8 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV3.ts @@ -1,7 +1,7 @@ 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" +import { tupleToRecord } from "~/helpers/functional/record" type SchemaType = | OpenAPIV3.ArraySchemaObjectType @@ -9,12 +9,12 @@ type SchemaType = type PrimitiveSchemaType = Exclude -type PrimitiveRequestBodyExampleType = string | number | boolean | null +type PrimitiveRequestBodyExample = string | number | boolean | null -type RequestBodyExampleType = - | PrimitiveRequestBodyExampleType - | Array - | { [name: string]: RequestBodyExampleType } +type RequestBodyExample = + | PrimitiveRequestBodyExample + | Array + | { [name: string]: RequestBodyExample } const isSchemaTypePrimitive = ( schemaType: SchemaType @@ -23,7 +23,7 @@ const isSchemaTypePrimitive = ( const getPrimitiveTypePlaceholder = ( primitiveType: PrimitiveSchemaType -): PrimitiveRequestBodyExampleType => { +): PrimitiveRequestBodyExample => { switch (primitiveType) { case "number": return 0.0 @@ -40,46 +40,34 @@ const getPrimitiveTypePlaceholder = ( // TODO(agarwal): Use Enum values, if any const generatePrimitiveRequestBodyExample = ( schemaObject: OpenAPIV3.NonArraySchemaObject -): RequestBodyExampleType => +): RequestBodyExample => getPrimitiveTypePlaceholder(schemaObject.type as PrimitiveSchemaType) // Use carefully, call only when type is object const generateObjectRequestBodyExample = ( schemaObject: OpenAPIV3.NonArraySchemaObject -): RequestBodyExampleType => +): RequestBodyExample => pipe( schemaObject.properties, O.fromNullable, - O.map( - (properties) => - Object.entries(properties) as [string, OpenAPIV3.SchemaObject][] - ), + O.map(Object.entries), O.getOrElseW(() => [] as [string, OpenAPIV3.SchemaObject][]), - A.reduce( - {} as { [name: string]: RequestBodyExampleType }, - (aggregatedExample, property) => { - aggregatedExample[property[0]] = - generateRequestBodyExampleFromSchemaObject(property[1]) - return aggregatedExample - } - ) + tupleToRecord ) const generateArrayRequestBodyExample = ( schemaObject: OpenAPIV3.ArraySchemaObject -): RequestBodyExampleType => - Array.of( - generateRequestBodyExampleFromSchemaObject( - schemaObject.items as OpenAPIV3.SchemaObject - ) - ) +): RequestBodyExample => [ + generateRequestBodyExampleFromSchemaObject( + schemaObject.items as OpenAPIV3.SchemaObject + ), +] const generateRequestBodyExampleFromSchemaObject = ( schemaObject: OpenAPIV3.SchemaObject -): RequestBodyExampleType => { +): RequestBodyExample => { // TODO: Handle schema objects with allof - if (schemaObject.example) - return schemaObject.example as RequestBodyExampleType + 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) @@ -110,10 +98,9 @@ const generateRequestBodyExampleFromSchemaObject = ( export const generateRequestBodyExampleFromMediaObject = ( mediaObject: OpenAPIV3.MediaTypeObject -): RequestBodyExampleType => { - if (mediaObject.example) return mediaObject.example as RequestBodyExampleType - if (mediaObject.examples) - return mediaObject.examples[0] as RequestBodyExampleType +): RequestBodyExample => { + if (mediaObject.example) return mediaObject.example as RequestBodyExample + if (mediaObject.examples) return mediaObject.examples[0] as RequestBodyExample return mediaObject.schema ? generateRequestBodyExampleFromSchemaObject( mediaObject.schema as OpenAPIV3.SchemaObject diff --git a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV31.ts b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV31.ts index 0a71f1d57..31f2d2f11 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV31.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/openapi/exampleV31.ts @@ -18,12 +18,12 @@ type PrimitiveSchemaType = Exclude< "object" > -type PrimitiveRequestBodyExampleType = string | number | boolean | null +type PrimitiveRequestBodyExample = string | number | boolean | null -type RequestBodyExampleType = - | PrimitiveRequestBodyExampleType - | Array - | { [name: string]: RequestBodyExampleType } +type RequestBodyExample = + | PrimitiveRequestBodyExample + | Array + | { [name: string]: RequestBodyExample } const isSchemaTypePrimitive = ( schemaType: SchemaType @@ -32,7 +32,7 @@ const isSchemaTypePrimitive = ( const getPrimitiveTypePlaceholder = ( primitiveType: PrimitiveSchemaType -): PrimitiveRequestBodyExampleType => { +): PrimitiveRequestBodyExample => { switch (primitiveType) { case "number": return 0.0 @@ -50,13 +50,13 @@ const getPrimitiveTypePlaceholder = ( // TODO(agarwal): Use Enum values, if any const generatePrimitiveRequestBodyExample = ( schemaObject: OpenAPIV31.NonArraySchemaObject -): RequestBodyExampleType => +): RequestBodyExample => getPrimitiveTypePlaceholder(schemaObject.type as PrimitiveSchemaType) // Use carefully, the schema type should necessarily be object const generateObjectRequestBodyExample = ( schemaObject: OpenAPIV31.NonArraySchemaObject -): RequestBodyExampleType => +): RequestBodyExample => pipe( schemaObject.properties, O.fromNullable, @@ -66,7 +66,7 @@ const generateObjectRequestBodyExample = ( ), O.getOrElseW(() => [] as [string, OpenAPIV31.SchemaObject][]), A.reduce( - {} as { [name: string]: RequestBodyExampleType }, + {} as { [name: string]: RequestBodyExample }, (aggregatedExample, property) => { aggregatedExample[property[0]] = generateRequestBodyExampleFromSchemaObject(property[1]) @@ -78,39 +78,34 @@ const generateObjectRequestBodyExample = ( // Use carefully, the schema type should necessarily be mixed array const generateMixedArrayRequestBodyEcample = ( schemaObject: OpenAPIV31.SchemaObject -): RequestBodyExampleType => +): RequestBodyExample => 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 + 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 - ) - ) +): RequestBodyExample => [ + generateRequestBodyExampleFromSchemaObject( + schemaObject.items as OpenAPIV31.SchemaObject + ), +] const generateRequestBodyExampleFromSchemaObject = ( schemaObject: OpenAPIV31.SchemaObject -): RequestBodyExampleType => { +): RequestBodyExample => { // TODO: Handle schema objects with oneof or anyof - if (schemaObject.example) - return schemaObject.example as RequestBodyExampleType + if (schemaObject.example) return schemaObject.example as RequestBodyExample if (schemaObject.examples) - return schemaObject.examples[0] as RequestBodyExampleType + return schemaObject.examples[0] as RequestBodyExample if (!schemaObject.type) return "" if (isSchemaTypePrimitive(schemaObject.type)) return generatePrimitiveRequestBodyExample( @@ -125,10 +120,9 @@ const generateRequestBodyExampleFromSchemaObject = ( export const generateRequestBodyExampleFromMediaObject = ( mediaObject: OpenAPIV31.MediaTypeObject -): RequestBodyExampleType => { - if (mediaObject.example) return mediaObject.example as RequestBodyExampleType - if (mediaObject.examples) - return mediaObject.examples[0] as RequestBodyExampleType +): RequestBodyExample => { + if (mediaObject.example) return mediaObject.example as RequestBodyExample + if (mediaObject.examples) return mediaObject.examples[0] as RequestBodyExample return mediaObject.schema ? generateRequestBodyExampleFromSchemaObject(mediaObject.schema) : ""