Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,16 @@ Following are some of the configuration options supported by the tool.
3. `tags`: This modes splits your OpenAPI schema based on the tags and generates a separate client for each tag. If a route has no tag set, it will be available in `default.ts` file.

To check how the output looks for each mode, check out the [examples](./examples) directory.
2. `--only-tags`: Filter the generated client to only include routes with specific tags from your OpenAPI schema. Multiple tags can be specified to include routes matching any of those tags. Routes without tags will be excluded. This is useful for generating focused clients that only contain the endpoints you need.
2. `--enum-generation-type`: Specify how enums are generated:
1. `const`: Generates a const object.
2. `enum`: Generates a native enum.
3. `union`: Generates a simple union type.
3. `--only-tags`: Filter the generated client to only include routes with specific tags from your OpenAPI schema. Multiple tags can be specified to include routes matching any of those tags. Routes without tags will be excluded. This is useful for generating focused clients that only contain the endpoints you need.
e.g. `openapi-to-k6 <path-to-openapi-schema> <output path> --only-tags ItemsHeader` will generate a client with only the routes that have the `ItemsHeader` tag. Multiple tags can be specified by using multiple `--only-tags` flags or by separating them with spaces.
3. `--disable-analytics`: Disable anonymous usage analytics reporting which helping making the tool better. You can also set an environment variable `DISABLE_ANALYTICS=true` to disable the analytics.
4. `--include-sample-script`: Generate a sample k6 script. The generated sample script uses the examples defined in the OpenAPI schema requests to make the script usable out of the box. If the examples are not defined, it will use Faker to generate random data.
5. `--verbose` or `-v` : Enable verbose logging to see more detailed logging output.
6. `--help` or `-h` : Show help message.
4. `--disable-analytics`: Disable anonymous usage analytics reporting which helping making the tool better. You can also set an environment variable `DISABLE_ANALYTICS=true` to disable the analytics.
5. `--include-sample-script`: Generate a sample k6 script. The generated sample script uses the examples defined in the OpenAPI schema requests to make the script usable out of the box. If the examples are not defined, it will use Faker to generate random data.
6. `--verbose` or `-v` : Enable verbose logging to see more detailed logging output.
7. `--help` or `-h` : Show help message.

## Developing locally

Expand Down
2,554 changes: 1,358 additions & 1,196 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
"chalk": "^4.1.2",
"commander": "^12.1.0",
"handlebars": "^4.7.8",
"openapi-typescript": "^7.4.1",
"orval": "^7.5.0",
"openapi-typescript": "^7.8.0",
"orval": "^7.10.0",
"prettier": "^3.3.3",
"uuid": "^10.0.0"
},
Expand Down
35 changes: 32 additions & 3 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import chalk from 'chalk'
import { Command, InvalidArgumentError } from 'commander'
import { generateDefaultAnalyticsData, reportUsageAnalytics } from './analytics'
import { Mode } from './constants'
import { EnumGenerationType, Mode } from './constants'
import { NoFilesGeneratedError } from './errors'
import generateK6SDK from './generator'
import { getPackageDetails } from './helper'
Expand All @@ -28,13 +28,31 @@ function validateMode(value: string): Mode {
return value as Mode
}

/**
* Validate that the enum generation type argument is one of the supported types.
*
* @param {string} value - The enum generation type value to validate
* @return {EnumGenerationType} - The validated enum generation type value
*/
function validateEnumGenerationType(value: string): EnumGenerationType {
if (
!Object.values(EnumGenerationType).includes(value as EnumGenerationType)
) {
throw new InvalidArgumentError(
`Supported enum generation types: ${Object.values(EnumGenerationType).join(', ')}`
)
}
return value as EnumGenerationType
}

async function generateSDK({
openApiPath,
outputDir,
shouldGenerateSampleK6Script,
analyticsData,
mode,
tags,
enumGenerationType,
}: GenerateK6SDKOptions) {
logger.logMessage(
'Generating TypeScript client for k6...\n' +
Expand All @@ -45,9 +63,11 @@ async function generateSDK({
chalk.cyan(outputDir) +
'\n' +
(tags?.length
? 'Filtering by tag(s): ' + chalk.cyan(tags.join(', '))
? 'Filtering by tag(s): ' + chalk.cyan(tags.join(', ')) + '\n'
: '') +
'\n'
(enumGenerationType
? 'Enum generation type: ' + chalk.cyan(enumGenerationType) + '\n'
: '')
)

await generateK6SDK({
Expand All @@ -57,6 +77,7 @@ async function generateSDK({
analyticsData,
mode,
tags,
enumGenerationType,
})

const message = shouldGenerateSampleK6Script
Expand All @@ -81,6 +102,12 @@ program
'--only-tags <filters...>',
'list of tags to filter on. Generated client will only include operations with these tags'
)
.option(
'--enum-generation-type <type>',
`Enum generation type to use. Valid values - ${Object.values(EnumGenerationType).join(', ')}`,
validateEnumGenerationType,
EnumGenerationType.CONST
)
.option('-v, --verbose', 'enable verbose mode to show debug logs')
.option('--include-sample-script', 'generate a sample k6 script')
.option('--disable-analytics', 'disable anonymous usage data collection')
Expand All @@ -94,6 +121,7 @@ program
onlyTags?: (string | RegExp)[]
disableAnalytics?: boolean
includeSampleScript?: boolean
enumGenerationType?: EnumGenerationType
}
) => {
let analyticsData: AnalyticsData | undefined
Expand Down Expand Up @@ -127,6 +155,7 @@ program
analyticsData,
mode: options.mode,
tags: options.onlyTags,
enumGenerationType: options.enumGenerationType,
})
} catch (error) {
if (error instanceof NoFilesGeneratedError) {
Expand Down
6 changes: 6 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,9 @@ export enum Mode {
SPLIT = 'split',
TAGS = 'tags',
}

export enum EnumGenerationType {
ENUM = 'enum',
CONST = 'const',
UNION = 'union',
}
2 changes: 2 additions & 0 deletions src/generator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export default async ({
analyticsData,
mode,
tags,
enumGenerationType,
}: GenerateK6SDKOptions) => {
/**
* Note!
Expand All @@ -128,6 +129,7 @@ export default async ({
getK6ClientBuilder(shouldGenerateSampleK6Script, analyticsData),
override: {
header: generatedFileHeaderGenerator,
enumGenerationType: enumGenerationType,
},
headers: true,
},
Expand Down
3 changes: 2 additions & 1 deletion src/type.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Mode } from './constants'
import { Mode, EnumGenerationType } from './constants'

export interface PackageDetails {
name: string
Expand Down Expand Up @@ -32,4 +32,5 @@ export interface GenerateK6SDKOptions {
analyticsData?: AnalyticsData
mode: Mode
tags?: (string | RegExp)[]
enumGenerationType?: EnumGenerationType
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
"fileName": "formDataAPI.ts",
"expectedSubstrings": [
"export class FormDataAPIClient",
"const formData = new FormData(); formData.append(\"file\", postUploadBody.file); if (postUploadBody.description !== undefined) { formData.append(\"description\", postUploadBody.description); } formData.append(\"userId\", postUploadBody.userId);",
"const formData = new FormData(); formData.append(`file`, postUploadBody.file); if (postUploadBody.description !== undefined) { formData.append(`description`, postUploadBody.description); } formData.append(`userId`, postUploadBody.userId);",
"const url = new URL(this.cleanBaseUrl + `/upload`);",
"const response = http.request(\"POST\", url.toString(), formData.body(), { ...mergedRequestParameters, headers: { ...mergedRequestParameters?.headers, \"Content-Type\": \"multipart/form-data; boundary=\" + formData.boundary, }, });"
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"openapi_schema": {
"openapi": "3.0.3",
"info": {
"title": "Example API",
"description": "API with enum (const) of query parameters",
"version": "1.0.0"
},
"paths": {
"/example": {
"get": {
"summary": "Get example data",
"description": "This endpoint demonstrates the use of enum (const) query parameters",
"operationId": "getExampleData",
"parameters": [
{
"name": "status",
"in": "query",
"required": false,
"schema": {
"type": "string",
"enum": ["active", "inactive"]
},
"description": "Status filter"
}
],
"responses": {
"200": {
"description": "Successful operation",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": ["active", "inactive"]
}
}
}
}
}
}
}
}
}
}
},
"expected_sdk": {
"fileName": "exampleAPI.ts",
"enumGenerationType": "const",
"expectedSubstrings": [
"export const GetExampleDataStatus = {",
"export const GetExampleData200Status = {",
"export class ExampleAPIClient {",
"this.cleanBaseUrl + `/example` + `?${new URLSearchParams(params).toString()}`",
"getExampleData("
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"openapi_schema": {
"openapi": "3.0.3",
"info": {
"title": "Example API",
"description": "API with enum (const) of query parameters",
"version": "1.0.0"
},
"paths": {
"/example": {
"get": {
"summary": "Get example data",
"description": "This endpoint demonstrates the use of enum (const) query parameters",
"operationId": "getExampleData",
"parameters": [
{
"name": "status",
"in": "query",
"required": false,
"schema": {
"type": "string",
"enum": ["active", "inactive"]
},
"description": "Status filter"
}
],
"responses": {
"200": {
"description": "Successful operation",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": ["active", "inactive"]
}
}
}
}
}
}
}
}
}
}
},
"expected_sdk": {
"fileName": "exampleAPI.ts",
"enumGenerationType": "enum",
"expectedSubstrings": [
"export enum GetExampleDataStatus {",
"export enum GetExampleData200Status {",
"export class ExampleAPIClient {",
"this.cleanBaseUrl + `/example` + `?${new URLSearchParams(params).toString()}`",
"getExampleData("
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"openapi_schema": {
"openapi": "3.0.3",
"info": {
"title": "Example API",
"description": "API with enum (const) of query parameters",
"version": "1.0.0"
},
"paths": {
"/example": {
"get": {
"summary": "Get example data",
"description": "This endpoint demonstrates the use of enum (const) query parameters",
"operationId": "getExampleData",
"parameters": [
{
"name": "status",
"in": "query",
"required": false,
"schema": {
"type": "string",
"enum": ["active", "inactive"]
},
"description": "Status filter"
}
],
"responses": {
"200": {
"description": "Successful operation",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": ["active", "inactive"]
}
}
}
}
}
}
}
}
}
}
},
"expected_sdk": {
"fileName": "exampleAPI.ts",
"enumGenerationType": "union",
"expectedSubstrings": [
"export type GetExampleDataStatus = \"active\" | \"inactive\"",
"export type GetExampleData200Status = \"active\" | \"inactive\"",
"export class ExampleAPIClient {",
"this.cleanBaseUrl + `/example` + `?${new URLSearchParams(params).toString()}`",
"getExampleData("
]
}
}
1 change: 1 addition & 0 deletions tests/functional-tests/test-generator/generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ describe('generator', () => {
openApiPath,
outputDir: generatedSchemaPath,
mode: Mode.SINGLE,
enumGenerationType: expectedGeneratedCode.enumGenerationType,
})

const generatedFiles = fs.readdirSync(generatedSchemaPath)
Expand Down