Skip to content

Commit

Permalink
Merge branch 'aj-foster:main' into nested_schema_reference
Browse files Browse the repository at this point in the history
  • Loading branch information
McSym28 authored Jun 29, 2024
2 parents 527cac2 + ba8f0ba commit c213b55
Show file tree
Hide file tree
Showing 13 changed files with 156 additions and 25 deletions.
39 changes: 29 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,32 @@ on: [pull_request]
jobs:
test:
name: "Test"
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
# See https://hexdocs.pm/elixir/compatibility-and-deprecations.html#compatibility-between-elixir-and-erlang-otp
strategy:
matrix:
include:
- pair:
otp: "23.3.4.20"
elixir: "1.14.5"
- pair:
otp: "27.0"
elixir: "1.17.1"
lint: lint
env:
MIX_ENV: test
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Setup BEAM
uses: erlef/setup-beam@v1
with:
otp-version: "25.2"
elixir-version: "1.14.2"
otp-version: ${{matrix.pair.otp}}
elixir-version: ${{matrix.pair.elixir}}

- name: Fetch Hex Cache
uses: actions/cache@v3
uses: actions/cache@v4
id: hex-cache
with:
path: |
Expand All @@ -29,17 +40,25 @@ jobs:
restore-keys: |
${{ runner.os }}-mix-
- name: Check Code Format
run: mix format --check-formatted
if: ${{matrix.lint}}

- name: Run Tests
run: |
mix deps.get
mix test
- name: Publish Results
uses: EnricoMi/publish-unit-test-result-action@v2
if: ${{ success() || failure() }}
uses: dorny/test-reporter@v1
if: ${{ failure() }}
with:
comment_mode: "off"
junit_files: _build/test/lib/oapi_generator/*.xml
fail-on-error: "false"
list-suites: failed
list-tests: failed
name: Results
path: _build/test/lib/oapi_generator/*.xml
reporter: java-junit

diff:
name: "Generate Diff"
Expand All @@ -49,4 +68,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
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Release
on:
push:
tags:
- '*'
- "*"

jobs:
publish:
Expand All @@ -16,8 +16,8 @@ jobs:
- name: Setup BEAM
uses: erlef/setup-beam@v1
with:
otp-version: "25.2"
elixir-version: "1.14.2"
otp-version: "27.0"
elixir-version: "1.17.1"

- name: Publish Package
run: |
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ oapi_generator-*.tar

# Test output files
/example*

# Test specifications
/specs
4 changes: 2 additions & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
elixir 1.16.2-otp-26
erlang 26.2.2
elixir 1.17.1-otp-27
erlang 27.0
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
35 changes: 35 additions & 0 deletions lib/open_api/processor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,34 @@ defmodule OpenAPI.Processor do
quote do
@behaviour OpenAPI.Processor

@impl OpenAPI.Processor
defdelegate ignore_operation?(state, operation), to: OpenAPI.Processor

@impl OpenAPI.Processor
defdelegate ignore_schema?(state, schema), to: OpenAPI.Processor

@impl OpenAPI.Processor
defdelegate operation_docstring(state, operation_spec, params), to: OpenAPI.Processor

@impl OpenAPI.Processor
defdelegate operation_function_name(state, operation_spec), to: OpenAPI.Processor

@impl OpenAPI.Processor
defdelegate operation_module_names(state, operation_spec), to: OpenAPI.Processor

@impl OpenAPI.Processor
defdelegate operation_request_body(state, operation_spec), to: OpenAPI.Processor

@impl OpenAPI.Processor
defdelegate operation_request_method(state, operation_spec), to: OpenAPI.Processor

@impl OpenAPI.Processor
defdelegate operation_response_body(state, operation_spec), to: OpenAPI.Processor

@impl OpenAPI.Processor
defdelegate schema_format(state, schema), to: OpenAPI.Processor

@impl OpenAPI.Processor
defdelegate schema_module_and_type(state, schema), to: OpenAPI.Processor

defoverridable ignore_operation?: 2,
Expand Down Expand Up @@ -438,6 +457,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 +521,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
31 changes: 31 additions & 0 deletions lib/open_api/renderer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,52 @@ defmodule OpenAPI.Renderer do
quote do
@behaviour OpenAPI.Renderer

@impl OpenAPI.Renderer
defdelegate format(state, file), to: OpenAPI.Renderer

@impl OpenAPI.Renderer
defdelegate location(state, file), to: OpenAPI.Renderer

@impl OpenAPI.Renderer
defdelegate render(state, file), to: OpenAPI.Renderer

@impl OpenAPI.Renderer
defdelegate render_default_client(state, file), to: OpenAPI.Renderer

@impl OpenAPI.Renderer
defdelegate render_moduledoc(state, file), to: OpenAPI.Renderer

@impl OpenAPI.Renderer
defdelegate render_operations(state, file), to: OpenAPI.Renderer

@impl OpenAPI.Renderer
defdelegate render_operation(state, operation), to: OpenAPI.Renderer

@impl OpenAPI.Renderer
defdelegate render_operation_doc(state, operation), to: OpenAPI.Renderer

@impl OpenAPI.Renderer
defdelegate render_operation_function(state, operation), to: OpenAPI.Renderer

@impl OpenAPI.Renderer
defdelegate render_operation_spec(state, operation), to: OpenAPI.Renderer

@impl OpenAPI.Renderer
defdelegate render_schema(state, file), to: OpenAPI.Renderer

@impl OpenAPI.Renderer
defdelegate render_schema_field_function(state, schemas), to: OpenAPI.Renderer

@impl OpenAPI.Renderer
defdelegate render_schema_struct(state, schemas), to: OpenAPI.Renderer

@impl OpenAPI.Renderer
defdelegate render_schema_types(state, schemas), to: OpenAPI.Renderer

@impl OpenAPI.Renderer
defdelegate render_using(state, file), to: OpenAPI.Renderer

@impl OpenAPI.Renderer
defdelegate write(state, file), to: OpenAPI.Renderer

defoverridable format: 2,
Expand Down
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ defmodule OpenAPI.MixProject do
[
app: :oapi_generator,
version: @version,
elixir: "~> 1.13",
elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
name: "OpenAPI Generator",
Expand All @@ -29,6 +29,7 @@ defmodule OpenAPI.MixProject do
defp deps do
[
{:ex_doc, "~> 0.29", only: :dev, runtime: false},
{:junit_formatter, "~> 3.4", only: [:test]},
{:yaml_elixir, "~> 2.9"}
]
end
Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
%{
"earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
"ex_doc": {:hex, :ex_doc, "0.32.2", "f60bbeb6ccbe75d005763e2a328e6f05e0624232f2393bc693611c2d3ae9fa0e", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "a4480305cdfe7fdfcbb77d1092c76161626d9a7aa4fb698aee745996e34602df"},
"junit_formatter": {:hex, :junit_formatter, "3.4.0", "d0e8db6c34dab6d3c4154c3b46b21540db1109ae709d6cf99ba7e7a2ce4b1ac2", [:mix], [], "hexpm", "bb36e2ae83f1ced6ab931c4ce51dd3dbef1ef61bb4932412e173b0cfa259dacd"},
"makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
"makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"},
Expand Down
8 changes: 6 additions & 2 deletions test/open_api/processor/ignore_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ defmodule OpenAPI.Processor.IgnoreTest do
base_file_path = ["components", "schemas", "ignored_schema"]
schema = %OpenAPI.Spec.Schema{schema | "$oag_base_file_path": base_file_path}

Application.put_env(:oapi_generator, @profile, ignore: ["components/schemas/ignored_schema"])
Application.put_env(:oapi_generator, @profile,
ignore: ["components/schemas/ignored_schema"]
)

assert Ignore.ignore_schema?(state, schema)
end
Expand All @@ -68,7 +70,9 @@ defmodule OpenAPI.Processor.IgnoreTest do
ref_path = ["components", "schemas", "ignored_schema"]
schema = %OpenAPI.Spec.Schema{schema | "$oag_last_ref_path": ref_path}

Application.put_env(:oapi_generator, @profile, ignore: ["components/schemas/ignored_schema"])
Application.put_env(:oapi_generator, @profile,
ignore: ["components/schemas/ignored_schema"]
)

assert Ignore.ignore_schema?(state, schema)
end
Expand Down
4 changes: 4 additions & 0 deletions test/test_helper.exs
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
if System.get_env("CI") do
ExUnit.configure(formatters: [JUnitFormatter])
end

ExUnit.start()

0 comments on commit c213b55

Please sign in to comment.