apple / swift-openapi-generator

Generate Swift client and server code from an OpenAPI document.
https://swiftpackageindex.com/apple/swift-openapi-generator/documentation
Apache License 2.0
1.23k stars 89 forks source link

Additional options to improve generated function/property names. #503

Closed CraigSiemens closed 4 months ago

CraigSiemens commented 4 months ago

Motivation

Generating function names based on the operationId and parameters.name can result in some difficult to read and use function and property names, respectively.

The following examples are from generating a client using the App Store Connect API OpenAPI spec.

operationId

betaTesters-get_collection becomes the function and operation type betaTesters_hyphen_get_collection.

This is partially due to the OpenAPI spec supporting virtually any string and only providing a recommendation.

Tools and libraries MAY use the operationId to uniquely identify an operation, therefore, it is RECOMMENDED to follow common programming naming conventions. https://swagger.io/specification/#operation-object

parameters.name

filter[id] becomes the property filter_lbrack_email_rbrack_

This is due to square brackets being used to indicate query fields that accept an array of values. These may need to be differentiated from another filterId containing the same letters but can only contain a single value.

Proposed solution

The configuration file could be extended to contain keys for defining the naming strategy to apply to the different fields. The term "strategy" is taken from JSONEncoder/Decoder since this is also for converting from a string representation to Swift.

These are just an initial idea, they may be too simple/complicated/limiting/flexible to handle cases I'm unaware of.

Transforms

There could be a common set of Transform types that can be used for both the operation and parameter names. The implementation of each transform would be responsible for taking a string, applying some changes to it, then returning the updated string. That would allow multiple transforms to be composed to apply more complex modifications. Each transform would be represented as a YAML map where it has a single key, the name of the transform, and a value, the options needed by that transform. The options could be empty, a string, list, or map depending on what the transform requires as input.

<transform name>:
  <transform option 1>: <value>
  <transform option 2>: <value>
  ...

Here is a possible list of transform types that could be supported.

operationId

The configuration would have a new top-level key operationNamingStrategy.

Example

operationNamingStrategy:
  source: operationId
  transforms:
    - changeCase:
        source: snakeCase
        destination: camelCase
    - replaceOccurrences:
        target: "-"
        replacement: "_"

When applied to the string betaTesters-get_collection, the result would be the following.

parameters.name

The configuration would have a new top-level key parameterNamingStrategy.

Example

parameterNamingStrategy:
  transforms:
    - replaceOccurrences:
        target: "["
        replacement: "_"
    - replaceOccurrences:
        target: "]"
        replacement: ""

When applied to the string filter[id], the result would be the following.

Alternatives considered

No response

Additional information

No response

czechboy0 commented 4 months ago

Hi @CraigSiemens,

thanks for the detailed writeup.

We've discussed this a bit before, see:

The TL;DR is that we're intentionally not providing customizable normalization names, because it's relatively subjective, and every change would be a breaking change, however we're already post-1.0.

The most likely solution, if any, would be #112, but even that needs to be motivated more.

In your case, you can solve both problems by writing a small CLI in Swift that uses OpenAPIKit to read the OpenAPI document, make the updates to operationId and parameter names, and emits a fixed-up copy. The output document is what you then would use with Swift OpenAPI Generator.

I'll close this issue as a duplicate, as we're already tracking this topic in other issues, where we can keep discussing this 🙂