Skip to content

Commit

Permalink
Correctly normalize abbreviations
Browse files Browse the repository at this point in the history
  • Loading branch information
aj-foster committed Mar 21, 2024
1 parent 467189f commit 9be455c
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 30 deletions.
67 changes: 37 additions & 30 deletions lib/open_api/processor/naming.ex
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,7 @@ defmodule OpenAPI.Processor.Naming do
id_name =
if length(modules) > 0 do
modules
|> Enum.map(&normalize_identifier/1)
|> Enum.map(&Macro.camelize/1)
|> Enum.map(&normalize_identifier(&1, :camel))
|> Module.concat()
end

Expand All @@ -134,8 +133,7 @@ defmodule OpenAPI.Processor.Naming do
Enum.map(tags, fn tag ->
tag
|> String.split("/", trim: true)
|> Enum.map(&normalize_identifier/1)
|> Enum.map(&Macro.camelize/1)
|> Enum.map(&normalize_identifier(&1, :camel))
|> Module.concat()
end)
else
Expand Down Expand Up @@ -493,24 +491,49 @@ defmodule OpenAPI.Processor.Naming do
end

@doc """
Normalize an identifier into snake_case
Normalize an identifier into CamelCase or snake_case
## Example
iex> normalize_identifier("get-/customer/purchases/{date}_byId")
"get_customer_purchases_date_by_id"
iex> normalize_identifier("openAPISpec", :camel)
"OpenAPISpec"
"""
@spec normalize_identifier(String.t()) :: String.t()
def normalize_identifier(input) do
@spec normalize_identifier(String.t(), :camel | :snake) :: String.t()
def normalize_identifier(input, casing \\ :snake)

def normalize_identifier(input, :camel) do
input
|> segment_identifier()
|> Enum.map(fn segment ->
if String.match?(segment, ~r/^[A-Z]+$/) do
segment
else
String.capitalize(segment)
end
end)
|> Enum.join()
end

def normalize_identifier(input, :snake) do
input
|> String.split(~r/([^A-Za-z0-9]+)?([A-Z]+)?([a-z0-9]+)?/, include_captures: true, trim: true)
|> Enum.map_join("_", fn segment ->
|> segment_identifier()
|> Enum.map_join("_", &String.downcase/1)
end

@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)
|> Enum.map(fn segment ->
segment
|> String.replace(~r/^[^A-Za-z]+/, "")
|> String.replace(~r/[^A-Za-z0-9]+$/, "")
|> String.downcase()
end)
|> Enum.reject(&(&1 == ""))
end

@doc """
Expand All @@ -535,22 +558,14 @@ defmodule OpenAPI.Processor.Naming do
def raw_schema_module_and_type(_state, _schema, %SchemaSpec{
"$oag_last_ref_path": ["components", "schemas", schema_name]
}) do
module =
schema_name
|> normalize_identifier()
|> Macro.camelize()

module = normalize_identifier(schema_name, :camel)
{module, "t"}
end

def raw_schema_module_and_type(_state, _schema, %SchemaSpec{
"$oag_last_ref_path": ["components", "schemas", schema_name, "items"]
}) do
module =
schema_name
|> normalize_identifier()
|> Macro.camelize()

module = normalize_identifier(schema_name, :camel)
{module, "t"}
end

Expand All @@ -564,11 +579,7 @@ defmodule OpenAPI.Processor.Naming do
"schema"
]
}) do
module =
schema_name
|> normalize_identifier()
|> Macro.camelize()

module = normalize_identifier(schema_name, :camel)
type = Enum.join([readable_content_type(content_type), "resp"], "_")

{module, type}
Expand Down Expand Up @@ -605,11 +616,7 @@ defmodule OpenAPI.Processor.Naming do

def raw_schema_module_and_type(_state, _schema, %SchemaSpec{title: schema_title})
when is_binary(schema_title) do
module =
schema_title
|> normalize_identifier()
|> Macro.camelize()

module = normalize_identifier(schema_title, :camel)
{module, "t"}
end

Expand Down
54 changes: 54 additions & 0 deletions test/fixture/abbreviation.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
components:
schemas:
RequestTOTPCreate:
additionalProperties: false
description: |
Object for a TOTP token config, with confirmation OTP.
properties:
otp:
description: The OTP as obtained from the authenticator app/device
type: string
uri:
description: The OTP Auth URI obtained from `TOTPRequest`
type: string
verification_code:
description: The opaque signature obtained from `TOTPRequest`
type: string
required:
- uri
- otp
- verification_code
title: TOTPCreate
type: object
info:
title: Acme API
version: "1"
openapi: 3.0.0
paths:
/api/v1/me/totp:
post:
callbacks: {}
description: |
Saves a config obtained with `TOTPRequest`, along with a device generated
OTP to confirm it's correctness.
operationId: Controllers.ApiV1.TOTP.totp_save
parameters: []
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/RequestTOTPCreate"
description: The TOTP config to save
required: false
responses:
"201":
description: TOTP request config saved
"429":
description: Too many requests, retry later
summary: Saves a TOTP config
tags: []
servers:
- url: "{endpoint_url}"
variables:
endpoint_url:
default: https://localhost:5443/
12 changes: 12 additions & 0 deletions test/open_api/processor/naming_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,21 @@ defmodule OpenAPI.Processor.NamingTest do
describe "normalize_identifier/1" do
test "normalizes identifiers" do
assert Naming.normalize_identifier("example") == "example"
assert Naming.normalize_identifier("example", :camel) == "Example"

assert Naming.normalize_identifier("example_op") == "example_op"
assert Naming.normalize_identifier("example_op", :camel) == "ExampleOp"

assert Naming.normalize_identifier("exampleOp") == "example_op"
assert Naming.normalize_identifier("exampleOp", :camel) == "ExampleOp"

assert Naming.normalize_identifier("mod_{NAME}/example-Op") == "mod_name_example_op"
assert Naming.normalize_identifier("mod_{NAME}/example-Op", :camel) == "ModNAMEExampleOp"
end

test "preserves abbreviations" do
assert Naming.normalize_identifier("OpenAPISpec") == "open_api_spec"
assert Naming.normalize_identifier("OpenAPISpec", :camel) == "OpenAPISpec"
end
end
end

0 comments on commit 9be455c

Please sign in to comment.