openapi-ts icon indicating copy to clipboard operation
openapi-ts copied to clipboard

Customize the operationId to be used

Open anchan828 opened this issue 1 year ago โ€ข 11 comments

Description

The impetus for this request is to see if something similar to the --remove-operation-id-prefix option of openapi-generator could be done.

Since the operationId needs to be unique, I am adding a tag to the operationId ({tag}_get) However, the generated client wants to remove this tag. I want it to be client.{tag}.get, not client.{tag}.{tag}Get.

Could this package provide some similar functionality?

I thought it would be nice to have various options in services.operationId:

export default defineConfig({
  input: './openapi.yml',
  output: './client',
  services: {
    // operationId: boolean | "remove-prefix" | ((operationId: string) => string)
    operationId: (operationId: string): string => operationId.split('_')[1],
  },
  name: 'ApiClient',
});

OAS

openapi: 3.0.0
info:
  title: Test

tags:
  - name: Tag

components:
  schemas:
    Tag:
      type: object
      properties:
        id:
          type: string
      required:
        - id
paths:
  /api/a:
    get:
      operationId: Tag_get
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Tag"
      tags:
        - Tag

Stackblitz

https://stackblitz.com/edit/hey-api-example-gtgx6x?file=openapi.yml


This package is very awesome to use. Thank you ๐Ÿ™Œ

anchan828 avatar May 05 '24 15:05 anchan828

I see why you'd want to do this @anchan828, I thought about it before. However, please be aware of the upcoming clients feature. Part of that release will be flattening the services layer so every method will be exported as a separate function. This will make it possible to do tree shaking on the final bundle. Unfortunately, this also means that your function names will have to be truly unique, just like operation IDs. Would you still need to transform operation ID in that case?

mrlubos avatar May 05 '24 16:05 mrlubos

@mrlubos Thanks for the details. I understand how it will be updated in the future. Depending on the unique function name, I might want to swap the tag name and order.

Tag_get -> client.tagGet Tag_get -> transform operationId -> client.getTag

To maintain backward compatibility, conversion will be necessary in one of the tools, as operationId is used in various places like Swagger UI URLs. Especially for released document URLs, if the issue is specific to operationId in openapi_ts, I would prefer to handle the conversion there to avoid changing the URLs if possible.

If I use openapi_ts in a new product in the future, I will use getTag for operationId from the beginning, so the conversion will not be necessary.

anchan828 avatar May 06 '24 03:05 anchan828

I see. We could give you operation ID transformers, hang on!

mrlubos avatar May 06 '24 05:05 mrlubos

@mrlubos I want this as well. Duplication sucks :(

export class CategoriesApi {
    public static categoriesApiList(): CancelablePromise<CategoriesApiListResponse> {
        /// ...
    }
    
    public static categoriesApiGet(data: CategoriesApiGetData): CancelablePromise<CategoriesApiGetResponse> {
        ///...
    }
    
}

syabro avatar May 19 '24 06:05 syabro

What would be a good solution for you @syabro?

mrlubos avatar May 19 '24 08:05 mrlubos

@mrlubos ideally in config something like

service: {
   methodNameGenarator: (operationId, serviceClassName) => {
       const newMethodName = operationId.replace(serviceClassName, '');
       return newMethodName.charAt(0).toLowerCase() + newMethodName.slice(1) 
  }
}

For the record I added npx fix_names.ts after the generation but I think it's fragile...

syabro avatar May 19 '24 08:05 syabro

Are you executing openapi-ts programmatically through Node.js? That would be the way to add support for executable function as you described it... can you share what's your fix_names script?

mrlubos avatar May 19 '24 08:05 mrlubos

...
"generate-openapi": "pnpm openapi-ts; pnpm tsx ./src/openapi/fix_names.ts",
...
import * as fs from 'fs'
import * as path from 'path'
import { fileURLToPath } from 'url'

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

function removeClassNameFromMethods(filePath: string) {
  const content = fs.readFileSync(filePath, 'utf-8')

  const classNameMatch = content.match(/class\s+(\w+)\s*{/)

  let updatedContent = content
  for (const match of content.matchAll(/class\s+(\w+)\s*{/g)) {
    const className = match[1]
    const prefix = className.charAt(0).toLowerCase() + className.slice(1)
    const pattern = new RegExp(`(public\\s+static\\s+)${prefix}(\\w+)(\\s*\\()`, 'g')

    updatedContent = updatedContent.replace(pattern, (fullMatch, p1, p2, p3) => {
      const newMethodName = p2.charAt(0).toLowerCase() + p2.slice(1)
      return `${p1}${newMethodName}${p3}`
    })
    console.log(`Removing class name from methods in class ${className}`)
  }

  fs.writeFileSync(filePath, updatedContent, 'utf-8')
}

const filePath = path.join(__dirname, 'services.gen.ts')
removeClassNameFromMethods(filePath)

syabro avatar May 19 '24 10:05 syabro

Ew. Yeah we will have to improve this ๐Ÿงน

mrlubos avatar May 19 '24 11:05 mrlubos

@mrlubos should I try to check and make a PR?

syabro avatar May 19 '24 12:05 syabro

Hi, This issue can be resolved by this PR. Should I leave this issue open? https://github.com/hey-api/openapi-ts/pull/663

anchan828 avatar Jun 19 '24 03:06 anchan828

Have you tried it @anchan828? If it fulfils all your requirements, feel free to close the issue ๐Ÿค

mrlubos avatar Jun 19 '24 03:06 mrlubos

@syabro Can you check if there's anything missing for your use case?

mrlubos avatar Jun 19 '24 03:06 mrlubos

In practice, the operationId passed in the methodNameGenarator is not the actual value, but rather the camel-cased version of it, so I will create an issue as soon as I create a reproduction project, but I think the approach itself is very good. My issue idea will affect everything, and there seems to be a conflict with ParamType.

If there are no comments from anyone in particular, I will close it after waiting a few days.

anchan828 avatar Jun 19 '24 05:06 anchan828

@mrlubos will check a bit later, thanks!

syabro avatar Jun 19 '24 06:06 syabro

I will create an issue as soon as I create a reproduction project

Created issue and PR. https://github.com/hey-api/openapi-ts/issues/695 https://github.com/hey-api/openapi-ts/pull/696

I've created the PR, but I don't mind at all if it's not merged because I want to hear your opinions.๐Ÿ˜€

anchan828 avatar Jun 19 '24 07:06 anchan828

@Stono check out this thread

mrlubos avatar Jun 19 '24 11:06 mrlubos

Seems legit to me ๐Ÿ‘

Stono avatar Jun 19 '24 12:06 Stono

Let's provide the whole operation to the user, they can pick whichever properties they like. Should be a straightforward migration for you, but let me know if you disagree

mrlubos avatar Jun 19 '24 12:06 mrlubos

Happy to do the migration. The more options you give to the user in a method designed to give them control makes sense.

Stono avatar Jun 19 '24 13:06 Stono

Appreciate the consideration though :)

Stono avatar Jun 19 '24 13:06 Stono

I created has been resolved issue now that the PR has been merged. If anyone is still experiencing issues, I think you can create a new one. Thank you, everyone! https://github.com/hey-api/openapi-ts/pull/696

anchan828 avatar Jun 24 '24 05:06 anchan828