From 1c916274417d7bf07ca547f598a3802483b573e4 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sun, 19 May 2024 21:30:11 -0700 Subject: [PATCH] separate packages, clean up deps (remove babel/core) --- .../__tests__/openapi.generate.test.ts | 2 +- .../__tests__/openapi.overrides.test.ts | 2 +- packages/open-api/__tests__/openapi.test.ts | 2 +- .../__tests__/template.literal.test.ts | 2 +- packages/open-api/package.json | 12 +- packages/open-api/src/context.ts | 84 --- packages/open-api/src/index.ts | 5 +- packages/open-api/src/openapi.ts | 6 +- packages/open-api/src/openapi.types.ts | 2 +- packages/open-api/src/schema.ts | 311 ---------- packages/open-api/src/types.ts | 30 - packages/open-api/src/utils.ts | 117 ---- .../__tests__/template.literal.test.ts | 61 -- packages/schema-typescript/package.json | 7 +- packages/schema-typescript/src/openapi.ts | 538 ------------------ .../schema-typescript/src/openapi.types.ts | 148 ----- packages/schema-typescript/src/utils.ts | 52 +- 17 files changed, 20 insertions(+), 1361 deletions(-) delete mode 100644 packages/open-api/src/context.ts delete mode 100644 packages/open-api/src/schema.ts delete mode 100644 packages/open-api/src/types.ts delete mode 100644 packages/schema-typescript/__tests__/template.literal.test.ts delete mode 100644 packages/schema-typescript/src/openapi.ts delete mode 100644 packages/schema-typescript/src/openapi.types.ts diff --git a/packages/open-api/__tests__/openapi.generate.test.ts b/packages/open-api/__tests__/openapi.generate.test.ts index 69bba33..cdecf66 100644 --- a/packages/open-api/__tests__/openapi.generate.test.ts +++ b/packages/open-api/__tests__/openapi.generate.test.ts @@ -1,7 +1,7 @@ import { writeFileSync } from 'fs'; +import { getDefaultSchemaTSOptions } from 'schema-typescript'; import schema from '../../../__fixtures__/openapi/swagger.json'; -import { getDefaultSchemaTSOptions } from '../src'; import { generateOpenApiClient } from '../src/openapi'; it('swagger', () => { diff --git a/packages/open-api/__tests__/openapi.overrides.test.ts b/packages/open-api/__tests__/openapi.overrides.test.ts index 98bf3c8..27b6ab1 100644 --- a/packages/open-api/__tests__/openapi.overrides.test.ts +++ b/packages/open-api/__tests__/openapi.overrides.test.ts @@ -1,7 +1,7 @@ import { writeFileSync } from 'fs'; +import { generateTypeScript } from 'schema-typescript'; import schema from '../../../__fixtures__/openapi/swagger.json'; -import { generateTypeScript } from '../src'; const myschema = { title: 'Kubernetes', diff --git a/packages/open-api/__tests__/openapi.test.ts b/packages/open-api/__tests__/openapi.test.ts index ca92f8b..a56862e 100644 --- a/packages/open-api/__tests__/openapi.test.ts +++ b/packages/open-api/__tests__/openapi.test.ts @@ -1,7 +1,7 @@ import { writeFileSync } from 'fs'; +import { generateTypeScript } from 'schema-typescript'; import schema from '../../../__fixtures__/openapi/swagger.json'; -import { generateTypeScript } from '../src'; const myschema = { title: 'Kubernetes', diff --git a/packages/open-api/__tests__/template.literal.test.ts b/packages/open-api/__tests__/template.literal.test.ts index 74c9c0c..025e34c 100644 --- a/packages/open-api/__tests__/template.literal.test.ts +++ b/packages/open-api/__tests__/template.literal.test.ts @@ -1,7 +1,7 @@ import generate from '@babel/generator'; +import { getDefaultSchemaTSOptions } from 'schema-typescript'; import { createPathTemplateLiteral } from '../src'; -import { getDefaultSchemaTSOptions } from '../src'; const options = getDefaultSchemaTSOptions(); export const renderTemplateTag = (str: string) => { diff --git a/packages/open-api/package.json b/packages/open-api/package.json index 6ccb186..d12b929 100644 --- a/packages/open-api/package.json +++ b/packages/open-api/package.json @@ -3,7 +3,7 @@ "version": "0.5.0", "description": "Convert JSON Schema to TypeScript Definitions", "author": "Dan Lynch ", - "homepage": "https://github.com/pyramation/schema-typescript#readme", + "homepage": "https://github.com/cosmology-tech/schema-typescript#readme", "license": "SEE LICENSE IN LICENSE", "main": "index.js", "module": "esm/index.js", @@ -14,10 +14,10 @@ }, "repository": { "type": "git", - "url": "https://github.com/pyramation/schema-typescript" + "url": "https://github.com/cosmology-tech/schema-typescript" }, "bugs": { - "url": "https://github.com/pyramation/schema-typescript/issues" + "url": "https://github.com/cosmology-tech/schema-typescript/issues" }, "scripts": { "copy": "copyfiles -f ../../LICENSE README.md package.json dist", @@ -28,11 +28,9 @@ "test:watch": "jest --watch" }, "dependencies": { - "@babel/core": "^7.24.4", "@babel/generator": "^7.24.4", "@babel/types": "^7.24.0", - "deepmerge": "^4.3.1", - "minimatch": "^9.0.4" + "schema-typescript": "^0.5.0" }, - "keywords": ["jsonschema", "schema", "typescript"] + "keywords": ["jsonschema", "schema", "typescript", "swagger", "openapi"] } diff --git a/packages/open-api/src/context.ts b/packages/open-api/src/context.ts deleted file mode 100644 index d97deb9..0000000 --- a/packages/open-api/src/context.ts +++ /dev/null @@ -1,84 +0,0 @@ -import deepmerge from 'deepmerge'; - -import type { DeepPartial, JSONSchema } from "./types"; - -export interface SchemaDefinitionOverrides { - [key: string]: JSONSchema -} - -export interface SchemaNamingStrategy { - useLastSegment?: boolean; - prefixStrip?: string | RegExp; - renameMap?: { [originalName: string]: string }; -} -export interface SchemaTSOptions { - useSingleQuotes: boolean; - camelCase?: boolean; // defaults to false - camelCaseFn?: (str: string) => string; // optional function to convert keys to camelCase - strictTypeSafety: boolean; // true uses { [k: string]: unknown; }, false uses any - overrides?: SchemaDefinitionOverrides; - - includeTypeComments?: boolean; - includePropertyComments?: boolean; - includeMethodComments?: boolean; - - namingStrategy?: SchemaNamingStrategy; - - // Include/Exclude types - include?: string[]; - exclude?: string[]; -} - -export interface SchemaTSContextI { - options: SchemaTSOptions; - root: JSONSchema; - schema: JSONSchema; - parents: JSONSchema[] -} - - -export class SchemaTSContext implements SchemaTSContextI { - options: SchemaTSOptions; - root: JSONSchema; - schema: JSONSchema; - parents: JSONSchema[]; - - constructor( - options: Partial, - root: JSONSchema, - schema: JSONSchema, - parents: JSONSchema[] = [] - ) { - this.options = getDefaultSchemaTSOptions(options); - this.schema = schema; - this.root = root; - this.parents = parents; - } - - // (currently not used) - // Clone the context with the option to add a new parent - clone(newParent: JSONSchema): SchemaTSContext { - // Create a new array for parents to avoid mutation of the original array - const newParents = [...this.parents, newParent]; - return new SchemaTSContext(this.options, this.root, this.schema, newParents); - } -} - -export const defaultSchemaTSOptions: SchemaTSOptions = { - useSingleQuotes: true, - camelCase: false, - camelCaseFn: null, - strictTypeSafety: true, - exclude: [], - include: [], - includePropertyComments: false, - includeMethodComments: false, - includeTypeComments: false, - namingStrategy: { - useLastSegment: true - } -}; - -export const getDefaultSchemaTSOptions = (options?: DeepPartial): SchemaTSOptions => { - return deepmerge(defaultSchemaTSOptions, options ?? {}) as SchemaTSOptions; -}; diff --git a/packages/open-api/src/index.ts b/packages/open-api/src/index.ts index 838725e..6ac06dd 100644 --- a/packages/open-api/src/index.ts +++ b/packages/open-api/src/index.ts @@ -1,4 +1,3 @@ -export * from './context'; -export * from './schema'; -export * from './types'; +export * from './openapi'; +export * from './openapi.types'; export * from './utils'; \ No newline at end of file diff --git a/packages/open-api/src/openapi.ts b/packages/open-api/src/openapi.ts index 6e5bc94..116c183 100644 --- a/packages/open-api/src/openapi.ts +++ b/packages/open-api/src/openapi.ts @@ -1,10 +1,10 @@ import generate from "@babel/generator"; import * as t from '@babel/types'; +import { SchemaTSOptions } from "schema-typescript"; +import { generateTypeScriptTypes } from "schema-typescript"; +import { createPathTemplateLiteral, getTypeNameSafe, shouldInclude, toCamelCase, toPascalCase } from "schema-typescript"; -import { SchemaTSOptions } from "./context"; import { OpenAPIPathItem, OpenAPISpec, Operation, Parameter, Response } from "./openapi.types"; -import { generateTypeScriptTypes } from "./schema"; -import { createPathTemplateLiteral, getTypeNameSafe, shouldInclude, toCamelCase, toPascalCase } from "./utils"; export interface OpenAPIOptions extends SchemaTSOptions { version?: 'v1' | 'v1beta1' | 'v2beta1' | 'v2beta2'; diff --git a/packages/open-api/src/openapi.types.ts b/packages/open-api/src/openapi.types.ts index 4962cea..e889a24 100644 --- a/packages/open-api/src/openapi.types.ts +++ b/packages/open-api/src/openapi.types.ts @@ -1,4 +1,4 @@ -import { JSONSchema } from "./types"; +import { JSONSchema } from "schema-typescript"; export interface OpenAPISpec { swagger: string; diff --git a/packages/open-api/src/schema.ts b/packages/open-api/src/schema.ts deleted file mode 100644 index bacecbd..0000000 --- a/packages/open-api/src/schema.ts +++ /dev/null @@ -1,311 +0,0 @@ -import generate from "@babel/generator"; -import * as t from "@babel/types"; - -import { SchemaTSContext, type SchemaTSOptions } from "./context"; -import type { JSONSchema } from "./types"; -import { getTypeNameSafe, isValidIdentifier, isValidIdentifierCamelized, makeCommentLine, shouldInclude, toCamelCase } from "./utils"; - -const getSchemaTypeNameSafe = (ctx: SchemaTSContext, str: string): string => { - return getTypeNameSafe(ctx.options.namingStrategy, str); -}; - -const identifier = (name: string, typeAnnotation: t.TSTypeAnnotation) => { - const i = t.identifier(name); - i.typeAnnotation = typeAnnotation; - return i; -}; - -const anyOrObjectWithUnknownProps = (ctx: SchemaTSContext) => { - return ctx.options.strictTypeSafety ? t.tsTypeLiteral([ - t.tsIndexSignature( - [ - identifier('key', t.tsTypeAnnotation( - t.tsStringKeyword() - )) - ], - t.tsTypeAnnotation(t.tsUnknownKeyword()) - ) - ]) : t.tsAnyKeyword(); -} - -export function generateTypeScriptTypes(schema: JSONSchema, options?: Partial): t.ExportNamedDeclaration[] { - const ctx = new SchemaTSContext(options, schema, schema, []); - return generateInterfaces(ctx, schema); -} - -export function generateTypeScript(schema: JSONSchema, options?: Partial): string { - const ctx = new SchemaTSContext(options, schema, schema, []); - const interfaces = generateInterfaces(ctx, schema); - return generate(t.file(t.program(interfaces))).code; -} - -export function generateInterfaces(ctx: SchemaTSContext, schema: JSONSchema): t.ExportNamedDeclaration[] { - const interfaces = []; - - try { - // Process both $defs and definitions - const definitions = schema.$defs || schema.definitions || {}; - for (const key in definitions) { - if (shouldInclude(key, { - include: ctx.options.include, - exclude: ctx.options.exclude, - })) { - interfaces.push(createInterfaceDeclaration(ctx, key, definitions[key])); - } - } - } catch (e) { - console.error('Error processing interfaces'); - throw e; - } - - // Process the main schema - const title = schema.title; - if (!title) { - throw new Error('schema or options require a title'); - } - if (shouldInclude(title, { - include: ctx.options.include, - exclude: ctx.options.exclude, - })) { - interfaces.push(createInterfaceDeclaration(ctx, title, schema)); - } - return interfaces; -} - -const createExportDeclarationForType = ( - ctx: SchemaTSContext, - name: string, - schema: JSONSchema, - node: t.Declaration, -) => { - const result = t.exportNamedDeclaration(node); - - if (ctx.options.includeTypeComments && schema.description) { - - if (name.includes('.')) { - // for complex names, let's add them for clarity/mapping - result.leadingComments = [makeCommentLine(name)[0], makeCommentLine(schema.description)[0]] - } else { - result.leadingComments = makeCommentLine(schema.description) - } - - } - return result; -} - -function createInterfaceDeclaration( - ctx: SchemaTSContext, - name: string, - schema: JSONSchema -): t.ExportNamedDeclaration { - // Handle standard properties if they exist - let bodyElements: any = []; - if (schema.properties) { - const properties = schema.properties; - const required = schema.required || []; - bodyElements = Object.keys(properties).map(key => { - const prop = properties[key]; - return createPropertySignature(ctx, key, prop, required, schema); - }); - } - - // Handling additionalProperties if they exist - if (schema.additionalProperties) { - const additionalType = typeof schema.additionalProperties === 'boolean' ? - t.tsStringKeyword() : getTypeForProp(ctx, schema.additionalProperties, [], schema); - const indexSignature = t.tsIndexSignature( - [t.identifier("key")], // index name, can be any valid name - t.tsTypeAnnotation(additionalType) - ); - indexSignature.parameters[0].typeAnnotation = t.tsTypeAnnotation(t.tsStringKeyword()); - bodyElements.push(indexSignature); - } - - // Handling oneOf, anyOf, allOf if properties are not defined - if (!schema.properties && (schema.oneOf || schema.anyOf || schema.allOf)) { - const types = []; - if (schema.oneOf) { - types.push(getTypeForProp(ctx, { oneOf: schema.oneOf }, [], schema)); - } - if (schema.anyOf) { - types.push(getTypeForProp(ctx, { anyOf: schema.anyOf }, [], schema)); - } - if (schema.allOf) { - types.push(getTypeForProp(ctx, { allOf: schema.allOf }, [], schema)); - } - - // Create a union type if multiple types are generated - const combinedType = types.length > 1 ? t.tsUnionType(types) : types[0]; - - // Create a type alias instead of an interface if we're only handling these constructs - const typeAlias = t.tsTypeAliasDeclaration(t.identifier(getSchemaTypeNameSafe(ctx, name)), null, combinedType); - return createExportDeclarationForType(ctx, name, schema, typeAlias); - } - - // Finally, create the interface declaration if there are any body elements - if (bodyElements.length > 0) { - const interfaceDeclaration = t.tsInterfaceDeclaration( - t.identifier(getSchemaTypeNameSafe(ctx, name)), - null, - [], - t.tsInterfaceBody(bodyElements) - ); - return createExportDeclarationForType(ctx, name, schema, interfaceDeclaration); - } - - if (schema.type) { - return createExportDeclarationForType(ctx, name, schema, t.tsTypeAliasDeclaration(t.identifier(getSchemaTypeNameSafe(ctx, name)), null, getTypeForProp(ctx, schema, [], schema))) - } - - if (ctx.options.overrides && Object.prototype.hasOwnProperty.call(ctx.options.overrides, name)) { - return createExportDeclarationForType(ctx, name, schema, - t.tsTypeAliasDeclaration(t.identifier(getSchemaTypeNameSafe(ctx, name)), null, - getTypeForProp(ctx, ctx.options.overrides[name], [], schema) - )); - } - - // Fallback to exporting a basic type if nothing else is possible - // console.warn(`No properties or type definitions found for ${name}, defaulting to 'any'.`); - return createExportDeclarationForType(ctx, name, schema, t.tsTypeAliasDeclaration(t.identifier(getSchemaTypeNameSafe(ctx, name)), null, t.tsAnyKeyword())) -} - -function createPropertySignature( - ctx: SchemaTSContext, - key: string, - prop: JSONSchema, - required: string[], - schema: JSONSchema -): t.TSPropertySignature { - let camelCaseFn: (str: string) => string = toCamelCase; - if (ctx.options.camelCaseFn) camelCaseFn = ctx.options.camelCaseFn; - const name = ctx.options.camelCase ? camelCaseFn(key) : key; - const propType = getTypeForProp(ctx, prop, required, schema); - let identifier: t.Identifier | t.StringLiteral; - let isIdent: boolean; - if (ctx.options.camelCase) { - isIdent = isValidIdentifierCamelized(key); - } else { - isIdent = isValidIdentifier(key); - } - identifier = isIdent ? t.identifier(name) : t.stringLiteral(key); - const propSig = t.tsPropertySignature( - identifier, - t.tsTypeAnnotation(propType) - ); - propSig.optional = !required.includes(key); - if (ctx.options.includePropertyComments && prop.description) { - propSig.leadingComments = makeCommentLine(prop.description); - } - - return propSig; -} - -function getTypeForProp(ctx: SchemaTSContext, prop: JSONSchema, required: string[], schema: JSONSchema): t.TSType { - if (prop.$ref) { - return resolveRefType(ctx, prop.$ref, schema); - } - - if (prop.enum) { - const enumType = prop.enum.map(enumValue => t.tsLiteralType(t.stringLiteral(enumValue))); - return t.tsUnionType(enumType); - } - - if (prop.const) { - return t.tsLiteralType(t.stringLiteral(prop.const)); - } - - if (prop.type) { - if (Array.isArray(prop.type)) { - const arrayType = prop.type.map(type => getTypeForProp(ctx, { type, items: prop.items }, [], schema)); - return t.tsUnionType(arrayType); - } - - switch (prop.type) { - case 'string': - return t.tsStringKeyword(); - case 'number': - case 'integer': - return t.tsNumberKeyword(); - case 'boolean': - return t.tsBooleanKeyword(); - case 'null': - return t.tsNullKeyword(); - case 'array': - if (prop.items) { - return t.tsArrayType(getTypeForProp(ctx, prop.items, required, schema)); - } else { - throw new Error('Array items specification is missing'); - } - case 'object': - if (prop.properties) { - const nestedProperties = prop.properties; - const nestedRequired = prop.required || []; - const typeElements = Object.keys(nestedProperties).map(nestedKey => { - const nestedProp = nestedProperties[nestedKey]; - return createPropertySignature(ctx, nestedKey, nestedProp, nestedRequired, schema); - }); - return t.tsTypeLiteral(typeElements); - } else { - // Handle dynamic properties with strict type safety option - return anyOrObjectWithUnknownProps(ctx); - } - default: - return t.tsAnyKeyword(); - } - } - - // Handling oneOf, anyOf, allOf - if (prop.anyOf) { - const types = prop.anyOf.map((subProp) => getTypeForProp(ctx, subProp, required, schema)); - return t.tsUnionType(types); - } - if (prop.allOf) { - const types = prop.allOf.map((subProp) => getTypeForProp(ctx, subProp, required, schema)); - return t.tsIntersectionType(types); - } - if (prop.oneOf) { - const types = prop.oneOf.map((subProp) => getTypeForProp(ctx, subProp, required, schema)); - return t.tsUnionType(types); - } - - // Fallback when no types are defined - return t.tsAnyKeyword() - -} - -function getTypeReferenceFromSchema(ctx: SchemaTSContext, schema: JSONSchema, definitionName: string): t.TSType | null { - if (definitionName) { - - // if (ctx.options.overrides && Object.prototype.hasOwnProperty.call(ctx.options.overrides, definitionName)) { - // return getTypeForProp(ctx, ctx.options.overrides[definitionName], [], schema); - // } - - if (schema.$defs && schema.$defs[definitionName]) { - return t.tsTypeReference(t.identifier(getSchemaTypeNameSafe(ctx, definitionName))); - } else if (schema.definitions && schema.definitions[definitionName]) { - return t.tsTypeReference(t.identifier(getSchemaTypeNameSafe(ctx, definitionName))); - } - } - return null; // Return null if no type reference is found -} - - -function resolveRefType(ctx: SchemaTSContext, ref: string, schema: JSONSchema): t.TSType { - const path = ref.split('/'); - const definitionName = path.pop(); - - // Try to resolve the type reference from the local schema - const localTypeReference = getTypeReferenceFromSchema(ctx, schema, definitionName); - if (localTypeReference) { - return localTypeReference; - } - - // Try to resolve the type reference from the root schema - const rootTypeReference = getTypeReferenceFromSchema(ctx, ctx.root, definitionName); - if (rootTypeReference) { - return rootTypeReference; - } - - // If no definitions are found in either schema, throw an error - throw new Error(`Reference ${ref} not found in definitions or $defs.`); -} diff --git a/packages/open-api/src/types.ts b/packages/open-api/src/types.ts deleted file mode 100644 index cd131cf..0000000 --- a/packages/open-api/src/types.ts +++ /dev/null @@ -1,30 +0,0 @@ -export interface JSONSchema { - $schema?: string; - $ref?: string; - title?: string; - properties?: { [key: string]: JSONSchema }; - required?: string[]; - type?: string; - const?: string; - enum?: string[]; - items?: JSONSchema; - $defs?: { [key: string]: JSONSchema }; // (JSON Schema Draft 2019-09 and later) - definitions?: { [key: string]: JSONSchema }; // (JSON Schema Draft-04 to Draft-07) - additionalProperties?: boolean | JSONSchema; - anyOf?: JSONSchema[]; - allOf?: JSONSchema[]; - oneOf?: JSONSchema[]; - description?: string; - default?: any; - format?: string; -} - -export type DeepPartial = { - [P in keyof T]?: T[P] extends Array - ? Array> // If it's an array, make each element a DeepPartial - : T[P] extends ReadonlyArray - ? ReadonlyArray> // Handle readonly arrays - : T[P] extends object - ? DeepPartial // Apply DeepPartial recursively if it's an object - : T[P]; // Otherwise, just make it optional -}; diff --git a/packages/open-api/src/utils.ts b/packages/open-api/src/utils.ts index c687a58..5b564d8 100644 --- a/packages/open-api/src/utils.ts +++ b/packages/open-api/src/utils.ts @@ -1,124 +1,7 @@ -import { minimatch } from 'minimatch'; import * as t from '@babel/types'; -import { SchemaNamingStrategy } from "./context"; -import { JSONSchema } from './types'; import { OpenAPIOptions } from './openapi'; -export function toPascalCase(str: string) { - return str.replace(/(^|_|\s|-)(\w)/g, (_: any, __: any, letter: string) => letter.toUpperCase()).replace(/[_\s-]/g, ''); -} - -export function toCamelCase(key: string) { - return key - // First, remove all leading non-alphabet characters - .replace(/^[^a-zA-Z]+/, '') - // Convert what follows a separator into upper case - .replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '') - // Ensure the first character of the result is always lowercase - .replace(/^./, (c) => c.toLowerCase()); -} - -// // Determine if the key is a valid JavaScript identifier -export function isValidIdentifier(key: string) { - return /^[$A-Z_][0-9A-Z_$]*$/i.test(key) && !/^[0-9]+$/.test(key); -} - - -// Determine if the key is a valid JavaScript-like identifier, allowing internal hyphens -export function isValidIdentifierCamelized(key: string) { - return /^[$A-Z_][0-9A-Z_$\-]*$/i.test(key) && !/^[0-9]+$/.test(key) && !/^-/.test(key); -} - -export const getTypeNameSafe = (strategy: SchemaNamingStrategy, str: string): string => { - if (Object.prototype.hasOwnProperty.call(strategy.renameMap ?? {}, str)) { - return toPascalCase(strategy.renameMap[str]); - } - - if (strategy.useLastSegment) { - const parts = str.split('.'); - if (parts.length > 1) { - return toPascalCase(parts[parts.length - 1]); - } - return toPascalCase(str); - } - - if (str.match(/\./)) return toPascalCase(str.replace(/\./g, '_')); - - return toPascalCase(str); -}; - - -const globPattern = /\*+([^+@!?\*\[\(]*)/; - -interface ShouldIncludeOptions { - include: string[]; - exclude: string[]; -} - -export const shouldInclude = (type: string, options: ShouldIncludeOptions): boolean => { - // Determine if 'include' and 'exclude' are effectively set - const includesEffectivelySet = options.include && options.include.length > 0; - const excludesEffectivelySet = options.exclude && options.exclude.length > 0; - - // Function to check if any patterns in the array match the type - const matchesPattern = (patterns: string[], type: string): boolean => - patterns.some(pattern => globPattern.test(pattern) ? minimatch(type, pattern) : type === pattern); - - // Check if the type is explicitly included or excluded using minimatch or exact match - const isIncluded = includesEffectivelySet ? matchesPattern(options.include, type) : true; - const isExcluded = excludesEffectivelySet ? matchesPattern(options.exclude, type) : false; - - // Apply the logic based on whether includes or excludes are effectively set - if (includesEffectivelySet && excludesEffectivelySet) { - return isIncluded && !isExcluded; - } else if (includesEffectivelySet) { - return isIncluded; - } else if (excludesEffectivelySet) { - return !isExcluded; - } - - // Default behavior if neither is effectively set - return true; -} - -export const cleanComment = (str: string) => { - return str.replace(/\*\//g, "*\\/"); -}; - -const ensureOneSpaceEnd = (str: string) => { - return /[\s\n\t]$/.test(str) ? str : `${str} `; -}; - -const ensureOneSpace = (str: string) => { - return /^[\s\n\t]+/.test(str) ? str : ` ${str}`; -}; - -export const processComment = (comment: string) => { - if (!comment) return ''; - - if (!/[\n]+/.test(comment)) { - return `*${ensureOneSpaceEnd(ensureOneSpace(cleanComment(comment)))}`; - } - - let lines = comment.split('\n'); - lines = ['*', ...lines.map(line => cleanComment(line).trim()), ' ']; // Clean and trim each line - return lines.map((line, i) => { - if (i === 0) return line; - if (i === lines.length - 1) return line; // The last line is just a space - return ` *${ensureOneSpace(line)}`; // Ensure a space after * for clean formatting - }).join('\n'); -}; - -export const makeComment = (comment: string) => { - return [{ type: 'CommentBlock', value: ` ${comment} ` }] -} - -export const makeCommentLine = (comment: string): t.CommentLine[] => { - // @ts-ignore - return [{ type: 'CommentBlock', value: ` ${comment} ` }] as t.CommentLine[]; -} - /** * Converts a URL path with placeholders into a Babel AST TemplateLiteral. * This is used to dynamically generate paths in API client classes. diff --git a/packages/schema-typescript/__tests__/template.literal.test.ts b/packages/schema-typescript/__tests__/template.literal.test.ts deleted file mode 100644 index 74c9c0c..0000000 --- a/packages/schema-typescript/__tests__/template.literal.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import generate from '@babel/generator'; - -import { createPathTemplateLiteral } from '../src'; -import { getDefaultSchemaTSOptions } from '../src'; - -const options = getDefaultSchemaTSOptions(); -export const renderTemplateTag = (str: string) => { - return generate(createPathTemplateLiteral({ - ...options, - mergedParams: true - }, str)).code -} -it('/osmosis/{gamm}/v1beta1/estimate/swap_exact_amount_in', () => { - expect(renderTemplateTag('/osmosis/{gamm}/v1beta1/estimate/swap_exact_amount_in')) - .toEqual("`/osmosis/${params.gamm}/v1beta1/estimate/swap_exact_amount_in`"); -}); - -it('/osmosis/{gamm}/v1beta1/{estimate}/swap_exact_amount_in', () => { - expect(renderTemplateTag('/osmosis/{gamm}/v1beta1/{estimate}/swap_exact_amount_in')) - .toEqual("`/osmosis/${params.gamm}/v1beta1/${params.estimate}/swap_exact_amount_in`"); -}); - -it('/osmosis/{gamm}/{v1beta1}/{estimate}/{swap_exact_amount_in}', () => { - expect(renderTemplateTag('/osmosis/{gamm}/{v1beta1}/{estimate}/{swap_exact_amount_in}')) - .toEqual("`/osmosis/${params.gamm}/${params.v1beta1}/${params.estimate}/${params.swap_exact_amount_in}`"); -}); - -it('/osmosis/gamm/v1beta1/estimate/{swap_exact_amount_in}', () => { - expect(renderTemplateTag('/osmosis/gamm/v1beta1/estimate/{swap_exact_amount_in}')) - .toEqual("`/osmosis/gamm/v1beta1/estimate/${params.swap_exact_amount_in}`"); -}); - -it('/cosmos/feegrant/v1beta1/allowance/{granter}/{grantee}', () => { - expect(renderTemplateTag('/cosmos/feegrant/v1beta1/allowance/{granter}/{grantee}')) - .toEqual("`/cosmos/feegrant/v1beta1/allowance/${params.granter}/${params.grantee}`"); -}); - -it('/cosmos/group/v1/vote_by_proposal_voter/{proposal_id}/{voter}', () => { - expect(renderTemplateTag('/cosmos/group/v1/vote_by_proposal_voter/{proposal_id}/{voter}')) - .toEqual("`/cosmos/group/v1/vote_by_proposal_voter/${params.proposal_id}/${params.voter}`"); -}); - -it('/cosmos/gov/v1beta1/proposals/{proposal_id}/tally', () => { - expect(renderTemplateTag('/cosmos/gov/v1beta1/proposals/{proposal_id}/tally')) - .toEqual("`/cosmos/gov/v1beta1/proposals/${params.proposal_id}/tally`"); -}); - -it('/cosmos/staking/v1beta1/validators/{validator_addr}/delegations', () => { - expect(renderTemplateTag('/cosmos/staking/v1beta1/validators/{validator_addr}/delegations')) - .toEqual("`/cosmos/staking/v1beta1/validators/${params.validator_addr}/delegations`"); -}); - -it('/cosmos/staking/v1beta1/validators/{validator_addr}/delegations/{delegator_addr}', () => { - expect(renderTemplateTag('/cosmos/staking/v1beta1/validators/{validator_addr}/delegations/{delegator_addr}')) - .toEqual("`/cosmos/staking/v1beta1/validators/${params.validator_addr}/delegations/${params.delegator_addr}`"); -}); - -it('/cosmos/gov/v1beta1/proposals/{proposal_id}/votes/{voter}', () => { - expect(renderTemplateTag('/cosmos/gov/v1beta1/proposals/{proposal_id}/votes/{voter}')) - .toEqual("`/cosmos/gov/v1beta1/proposals/${params.proposal_id}/votes/${params.voter}`"); -}); diff --git a/packages/schema-typescript/package.json b/packages/schema-typescript/package.json index cf577e7..c004ef8 100644 --- a/packages/schema-typescript/package.json +++ b/packages/schema-typescript/package.json @@ -3,7 +3,7 @@ "version": "0.5.0", "description": "Convert JSON Schema to TypeScript Definitions", "author": "Dan Lynch ", - "homepage": "https://github.com/pyramation/schema-typescript#readme", + "homepage": "https://github.com/cosmology-tech/schema-typescript#readme", "license": "SEE LICENSE IN LICENSE", "main": "index.js", "module": "esm/index.js", @@ -14,10 +14,10 @@ }, "repository": { "type": "git", - "url": "https://github.com/pyramation/schema-typescript" + "url": "https://github.com/cosmology-tech/schema-typescript" }, "bugs": { - "url": "https://github.com/pyramation/schema-typescript/issues" + "url": "https://github.com/cosmology-tech/schema-typescript/issues" }, "scripts": { "copy": "copyfiles -f ../../LICENSE README.md package.json dist", @@ -28,7 +28,6 @@ "test:watch": "jest --watch" }, "dependencies": { - "@babel/core": "^7.24.4", "@babel/generator": "^7.24.4", "@babel/types": "^7.24.0", "deepmerge": "^4.3.1", diff --git a/packages/schema-typescript/src/openapi.ts b/packages/schema-typescript/src/openapi.ts deleted file mode 100644 index 6e5bc94..0000000 --- a/packages/schema-typescript/src/openapi.ts +++ /dev/null @@ -1,538 +0,0 @@ -import generate from "@babel/generator"; -import * as t from '@babel/types'; - -import { SchemaTSOptions } from "./context"; -import { OpenAPIPathItem, OpenAPISpec, Operation, Parameter, Response } from "./openapi.types"; -import { generateTypeScriptTypes } from "./schema"; -import { createPathTemplateLiteral, getTypeNameSafe, shouldInclude, toCamelCase, toPascalCase } from "./utils"; - -export interface OpenAPIOptions extends SchemaTSOptions { - version?: 'v1' | 'v1beta1' | 'v2beta1' | 'v2beta2'; - mergedParams?: boolean; - paths?: { - // Include/Exclude types - include?: string[]; - exclude?: string[]; - - includeTags?: string[]; - excludeTags?: string[]; - - includeRequests?: string[]; - excludeRequests?: string[]; - } -} - -/** -includes: { - requests: ['patch', 'head', 'options', 'get', 'delete'], - tags: ['patch', 'head', 'options', 'get', 'delete'] -} -*/ -const METHOD_TYPES = ['get', 'post', 'put', 'delete', 'options', 'head', 'patch']; -type MethodType = 'get' | 'post' | 'put' | 'delete' | 'options' | 'head' | 'patch'; - - -interface ParsedRoute { - path: string; - params: string[]; -} - -function parseRoute(route: string): ParsedRoute { - const paramRegex = /\{([^\}]+)\}/g; // Regular expression to find {param} - let match: RegExpExecArray | null; - const params: string[] = []; - - // Extract parameters using regex - while ((match = paramRegex.exec(route)) !== null) { - params.push(match[1]); // Capture the parameter name, excluding the braces - } - - // Return the original path and the extracted parameters - return { - path: route, - params - }; -} - -const shouldIncludeOperation = ( - options: OpenAPIOptions, - pathItem: OpenAPIPathItem, - path: string, - method: MethodType -) => { - - // @ts-ignore - const operation: Operation = pathItem[method]; - - if (!operation) return false; - - const shouldIncludeByPath = shouldInclude(path, { - include: options.paths?.include ?? [], - exclude: options.paths?.exclude ?? [], - }); - - if (!shouldIncludeByPath) return false; - - const shouldIncludeByTag = operation.tags.some(tag => - shouldInclude(tag, { - include: options.paths?.includeTags ?? [], - exclude: options.paths?.excludeTags ?? [] - }) - ); - - if (!shouldIncludeByTag) return false; - - const shouldIncludeByRequest = shouldInclude(method, { - include: options.paths?.includeRequests ?? [], - exclude: options.paths?.excludeRequests ?? [] - }) - - if (!shouldIncludeByRequest) return false; - return true; -} - -export const getApiTypeNameSafe = (options: OpenAPIOptions, str: string): string => { - return getTypeNameSafe(options.namingStrategy, str); -}; - -export const getOperationReturnType = ( - options: OpenAPIOptions, - operation: Operation, - method: string -) => { - if (operation.responses) { - if (operation.responses['200']) { - const prop = operation.responses['200']; - return getResponseType(options, prop); - } - } - if (method === 'get') return t.tsAnyKeyword(); - return t.tsVoidKeyword(); -}; - -export const getResponseType = (options: OpenAPIOptions, prop: Response) => { - if (prop.schema.type) { - switch (prop.schema.type) { - case 'string': - return t.tsStringKeyword(); - case 'number': - case 'integer': - return t.tsNumberKeyword(); - case 'boolean': - return t.tsBooleanKeyword(); - case 'null': - return t.tsNullKeyword(); - case 'array': - throw new Error('Array items specification is missing'); - case 'object': - throw new Error('Array items specification is missing'); - default: - return t.tsAnyKeyword(); - } - } - - // resolve $ref - if (prop.schema) { - if (!prop.schema.$ref) { - throw new Error('no property set on open api parameter schema!') - } - const ref = prop.schema.$ref.split('/'); - const definitionName = ref.pop(); - return t.tsTypeReference(t.identifier(getApiTypeNameSafe(options, definitionName))); - } - return t.tsAnyKeyword(); -} - -export const getParameterType = (options: OpenAPIOptions, prop: Parameter) => { - if (prop.type) { - switch (prop.type) { - case 'string': - return t.tsStringKeyword(); - case 'number': - case 'integer': - return t.tsNumberKeyword(); - case 'boolean': - return t.tsBooleanKeyword(); - case 'null': - return t.tsNullKeyword(); - case 'array': - throw new Error('Array items specification is missing'); - case 'object': - throw new Error('Array items specification is missing'); - default: - return t.tsAnyKeyword(); - } - } - - // resolve $ref - if (prop.schema) { - if (!prop.schema.$ref) { - throw new Error('no property set on open api parameter schema!') - } - const ref = prop.schema.$ref.split('/'); - const definitionName = ref.pop(); - return t.tsTypeReference(t.identifier(getApiTypeNameSafe(options, definitionName))); - } - return t.tsAnyKeyword(); -} - -interface ParameterInterfaces { - query: Parameter[]; - header: Parameter[]; - path: Parameter[]; - formData: Parameter[]; - body: Parameter[]; -} - -interface OpParameterInterfaces { - pathLevel: ParameterInterfaces; - get: ParameterInterfaces; - post: ParameterInterfaces; - put: ParameterInterfaces; - delete: ParameterInterfaces; - options: ParameterInterfaces; - head: ParameterInterfaces; - patch: ParameterInterfaces; -} - -const initParams = (): ParameterInterfaces => { - return { - query: [], - header: [], - path: [], - formData: [], - body: [] - } -} - - -export function generateOpenApiParams(options: OpenAPIOptions, path: string, pathItem: OpenAPIPathItem): t.TSInterfaceDeclaration[] { - const opParams: OpParameterInterfaces = getOpenApiParams(options, path, pathItem); - const interfaces: t.TSInterfaceDeclaration[] = []; - [ - 'get', - 'post', - 'put', - 'delete', - 'options', - 'head', - 'patch' - ] - .forEach(method => { - if (Object.prototype.hasOwnProperty.call(pathItem, method)) { - // @ts-ignore - const operation: Operation = pathItem[method]; - if (!shouldIncludeOperation(options, pathItem, path, method as any)) return; - - // @ts-ignore - const methodType: 'get' | 'post' | 'put' | 'delete' | 'options' | 'head' | 'patch' = method; - - // @ts-ignore - const opParamMethod: ParameterInterfaces = opParams[method]; - - const props: t.TSPropertySignature[] = []; - - Object.keys(opParamMethod).forEach(key => { - // @ts-ignore - const params: Parameter[] = opParamMethod[key]; - // @ts-ignore - const paramType: 'query' | 'body' | 'formData' | 'header' | 'path' = key; - - - // only include body sometimes - if (['body', 'formData'].includes(paramType) && !['post', 'put', 'patch'].includes(methodType)) return; - - const inner: t.TSPropertySignature[] = []; - params.forEach(param => { - const p = t.tsPropertySignature( - t.identifier(param.name), - t.tsTypeAnnotation(getParameterType(options, param)) - ); - if (!param.required) { - p.optional = true; - } - inner.push(p); - }) - - if (!options.mergedParams) { - if (paramType === 'body') { - props.push(...inner); - } else { - const p = t.tsPropertySignature( - t.identifier(paramType), - t.tsTypeAnnotation( - t.tsTypeLiteral( - [ - ...inner - ] - ) - ) - ); - if (inner.length) { - props.push( - p - ) - } - } - } else { - props.push(...inner); - } - }); - - const typeName = toPascalCase(getOperationMethodName(operation, method, path)) + 'Request'; - const paramsInterface = t.tsInterfaceDeclaration( - t.identifier(typeName), - null, - [], - t.tsInterfaceBody(props) - ); - interfaces.push(paramsInterface); - } - }); - - return interfaces; -}; - -export function getOpenApiParams(options: OpenAPIOptions, path: string, pathItem: OpenAPIPathItem): OpParameterInterfaces { - const opParams: OpParameterInterfaces = { - pathLevel: initParams(), - get: initParams(), - post: initParams(), - put: initParams(), - delete: initParams(), - options: initParams(), - head: initParams(), - patch: initParams(), - }; - - const pathInfo = parseRoute(path); - - // BEGIN SANITIZE PARAMS - pathItem.parameters = pathItem.parameters ?? []; - const pathParms = pathItem.parameters?.filter(param => param.in === 'path') ?? []; - if (pathParms.length !== pathInfo.params.length) { - const parameters = pathItem.parameters?.filter(param => param.in !== 'path') ?? [] - pathInfo.params.forEach(name => { - const found = pathParms.find(param => param.name === name); - parameters.push(found ? found : { - name, - type: 'string', - required: true, - in: 'path' - }) - }); - pathItem.parameters = parameters; - } - // END SANITIZE PARAMS - - // load Path-Level params - pathItem.parameters.forEach(param => { - opParams.pathLevel[param.in].push(param); - }); - - [ - 'get', - 'post', - 'put', - 'delete', - 'options', - 'head', - 'patch' - ] - .forEach(method => { - if (Object.prototype.hasOwnProperty.call(pathItem, method)) { - // @ts-ignore - const operation: Operation = pathItem[method]; - if (!shouldIncludeOperation(options, pathItem, path, method as any)) return; - - // @ts-ignore - const opParamMethod: ParameterInterfaces = opParams[method]; - - // push Path-Level params into op - opParamMethod.path.push(...opParams.pathLevel.path); - opParamMethod.query.push(...opParams.pathLevel.query); - opParamMethod.header.push(...opParams.pathLevel.header); - - // get the params - if (operation.parameters) { - // Categorize parameters by 'in' field - operation.parameters.forEach(param => { - opParamMethod[param.in].push(param) - }); - } - - } - }); - - return opParams; -}; -export function generateOpenApiTypes(options: OpenAPIOptions, schema: OpenAPISpec): t.ExportNamedDeclaration[] { - const interfaces: t.TSInterfaceDeclaration[] = []; - // Iterate through each path and each method to generate interfaces - Object.entries(schema.paths).forEach(([path, pathItem]) => { - interfaces.push(...generateOpenApiParams(options, path, pathItem)); - }); - return interfaces.map(i => t.exportNamedDeclaration(i)) -} - -const getOperationMethodName = (operation: Operation, method: string, path: string) => { - const methodName = operation.operationId || toCamelCase(method + path.replace(/\W/g, '_')); - return methodName; -} - -export function generateMethods(options: OpenAPIOptions, schema: OpenAPISpec): t.ClassMethod[] { - const methods: t.ClassMethod[] = []; - - // Iterate through each path and each method in the path - Object.entries(schema.paths).forEach(([path, pathItem]) => { - - // const opParams: OpParameterInterfaces = getOpenApiParams(options, path, pathItem); - - METHOD_TYPES.forEach(method => { - if (Object.prototype.hasOwnProperty.call(pathItem, method)) { - // @ts-ignore - const operation: Operation = pathItem[method]; - if (!shouldIncludeOperation(options, pathItem, path, method as any)) return; - - - const typeName = toPascalCase(getOperationMethodName(operation, method, path)) + 'Request'; - const id = t.identifier('params'); - id.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(typeName))) - const params = [id]; - - const returnType = getOperationReturnType(options, operation, method); - const methodName = getOperationMethodName(operation, method, path); - - - - const callMethod = t.callExpression( - t.memberExpression( - t.thisExpression(), - t.identifier(method) - ), - ['post', 'put', 'patch', 'formData'].includes(method) ? - [ - t.identifier('path'), - t.memberExpression( - t.identifier('params'), - t.identifier('body') - ) - ] : [ - t.identifier('path') - ] - ); - callMethod.typeParameters = t.tsTypeParameterInstantiation([ - returnType - ]); - const methodFunction = t.classMethod( - 'method', - t.identifier(methodName), - params, - t.blockStatement([ - t.variableDeclaration('const', [ - t.variableDeclarator( - t.identifier('path'), - createPathTemplateLiteral(options, path) - ) - ]), - t.returnStatement( - t.awaitExpression( - callMethod - ) - ) - ]), - false, - false, - false, - true - ); - methodFunction.returnType = t.tsTypeAnnotation( - t.tsTypeReference( - t.identifier("Promise"), - t.tsTypeParameterInstantiation([ - returnType - ]) - ) - ); - methods.push(methodFunction); - } - - }); - }); - - return methods; -} - - -export function generateOpenApiClient(options: OpenAPIOptions, schema: OpenAPISpec): string { - const methods = [ - t.classMethod( - 'method', - t.identifier('getSwaggerJSON'), - [], - t.blockStatement([ - t.variableDeclaration('const', [ - t.variableDeclarator( - t.identifier('path'), - t.stringLiteral('/openapi/v2') // Change to '/swagger.json' if needed - ) - ]), - t.returnStatement( - t.callExpression( - t.memberExpression(t.thisExpression(), t.identifier('get')), - [t.identifier('path')] - ) - ) - ]), - false, - false, - false, - true - ), - ...generateMethods(options, schema) - ] - - const classBody = t.classBody([ - t.classMethod( - 'constructor', - t.identifier('constructor'), - [t.identifier('options')], - t.blockStatement([ - t.expressionStatement( - t.callExpression(t.super(), [t.identifier('options')]) - ) - ]) - ), - ...methods - ]); - - const clientClass = t.exportNamedDeclaration(t.classDeclaration( - t.identifier('KubernetesClient'), - t.identifier('APIClient'), - classBody, - [] - )); - - //// INTERFACES - const kubeSchema = { - title: 'Kubernetes', - definitions: schema.definitions - }; - - const types = generateTypeScriptTypes(kubeSchema, { - ...(options as any), - exclude: ['Kubernetes', ...(options.exclude ?? [])] - }); - const openApiTypes = generateOpenApiTypes(options, schema); - - return generate(t.file(t.program([ - t.importDeclaration( - [t.importSpecifier(t.identifier('APIClient'), t.identifier('APIClient'))], - t.stringLiteral('./api-client') - ), - ...types, - ...openApiTypes, - clientClass - ]))).code; -}; - diff --git a/packages/schema-typescript/src/openapi.types.ts b/packages/schema-typescript/src/openapi.types.ts deleted file mode 100644 index 4962cea..0000000 --- a/packages/schema-typescript/src/openapi.types.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { JSONSchema } from "./types"; - -export interface OpenAPISpec { - swagger: string; - info: Info; - host?: string; - basePath?: string; - schemes?: string[]; - consumes?: string[]; - produces?: string[]; - paths: { [path: string]: OpenAPIPathItem }; - definitions?: { [key: string]: JSONSchema }; - parameters?: { [key: string]: Parameter }; - responses?: { [key: string]: Response }; - securityDefinitions?: { [key: string]: SecurityDefinition }; - security?: SecurityRequirement[]; - tags?: Tag[]; - externalDocs?: ExternalDocumentation; -} - - -interface SecurityDefinition { - type: 'basic' | 'apiKey' | 'oauth2'; - description?: string; - name?: string; // for apiKey - in?: 'header' | 'query'; // for apiKey - flow?: 'implicit' | 'password' | 'application' | 'accessCode'; // for oauth2 - authorizationUrl?: string; // for oauth2 - tokenUrl?: string; // for oauth2 - scopes?: { [scopeName: string]: string }; // for oauth2 -} - -interface SecurityRequirement { - [name: string]: string[]; -} - -export interface Info { - title: string; - description?: string; - termsOfService?: string; - contact?: Contact; - license?: License; - version: string; -} - -export interface Contact { - name?: string; - url?: string; - email?: string; -} - -export interface License { - name: string; - url?: string; -} - -export interface Tag { - name: string; - description?: string; - externalDocs?: ExternalDocumentation; -} - -export interface ExternalDocumentation { - description?: string; - url: string; -} - - -export interface OpenAPIPathItem { - description?: string; - summary?: string; - get?: Operation; - put?: Operation; - post?: Operation; - delete?: Operation; - options?: Operation; - head?: Operation; - patch?: Operation; - parameters?: Parameter[]; -} - -export interface Operation { - description?: string; - summary?: string; - operationId?: string; - consumes?: string[]; - produces?: string[]; - tags?: string[]; - schemes?: string[]; - responses: { [statusCode: string]: Response }; - parameters?: Parameter[]; -} - -export interface Items { - type: string; - format?: string; - items?: Items; // For nested array types - collectionFormat?: 'csv' | 'ssv' | 'tsv' | 'pipes' | 'multi'; - default?: any; - enum?: any[]; - maximum?: number; - exclusiveMaximum?: boolean; - minimum?: number; - exclusiveMinimum?: boolean; - maxLength?: number; - minLength?: number; - pattern?: string; - maxItems?: number; - minItems?: number; - uniqueItems?: boolean; - multipleOf?: number; -} - -export interface Parameter { - name: string; - in: 'query' | 'header' | 'path' | 'formData' | 'body'; - description?: string; - required?: boolean; - type?: string; - schema?: Schema; - format?: string; - allowEmptyValue?: boolean; - items?: Items; - collectionFormat?: string; - default?: any; - maximum?: number; - exclusiveMaximum?: boolean; - minimum?: number; - exclusiveMinimum?: boolean; - maxLength?: number; - minLength?: number; - pattern?: string; - maxItems?: number; - minItems?: number; - uniqueItems?: boolean; - enum?: string[]; - multipleOf?: number; -} -export interface Response { - description: string; - schema?: Schema; -} - -export interface Schema { - $ref?: string; - type?: string; - items?: Schema; -} diff --git a/packages/schema-typescript/src/utils.ts b/packages/schema-typescript/src/utils.ts index c687a58..4567765 100644 --- a/packages/schema-typescript/src/utils.ts +++ b/packages/schema-typescript/src/utils.ts @@ -1,9 +1,7 @@ -import { minimatch } from 'minimatch'; import * as t from '@babel/types'; +import { minimatch } from 'minimatch'; import { SchemaNamingStrategy } from "./context"; -import { JSONSchema } from './types'; -import { OpenAPIOptions } from './openapi'; export function toPascalCase(str: string) { return str.replace(/(^|_|\s|-)(\w)/g, (_: any, __: any, letter: string) => letter.toUpperCase()).replace(/[_\s-]/g, ''); @@ -117,50 +115,4 @@ export const makeComment = (comment: string) => { export const makeCommentLine = (comment: string): t.CommentLine[] => { // @ts-ignore return [{ type: 'CommentBlock', value: ` ${comment} ` }] as t.CommentLine[]; -} - -/** - * Converts a URL path with placeholders into a Babel AST TemplateLiteral. - * This is used to dynamically generate paths in API client classes. - * - * @param {string} path - The API path, potentially containing placeholders like {param}. - * @returns {t.TemplateLiteral} - The constructed TemplateLiteral representing the dynamic path. - */ -export function createPathTemplateLiteral(options: OpenAPIOptions, path: string): t.TemplateLiteral { - const segments = path.split('/'); - const expressions: (t.Identifier | t.MemberExpression)[] = []; - const quasis = []; - let accumulatedPath = ''; - let isFirst = true; - - segments.forEach((segment, _index) => { - if (segment.startsWith('{') && segment.endsWith('}')) { - // Dynamic segment - const paramName = segment.slice(1, -1); - // Push the accumulated static text as a quasi before adding the expression - quasis.push(t.templateElement({ raw: accumulatedPath + '/', cooked: accumulatedPath }, false)); - accumulatedPath = ''; // Reset accumulated path after adding to quasis - - // expressions.push(t.identifier(`params.${paramName}`)); - expressions.push( - options.mergedParams ? - t.memberExpression(t.identifier('params'), t.identifier(paramName)) : - t.memberExpression( - t.memberExpression(t.identifier('params'), t.identifier('path')) - , t.identifier(paramName)) - ); - - // Prepare the next quasi to start with a slash if this is not the last segment - isFirst = false; - } else { - // Accumulate static text, ensuring to prepend a slash if it's not the first segment - accumulatedPath += (isFirst ? '' : '/') + segment; - isFirst = false; - } - }); - - // Add the final accumulated static text as the last quasi - quasis.push(t.templateElement({ raw: accumulatedPath, cooked: accumulatedPath }, true)); // Mark the last quasi as tail - - return t.templateLiteral(quasis, expressions); -} +} \ No newline at end of file