Skip to content

Commit

Permalink
Merge pull request #49 from aj-foster/aj/normalize-field-names
Browse files Browse the repository at this point in the history
Normalize Field Names
  • Loading branch information
aj-foster authored Jun 24, 2024
2 parents 9ce5f72 + 7749537 commit 039613a
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,4 @@ jobs:
env:
GH_TOKEN: ${{ secrets.DIFF_TOKEN }}
run: |
gh workflow run generate.yml --repo aj-foster/open-api-diffs --field ref=$GITHUB_SHA
gh workflow run generate.yml --repo aj-foster/open-api-diffs --field ref=$GITHUB_HEAD_REF
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

_Nothing yet._
* **Breaking**: Snake case normalization (ex. function names) now correctly segments numbers.
For example, an operation `v2example` is now output as `v2_example`.
This may be a breaking change for clients with numbers in operation IDs.

* **Add**: New configuration option `naming.field_casing` to choose between `:camel` case, `:snake` case, or performing no normalization (`nil`, the default).
Using this option may be necessary for API descriptions that include non-normalized field names (for example, fields that begin with a number or symbol).
Setting this configuration would be a breaking change for any clients based on API descriptions that have inconsistent field casing.

### 0.1.1 (2024-05-17)

Expand Down
4 changes: 4 additions & 0 deletions guides/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,10 @@ Remember that all configuration values must be contained within a profile.
Defaults to `Operations`.
See `OpenAPI.Processor.Naming.operation_modules/2` for more information.

* `naming.field_casing`: Either `:camel`, `:snake`, or `nil` (default) to output schema field names as `camelCase`, `snake_case`, or leave the fields name as-is from the API description.
Changing the field casing is likely to be a breaking change for clients, unless the API description consistently uses the same casing.
Setting this field may be necessary if field names require normalization (ex. if a field begins with a number).

* `naming.group`: List of module namespaces to use while naming operations and schemas.
Defaults to an empty list of modules.
See `OpenAPI.Processor.Naming.group_schema/2` for more information.
Expand Down
16 changes: 16 additions & 0 deletions lib/open_api/processor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,13 @@ defmodule OpenAPI.Processor do
end
end)

field_name =
case config(state, :field_casing) do
:camel -> OpenAPI.Processor.Naming.normalize_identifier(field_name, :lower_camel)
:snake -> OpenAPI.Processor.Naming.normalize_identifier(field_name, :snake)
_else -> field_name
end

field = %Field{
name: field_name,
nullable: nullable?,
Expand Down Expand Up @@ -495,4 +502,13 @@ defmodule OpenAPI.Processor do
State.put_schema(state, schema.ref, schema)
end
end

@spec config(OpenAPI.Processor.State.t(), atom) :: term
@spec config(OpenAPI.Processor.State.t(), atom, term) :: term
defp config(state, key, default \\ nil) do
%OpenAPI.Processor.State{profile: profile} = state

Application.get_env(:oapi_generator, profile, [])
|> Keyword.get(key, default)
end
end
35 changes: 29 additions & 6 deletions lib/open_api/processor/naming.ex
Original file line number Diff line number Diff line change
Expand Up @@ -501,8 +501,11 @@ defmodule OpenAPI.Processor.Naming do
iex> normalize_identifier("openAPISpec", :camel)
"OpenAPISpec"
iex> normalize_identifier("get-/customer/purchases/{date}_byId", :lower_camel)
"getCustomerPurchasesDateById"
"""
@spec normalize_identifier(String.t(), :camel | :snake) :: String.t()
@spec normalize_identifier(String.t(), :camel | :lower_camel | :snake) :: String.t()
def normalize_identifier(input, casing \\ :snake)

def normalize_identifier(input, :camel) do
Expand All @@ -518,6 +521,21 @@ defmodule OpenAPI.Processor.Naming do
|> Enum.join()
end

def normalize_identifier(input, :lower_camel) do
[first_segment | segments] = segment_identifier(input)

segments =
Enum.map(segments, fn segment ->
if String.match?(segment, ~r/^[A-Z]+$/) do
segment
else
String.capitalize(segment)
end
end)

Enum.join([first_segment | segments])
end

def normalize_identifier(input, :snake) do
input
|> segment_identifier()
Expand All @@ -526,12 +544,17 @@ defmodule OpenAPI.Processor.Naming do

@doc false
def segment_identifier(input) do
input
|> String.split(~r/[^A-Za-z0-9]+|([A-Z]?[a-z0-9]+)/, include_captures: true, trim: true)
[first_segment | segments] =
String.split(input, ~r/[^A-Za-z0-9]+|([A-Z]?[a-z]+[0-9]?+)/,
include_captures: true,
trim: true
)

first_segment = String.replace(first_segment, ~r/^[^A-Za-z]+/, "")

[first_segment | segments]
|> Enum.map(fn segment ->
segment
|> String.replace(~r/^[^A-Za-z]+/, "")
|> String.replace(~r/[^A-Za-z0-9]+$/, "")
String.replace(segment, ~r/[^A-Za-z0-9]+$/, "")
end)
|> Enum.reject(&(&1 == ""))
end
Expand Down

0 comments on commit 039613a

Please sign in to comment.