Skip to content

Switch to Idiomatic Swift Naming Convention #114

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 46 commits into from
May 4, 2025

Conversation

ryansobol
Copy link
Contributor

@ryansobol ryansobol commented May 1, 2025

Introduction

First, I want to thank you for creating and maintaining such an exceptional Swift client for the GitHub REST API. As a Swift developer who has benefited greatly from this library, I'm excited to contribute back to the project. While I'm not a regular contributor, I believe this change could significantly enhance the developer experience by making the generated code feel more natural and idiomatic to Swift developers.

Summary

This PR modifies the OpenAPI generation configuration to use the idiomatic Swift naming strategy instead of the default defensive one. This change transforms generated symbol names to follow Swift's conventional naming patterns rather than preserving special characters with descriptive words (e.g., users_sol_listusersList), significantly enhancing the developer experience.

Scope Impact: This change affects virtually all non-trivial Swift symbols throughout the codebase. Due to the comprehensive impact, I recommend this as a breaking change for a v3.0.0 release, making it opt-in for developers who use semantic versioning best practices.

Examples of Name Changes

Function Names

  • Before: copilot_sol_get_hyphen_copilot_hyphen_organization_hyphen_details
  • After: copilotGetCopilotOrganizationDetails

Type Names

  • Before: copilot_hyphen_organization_hyphen_details
  • After: CopilotOrganizationDetails

Enum Case Names

  • Before: text_x_hyphen_markdown
  • After: textXMarkdown

Property Names

  • Before: total_engaged_users
  • After: totalEngagedUsers

Parameter Names

  • Before: team_slug
  • After: teamSlug

Rationale & Motivation

Improved Accessibility & Developer Experience

The primary goal of this change is to significantly enhance the usability of the generated Swift client library. The idiomatic naming strategy aligns perfectly with standard Swift practices, making the API feel more natural and intuitive for Swift developers.

Maybe it's just me, but I've found the defensive naming difficult to work with in practice. Methods like reactions_sol_list_hyphen_for_hyphen_team_hyphen_discussion_hyphen_comment_hyphen_in_hyphen_org become much more readable as reactionsListForTeamDiscussionCommentInOrg. This transformation dramatically improves readability and understanding for developers at all experience levels, making the API more intuitive and reducing cognitive overhead.

This change represents a significant accessibility improvement, making the API feel like idiomatic Swift rather than an awkward translation from another language.

Changes Made

  • Upgraded Swift OpenAPI Generator dependency from 1.2.1 to 1.7.2
  • Updated the generation configuration in Scripts/GeneratorConfigBuilder.swift to use the idiomatic naming strategy
  • Added specific name overrides for handling edge cases like "+1" and "-1" (i.e., thumbs up and thumbs down) reactions
  • Regenerated the Swift source code with the new configuration, resulting in more Swift-like public symbol names
  • Fixed the test to use the new naming patterns and non-deprecated Servers.Server1.url() API
  • Updated example code in the README.md file similarly

Proposed Migration Strategy

As this change introduces significant, albeit beneficial, breaking changes across the API surface, thoughtful consideration of migration options would be valuable. Semantic versioning principles provide helpful guidance here.

One potential approach could be:

  • Maintain the current v2.x releases with the existing defensive naming strategy, possibly on a dedicated 2.x branch.
  • Introduce the idiomatic naming strategy proposed in this PR into the main branch for a future v3.0.0 release as a breaking change.

The existing automated build system that monitors GitHub's OpenAPI specification for changes could be leveraged in this scenario - continuing to update and publish v2.x releases with the defensive naming convention from a maintenance branch, while the main branch would generate idiomatic naming for v3.0.0 and beyond. This would provide support for both existing users and those wanting the improved Swift-native experience.

That said, maintaining parallel release streams does involve additional overhead, and the maintainers might reasonably prefer to focus solely on the new idiomatic version. There may also be other migration approaches not outlined here that would better fit the project's needs. Ultimately, the decision on versioning strategy rests entirely with the project maintainers.

This pull request focuses solely on implementing the switch to the idiomatic naming convention and regenerating the code accordingly. Any branching strategy or infrastructure changes to support versioning would need to be addressed separately if desired.

Also, supporting both naming conventions simultaneously within the same module would likely introduce significant complexity to the build system and codebase. A one-convention-per-major-version approach provides a cleaner migration path, allowing users to opt-in to the improved naming when ready.

I hope this suggestion contributes constructively to the discussion of how to balance improved developer experience with a smooth transition for existing users.

Swift OpenAPI Generator Upgrade

This PR includes an upgrade from Swift OpenAPI Generator 1.2.1 to 1.7.2. Upgrading to at least version 1.6.0 was a requirement for this PR, as that's when the idiomatic naming strategy was first introduced. The decision to go all the way to 1.7.2 provides additional benefits including improved naming configuration options, Windows support, and Swift 6.1+ compatibility.

Click to see detailed version highlights from 1.2.1 to 1.7.2

Version Highlights

  1. Version 1.3.0 (July 2024):

    • Added CaseIterable conformance for string and int enums
    • Correctly handled path params with hyphens in names
    • Fixed multipart + additionalProperties + string support
  2. Version 1.4.0 (October 2024):

    • Generated enums for server variables
    • Added static property to create responses without parameters
    • Introduced TranslatorContext for better code organization
  3. Version 1.5.0-1.5.1 (November-December 2024):

    • Added support for OpenAPI 3.0.4 and 3.1.1
    • Improved parameter handling of MIME types
    • Enabled strict concurrency
    • Implemented explicit self in encoder/decoder methods
  4. Version 1.6.0 (December 2024):

    • Introduced idiomatic naming strategy as opt-in feature
    • Improved member access/assignment in initializers, decoders, and encoders
  5. Version 1.7.0 (January 2025):

    • Added a CLI option for selecting the naming strategy
    • Updated Examples and IntegrationTest to use idiomatic naming
    • Fixed Swift 6.1+ compatibility issues
  6. Version 1.7.1-1.7.2 (March-April 2025):

    • Added Windows support
    • Improved CI workflows
    • Added streaming ChatGPT proxy example from FOSDEM 2025

Verification & Potential Drawbacks

Verification

All tests pass successfully when swift build and swift test are run locally. The generated code compiles properly and all functionality remains intact with the new naming convention.

NOTE: The following make install warnings are present on both the main branch and this feature branch. They are related to issues in the OpenAPI specification interpretation and are not introduced by this PR.

Click to see the make install warnings
warning: A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property. [context: foundIn=Components.Schemas.PackageVersion.MetadataPayload.DockerPayload (#/components/schemas/package-version/metadata/docker)/tags]
warning: A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property. [context: foundIn=Operations.PullsRequestReviewers.Input.Body.JsonPayload.Value1Payload (#/paths/repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers/POST/requestBody/json/value1)/reviewers]
warning: A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property. [context: foundIn=Operations.PullsRequestReviewers.Input.Body.JsonPayload.Value2Payload (#/paths/repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers/POST/requestBody/json/value2)/team_reviewers]
warning: A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property. [context: foundIn=Operations.PullsRequestReviewers.Input.Body.JsonPayload.Value1Payload (#/paths/repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers/POST/requestBody/json/value1)/reviewers]
warning: A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property. [context: foundIn=Operations.PullsRequestReviewers.Input.Body.JsonPayload.Value2Payload (#/paths/repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers/POST/requestBody/json/value2)/team_reviewers]
warning: Schema "array ()" is not supported, reason: "not an object-ish schema (object, ref, allOf)", skipping [context: foundIn=Operations.ReposGetContent.Output.Ok.Body]
warning: A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property. [context: foundIn=Operations.ReposCreatePagesSite.Input.Body.JsonPayload.Value1Payload (#/paths/repos/{owner}/{repo}/pages/POST/requestBody/json/value1)/source]
warning: A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property. [context: foundIn=Operations.ReposCreatePagesSite.Input.Body.JsonPayload.Value2Payload (#/paths/repos/{owner}/{repo}/pages/POST/requestBody/json/value2)/build_type]
warning: A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property. [context: foundIn=Operations.ReposUpdateInformationAboutPagesSite.Input.Body.JsonPayload.Value1Payload (#/paths/repos/{owner}/{repo}/pages/PUT/requestBody/json/value1)/build_type]
warning: A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property. [context: foundIn=Operations.ReposUpdateInformationAboutPagesSite.Input.Body.JsonPayload.Value2Payload (#/paths/repos/{owner}/{repo}/pages/PUT/requestBody/json/value2)/source]
warning: A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property. [context: foundIn=Operations.ReposUpdateInformationAboutPagesSite.Input.Body.JsonPayload.Value3Payload (#/paths/repos/{owner}/{repo}/pages/PUT/requestBody/json/value3)/cname]
warning: A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property. [context: foundIn=Operations.ReposUpdateInformationAboutPagesSite.Input.Body.JsonPayload.Value4Payload (#/paths/repos/{owner}/{repo}/pages/PUT/requestBody/json/value4)/public]
warning: A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property. [context: foundIn=Operations.ReposUpdateInformationAboutPagesSite.Input.Body.JsonPayload.Value5Payload (#/paths/repos/{owner}/{repo}/pages/PUT/requestBody/json/value5)/https_enforced]
warning: Schema "array ()" is not supported, reason: "not an object-ish schema (object, ref, allOf)", skipping [context: foundIn=Operations.ReposGetContent.Output.Ok.Body (#/paths/repos/{owner}/{repo}/contents/{path}/GET/responses/200/content)]
warning: A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property. [context: foundIn=Operations.ReposCreatePagesSite.Input.Body.JsonPayload.Value1Payload (#/paths/repos/{owner}/{repo}/pages/POST/requestBody/json/value1)/source]
warning: A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property. [context: foundIn=Operations.ReposCreatePagesSite.Input.Body.JsonPayload.Value2Payload (#/paths/repos/{owner}/{repo}/pages/POST/requestBody/json/value2)/build_type]
warning: A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property. [context: foundIn=Operations.ReposUpdateInformationAboutPagesSite.Input.Body.JsonPayload.Value1Payload (#/paths/repos/{owner}/{repo}/pages/PUT/requestBody/json/value1)/build_type]
warning: A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property. [context: foundIn=Operations.ReposUpdateInformationAboutPagesSite.Input.Body.JsonPayload.Value2Payload (#/paths/repos/{owner}/{repo}/pages/PUT/requestBody/json/value2)/source]
warning: A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property. [context: foundIn=Operations.ReposUpdateInformationAboutPagesSite.Input.Body.JsonPayload.Value3Payload (#/paths/repos/{owner}/{repo}/pages/PUT/requestBody/json/value3)/cname]
warning: A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property. [context: foundIn=Operations.ReposUpdateInformationAboutPagesSite.Input.Body.JsonPayload.Value4Payload (#/paths/repos/{owner}/{repo}/pages/PUT/requestBody/json/value4)/public]
warning: A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property. [context: foundIn=Operations.ReposUpdateInformationAboutPagesSite.Input.Body.JsonPayload.Value5Payload (#/paths/repos/{owner}/{repo}/pages/PUT/requestBody/json/value5)/https_enforced]

NOTE: The following swift build warnings are present on both the main branch and feature branch. The only difference is the naming conventions (camel case vs snake case). These warnings don't affect functionality and are not introduced by this PR.

Click to see the swift build warnings
Sources/meta/Types.swift:295:22: warning: 'hubUrl' is deprecated
Sources/migrations/Types.swift:1894:22: warning: 'hasDownloads' is deprecated
Sources/migrations/Types.swift:1908:22: warning: 'useSquashPrTitleAsDefault' is deprecated
Sources/search/Types.swift:1521:22: warning: 'hasDownloads' is deprecated
Sources/search/Types.swift:1535:22: warning: 'useSquashPrTitleAsDefault' is deprecated
Sources/search/Types.swift:5234:26: warning: 'sort' is deprecated
Sources/search/Types.swift:5235:26: warning: 'order' is deprecated
Sources/search/Client.swift:92:40: warning: 'sort' is deprecated
Sources/search/Client.swift:99:40: warning: 'order' is deprecated
Sources/copilot/Types.swift:1407:22: warning: 'updatedAt' is deprecated
Sources/copilot/Types.swift:1451:22: warning: 'updatedAt' is deprecated
Sources/security-advisories/Types.swift:2190:22: warning: 'hasDownloads' is deprecated
Sources/security-advisories/Types.swift:2204:22: warning: 'useSquashPrTitleAsDefault' is deprecated
Sources/security-advisories/Types.swift:3755:22: warning: 'hasDownloads' is deprecated
Sources/security-advisories/Types.swift:3769:22: warning: 'useSquashPrTitleAsDefault' is deprecated
Sources/gists/Types.swift:2242:22: warning: 'forks' is deprecated
Sources/gists/Types.swift:2243:22: warning: 'history' is deprecated
Sources/dependabot/Client.swift:1217:40: warning: 'page' is deprecated
Sources/dependabot/Client.swift:1224:40: warning: 'perPage' is deprecated
Sources/dependabot/Types.swift:6399:26: warning: 'page' is deprecated
Sources/dependabot/Types.swift:6400:26: warning: 'perPage' is deprecated
Sources/pulls/Types.swift:2215:22: warning: 'hasDownloads' is deprecated
Sources/pulls/Types.swift:2229:22: warning: 'useSquashPrTitleAsDefault' is deprecated
Sources/pulls/Types.swift:7720:30: warning: 'position' is deprecated
Sources/orgs/Types.swift:5878:22: warning: 'advancedSecurityEnabledForNewRepositories' is deprecated
Sources/orgs/Types.swift:5879:22: warning: 'dependabotAlertsEnabledForNewRepositories' is deprecated
Sources/orgs/Types.swift:5880:22: warning: 'dependabotSecurityUpdatesEnabledForNewRepositories' is deprecated
Sources/orgs/Types.swift:5881:22: warning: 'dependencyGraphEnabledForNewRepositories' is deprecated
Sources/orgs/Types.swift:5882:22: warning: 'secretScanningEnabledForNewRepositories' is deprecated
Sources/orgs/Types.swift:5883:22: warning: 'secretScanningPushProtectionEnabledForNewRepositories' is deprecated
Sources/orgs/Types.swift:9399:30: warning: 'advancedSecurityEnabledForNewRepositories' is deprecated
Sources/orgs/Types.swift:9400:30: warning: 'dependabotAlertsEnabledForNewRepositories' is deprecated
Sources/orgs/Types.swift:9401:30: warning: 'dependabotSecurityUpdatesEnabledForNewRepositories' is deprecated
Sources/orgs/Types.swift:9402:30: warning: 'dependencyGraphEnabledForNewRepositories' is deprecated
Sources/orgs/Types.swift:9403:30: warning: 'secretScanningEnabledForNewRepositories' is deprecated
Sources/orgs/Types.swift:9404:30: warning: 'secretScanningPushProtectionEnabledForNewRepositories' is deprecated
Sources/activity/Types.swift:2053:22: warning: 'hasDownloads' is deprecated
Sources/activity/Types.swift:2067:22: warning: 'useSquashPrTitleAsDefault' is deprecated
Sources/apps/Types.swift:3981:22: warning: 'hasDownloads' is deprecated
Sources/apps/Types.swift:3995:22: warning: 'useSquashPrTitleAsDefault' is deprecated
Sources/codespaces/Types.swift:2575:22: warning: 'hasDownloads' is deprecated
Sources/codespaces/Types.swift:2589:22: warning: 'useSquashPrTitleAsDefault' is deprecated
Sources/codespaces/Types.swift:4731:22: warning: 'hasDownloads' is deprecated
Sources/codespaces/Types.swift:4745:22: warning: 'useSquashPrTitleAsDefault' is deprecated
Sources/issues/Types.swift:2907:22: warning: 'hasDownloads' is deprecated
Sources/issues/Types.swift:2921:22: warning: 'useSquashPrTitleAsDefault' is deprecated
Sources/actions/Types.swift:5494:22: warning: 'hasDownloads' is deprecated
Sources/actions/Types.swift:5508:22: warning: 'useSquashPrTitleAsDefault' is deprecated
Sources/repos/Types.swift:7921:22: warning: 'hasDownloads' is deprecated
Sources/repos/Types.swift:7935:22: warning: 'useSquashPrTitleAsDefault' is deprecated
Sources/repos/Types.swift:10315:22: warning: 'hasDownloads' is deprecated
Sources/repos/Types.swift:10329:22: warning: 'useSquashPrTitleAsDefault' is deprecated
Sources/repos/Types.swift:23836:30: warning: 'useSquashPrTitleAsDefault' is deprecated
Sources/repos/Types.swift:26272:30: warning: 'useSquashPrTitleAsDefault' is deprecated
Sources/repos/Types.swift:29399:34: warning: 'contexts' is deprecated
Sources/repos/Types.swift:31871:30: warning: 'contexts' is deprecated

Potential Drawbacks & Mitigation

There is a risk that future GitHub API elements with names that conflict with Swift keywords or conventions might break the automated generation process. However, the significant improvement in API accessibility and developer experience far outweighs the occasional inconvenience of needing to manually configure name overrides when such edge cases arise.

These edge cases are likely to be rare, and the provided override mechanism gives us a straightforward way to handle them when they appear. For example, necessary overrides for the "+1" and "-1" reactions have been included in this PR, and serve as a model for any future scenarios that may arise.

IMHO, the benefit to developer productivity and code readability makes this trade-off well worth it, especially for a team working with this API on a daily basis.

@ryansobol ryansobol marked this pull request as ready for review May 1, 2025 21:40
@wei18 wei18 self-requested a review May 2, 2025 01:31
@wei18
Copy link
Owner

wei18 commented May 4, 2025

Hi @ryansobol,

Thank you for your contribution and for introducing those fantastic features: Configuring the Generator and SOAR-0013.

These ideas are great! I'm planning to bump the version to 3.0.0 to reflect the breaking changes.
I'm also very impressed by how thorough and well-documented the PR description is. WOW.

MISC:

@wei18 wei18 merged commit 15f8f4f into wei18:main May 4, 2025
1 check passed
@wei18 wei18 added the enhancement New feature or request label May 4, 2025
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Is it possible to make functions and types conform to the Swift naming convention?
2 participants