Skip to content

Commit e4b4624

Browse files
authored
SOAR-0002: Improved naming of content types (#170)
### Motivation See proposal text. ### Modifications N/A ### Result N/A ### Test Plan N/A
1 parent abcc8bb commit e4b4624

File tree

4 files changed

+153
-4
lines changed

4 files changed

+153
-4
lines changed

Sources/swift-openapi-generator/Documentation.docc/Proposals/Proposals.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,5 @@ If you have any questions, tag [Honza Dvorsky](https://github.com/czechboy0) or
4343
## Topics
4444

4545
- <doc:SOAR-NNNN>
46+
- <doc:SOAR-0001>
47+
- <doc:SOAR-0002>

Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0001.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# SOAR-0001
1+
# SOAR-0001: Improved mapping of identifiers
22

3-
Encoding for Property Names
3+
Improved mapping of OpenAPI identifiers to Swift identifiers.
44

55
## Overview
66

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# SOAR-0002: Improved naming of content types
2+
3+
Improved naming of content types to Swift identifiers.
4+
5+
## Overview
6+
7+
- Proposal: SOAR-0002
8+
- Author(s): [Honza Dvorsky](https://github.com/czechboy0)
9+
- Status: **In Preview**
10+
- Issue: N/A, was part of multiple content type support: [apple/swift-openapi-generator#6](https://github.com/apple/swift-openapi-generator/issues/6) and [apple/swift-openapi-generator#7](https://github.com/apple/swift-openapi-generator/issues/7)
11+
- Implementation:
12+
- [Landed behind a feature flag as part of apple/swift-openapi-generator#146](https://github.com/czechboy0/swift-openapi-generator/blob/4555f8e998b24aa65a462a63828d9195c50dcc23/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentSwiftName.swift#L23-L42)
13+
- Feature flag: `multipleContentTypes`
14+
- Affected components:
15+
- generator
16+
- Versions:
17+
- v1 (2023-08-07): First draft
18+
- v2 (2023-08-08): Second draft with the following changes:
19+
- added 6 more short names
20+
- updated short names for a few of the originally proposed content types
21+
- updated the logic for generic names, gets rid of `_sol_` for the slash
22+
- v3 (2023-08-08): Third draft with the following changes:
23+
- `multipart/form-data` short name changed from `formData` to `multipartForm`
24+
25+
### Introduction
26+
27+
Introduce a new content type -> Swift name naming scheme to allow for multiple content types within the same request or response body.
28+
29+
### Motivation
30+
31+
Previously, the logic for assigning a Swift name to a content type always produced one of the following three strings: `json`, `text`, or `binary`.
32+
33+
That worked fine at the beginning, but now with multiple content type support for [request](https://github.com/apple/swift-openapi-generator/issues/7) and [response](https://github.com/apple/swift-openapi-generator/issues/6) bodies landed behind a feature flag, we need a naming scheme that produces much fewer conflicts.
34+
35+
Without the change, the following OpenAPI snippet would continue to fail to build:
36+
37+
```yaml
38+
paths:
39+
/foo:
40+
get:
41+
responses:
42+
'200':
43+
content:
44+
application/json: {}
45+
application/vendor1+json: {}
46+
application/vendor2+json: {}
47+
```
48+
49+
That's because all three would use the name `json` in the generated `Output.*.Body` enum.
50+
51+
There are currently no workarounds apart from removing the additional content types from your OpenAPI document.
52+
53+
### Proposed solution
54+
55+
I propose to extend the naming logic to achieve two goals:
56+
- continue to use short and ergonomic names for common content types, like today
57+
- avoid conflicts for arbitrary, less common content types using the new logic introduced in [SOAR-0001](https://github.com/apple/swift-openapi-generator/blob/main/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0001.md) _for each component of the content type, and concatenate them with an underscore_ (**changed in v2**)
58+
59+
In practical terms, it means that if a content type exactly matches one of the predefined content types that have a short name assigned, the short name will be used.
60+
61+
Otherwise, each component of the content type string (for an example `application/vendor1+json` the components would be `application` and `vendor1+json`) will be passed to the `swiftSafeName` function, which was improved in [SOAR-0001](https://github.com/apple/swift-openapi-generator/blob/main/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0001.md), and produce a deterministic name that is unlikely to conflict with any other content type.
62+
63+
Let's look at a few examples:
64+
- for a common content type, such as `application/json`, a short name `json` will be used
65+
- for an arbitrary content type, such as `application/vendor1+json`, a deterministic name will be produced, such as `application_vendor1_plus_json` (**changed in v2**, was `application_sol_vendor1_plus_json` in v1)
66+
67+
This way, adopters continue to get short names for commonly used content types, but can also use completely custom content types, without getting a build error in the generated code.
68+
69+
### Detailed design
70+
71+
The whole implementation of the proposed logic for the function `func contentSwiftName(_ contentType: ContentType) -> String` in `FileTranslator` would change to the following (shows the list of predefined content types):
72+
73+
```swift
74+
func contentSwiftName(_ contentType: ContentType) -> String {
75+
switch contentType.lowercasedTypeAndSubtype {
76+
case "application/json":
77+
return "json"
78+
case "application/x-www-form-urlencoded":
79+
return "urlEncodedForm"
80+
case "multipart/form-data":
81+
return "multipartForm"
82+
case "text/plain":
83+
return "plainText"
84+
case "*/*":
85+
return "any"
86+
case "application/xml":
87+
return "xml"
88+
case "application/octet-stream":
89+
return "binary"
90+
case "text/html":
91+
return "html"
92+
case "application/yaml":
93+
return "yaml"
94+
case "text/csv":
95+
return "csv"
96+
case "image/png":
97+
return "png"
98+
case "application/pdf":
99+
return "pdf"
100+
case "image/jpeg":
101+
return "jpeg"
102+
default:
103+
let safedType = swiftSafeName(for: contentType.originallyCasedType)
104+
let safedSubtype = swiftSafeName(for: contentType.originallyCasedSubtype)
105+
return "\(safedType)_\(safedSubtype)"
106+
}
107+
}
108+
```
109+
110+
The above shows that the content types that have a short name assigned are:
111+
- `application/json` -> `json`
112+
- `application/x-www-form-urlencoded` -> `urlEncodedForm` (**changed in v2**, was `form` in v1)
113+
- `multipart/form-data` -> `multipartForm` (**changed in v2 and v3**, was `multipart` in v1, `formData` in v2)
114+
- `text/plain` -> `plainText` (**changed in v2**, was `text` in v1)
115+
- `*/*` -> `any`
116+
- `application/xml` -> `xml`
117+
- `application/octet-stream` -> `binary`
118+
- `text/html` -> `html` (**added in v2**)
119+
- `application/yaml` -> `yaml` (**added in v2**)
120+
- `text/csv` -> `csv` (**added in v2**)
121+
- `image/png` -> `png` (**added in v2**)
122+
- `application/pdf` -> `pdf` (**added in v2**)
123+
- `image/jpeg` -> `jpeg` (**added in v2**)
124+
125+
These specific values were not chosen arbitrarily, instead I wrote a script that collected and processed about 1200 OpenAPI documents from the wild, and aggregated usage statistics. These content types, in this order, were the top used content types from those documents.
126+
127+
> Note: While Swift OpenAPI Generator does not yet support some of the content types above (such as `multipart/form-data` (tracked by [#36](https://github.com/apple/swift-openapi-generator/issues/36)) and `*/*` (tracked by [#71](https://github.com/apple/swift-openapi-generator/issues/71))), we should still make room for them here now, as changing the naming logic is a breaking change, so we don't want to undergo it again in the future.
128+
129+
### API stability
130+
131+
This change breaks backwards compatibility of existing generated code as it renames the enum cases in the generated `Body` enums for requests and responses.
132+
133+
The change is currently hidden behind the `multipleContentTypes` feature flag, and once approved, would be rolled out together with that feature in the next breaking version (likely 0.2.0).
134+
135+
No other API impact.
136+
137+
### Future directions
138+
139+
Nothing comes to mind right now, as we already make provisions for not-yet-supported content types (see the note about `multipart/form-data` and `*/*`), so I'm not expecting a need to change this naming logic again.
140+
141+
### Alternatives considered
142+
143+
#### No short names
144+
145+
A conceptually simpler solution to the problem of conflicting content type Swift names was to always generate full names (such as `application/vendor1+json` -> `application_vendor1_plus_json`), however that would have resulted in unnecessarily long names for common content types, for example, `application/json` would have been `application_json`, instead of `json`. _However, projects in the ecosystem that provide type-safe access to common content types also use short names, showing that developers don't seem to get confused by the commonly used short names._ (**sentence added in v2**)
146+
147+
This idea was rejected as data from real-world OpenAPI documents showed that there is a very small number (~13) (**changed in v2**, was ~7 in v1) of content types that are used most often, so making the readability for adopters easier comes at a relatively low cost (see the full implementation of the naming logic above). This follows the principle of making the simple things easy/pretty, and difficult things possible/usable.

Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-NNNN.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# SOAR-NNNN
1+
# SOAR-NNNN: Feature name
22

3-
Feature name (template proposal)
3+
Feature abstract – a one sentence summary.
44

55
## Overview
66

0 commit comments

Comments
 (0)