From 267c1f0d5b27a1bf8e280ebbb76b1a11c4ba44ff Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Tue, 6 Jun 2023 17:20:23 +0200 Subject: [PATCH 1/3] [Docs] Document the Converter type in preparation for #43 --- ...Converting-between-data-and-Swift-types.md | 234 ++++++++++++++++++ .../Documentation-for-maintainers.md | 13 + .../Swift-OpenAPI-Generator.md | 1 + 3 files changed, 248 insertions(+) create mode 100644 Sources/swift-openapi-generator/Documentation.docc/Development/Converting-between-data-and-Swift-types.md create mode 100644 Sources/swift-openapi-generator/Documentation.docc/Development/Documentation-for-maintainers.md diff --git a/Sources/swift-openapi-generator/Documentation.docc/Development/Converting-between-data-and-Swift-types.md b/Sources/swift-openapi-generator/Documentation.docc/Development/Converting-between-data-and-Swift-types.md new file mode 100644 index 00000000..e221ab23 --- /dev/null +++ b/Sources/swift-openapi-generator/Documentation.docc/Development/Converting-between-data-and-Swift-types.md @@ -0,0 +1,234 @@ +# Converting between data and Swift types + +Learn about the type responsible for convertering between raw data and Swift types. + +## Overview + +The [`Converter`](https://github.com/apple/swift-openapi-runtime/blob/main/Sources/OpenAPIRuntime/Conversion/Converter.swift) type is a structure defined in the runtime library and is used by both the client and server generated code to perform conversions between raw data and Swift types. + +> Note: `Converter` is one of the SPI types, not considered part of the public API of the runtime library. However, because generated code relies on it, SPI stability needs to be considered when making changes to it and to the generator. + +Most of the functionality of `Converter` is implemented as helper methods in extensions: +- [`Converter+Client.swift`](https://github.com/apple/swift-openapi-runtime/blob/main/Sources/OpenAPIRuntime/Conversion/Converter%2BClient.swift) +- [`Converter+Server.swift`](https://github.com/apple/swift-openapi-runtime/blob/main/Sources/OpenAPIRuntime/Conversion/Converter%2BServer.swift) +- [`Converter+Common.swift`](https://github.com/apple/swift-openapi-runtime/blob/main/Sources/OpenAPIRuntime/Conversion/Converter%2BCommon.swift) + +Some helper methods can be reused between client and server code, such as headers, but most can't. It's important that we only generalize (move helper methods into common extensions) if the client and server variants would have been exact copies. However, if there are differences, prefer to keep them separate and optimize each variant (for client or server) separately. + +### Generated code and generics interaction + +As outlined in , we aim to minimize the complexity of the generator and rely on the Swift compiler to help ensure that if generated code compiles, it's likely to work correctly. + +To that end, if the input OpenAPI document contains an input that Swift OpenAPI Generator doesn't support, our first preference is to catch it in the generator and emit a descriptive error. However, there are cases where that is prohibitively complex, and we let the Swift compiler ensure that, for example, an array of strings cannot be used as a path parameter. In this example case, the generator emits code with the path parameter being of Swift type `[String]`, but since there doesn't exist a converter method for it, it will fail to build. This is considered expected behavior. + +In the case of the converter, it contains helper methods for all the supported combinations of an HTTP location, a "content type family" and a Swift type. + +First, a _schema location_ refers to one of the several places where schemas can be used in OpenAPI documents. For example: +- request path parameters +- request headers +- response bodies +- and more + +Second, a _content type family_ can be one of: +- `structured` + - example: `application/json` + - uses the type's `Codable` implementation +- `text` + - example: `text/plain` + - uses the type's `LosslessStringConvertible` implementation, except for `Foundation.Date`, which uses a system date formatter +- `raw` + - example: `application/octet-stream` + - doesn't transform the raw data, just passes it through + +The content type family is derived from the `content` map in the OpenAPI document, if provided. If none is provided, such as in case of parameters, `text` is used. + +And third, a Swift type is calculated from the JSON schema provided in the OpenAPI document. + +For example, a `string` schema is generated as `Swift.String`, an `object` schema is generated as a Swift structure, and an array schema is generated as a `Swift.Array` generic over the element type. + +Together, the schema location, the content type family, and the Swift type is enough to unambiguously decide which helper method on the converter should be used. + +For example, to use the converter to get a required response header of type `Foundation.Date` using the `text` content type family, look for a method (exact spelling is subject to change) that looks like: + +```swift +func headerFieldGetTextRequired( // <<< 1. + in headerFields: [HeaderField], + name: String, + as type: Date.Type // <<< 2. +) throws -> Date +``` + +In `1.`, notice that the method name contains which schema location, content type family, and optionality; whilie in `2.` it contains the Swift type. + +### Helper method variants + +In the nested list below, each leaf is one helper method. + +"string-convertible" refers to types that conform to `LosslessStringConvertible` (but not `Foundation.Date`, which is handled separately). + + +#### Required by client code + +- request + - set request path [client-only] + - text + - string-convertible + - optional/required + - date + - optional/required + - set request query [client-only] + - text + - string-convertible + - optional/required + - array of string-convertibles + - optional/required + - date + - optional/required + - array of dates + - optional/required + - set request headers [common] + - text + - string-convertible + - optional/required + - array of string-convertibles + - optional/required + - date + - optional/required + - array of dates + - optional/required + - structured + - codable + - optional/required + - set request body [client-only] + - text + - string-convertible + - optional + - required + - date + - optional + - required + - structured + - codable + - optional + - required + - raw + - data + - optional + - required +- response + - get response headers [common] + - text + - string-convertible + - optional + - required + - array of string-convertibles + - optional + - required + - date + - optional + - required + - array of dates + - optional + - required + - structured + - codable + - optional + - required + - get response body [client-only] + - text + - string-convertible + - required + - date + - required + - structured + - codable + - required + - raw + - data + - required + +#### Required by server code + +- request + - get request path [server-only] + - text + - string-convertible + - optional + - required + - date + - optional + - required + - get request query [server-only] + - text + - string-convertible + - optional + - required + - array of string-convertibles + - optional + - required + - date + - optional + - required + - array of dates + - optional + - required + - get request headers [common] + - text + - string-convertible + - optional + - required + - array of string-convertibles + - optional + - required + - date + - optional + - required + - array of dates + - optional + - required + - structured + - codable + - optional + - required + - get request body [server-only] + - text + - string-convertible + - optional + - required + - date + - optional + - required + - structured + - codable + - optional + - required + - raw + - data + - optional + - required +- response + - set response headers [common] + - text + - string-convertible + - optional/required + - array of string-convertibles + - optional/required + - date + - optional/required + - array of dates + - optional/required + - structured + - codable + - optional/required + - set response body [server-only] + - text + - string-convertible + - required + - date + - required + - structured + - codable + - required + - raw + - data + - required diff --git a/Sources/swift-openapi-generator/Documentation.docc/Development/Documentation-for-maintainers.md b/Sources/swift-openapi-generator/Documentation.docc/Development/Documentation-for-maintainers.md new file mode 100644 index 00000000..6ac22f4a --- /dev/null +++ b/Sources/swift-openapi-generator/Documentation.docc/Development/Documentation-for-maintainers.md @@ -0,0 +1,13 @@ +# Documentation for maintainers + +Learn about the internals of Swift OpenAPI Generator. + +## Overview + +Swift OpenAPI Generator contains multiple moving pieces, from the runtime library, to the generator CLI, plugin, to extension packages using the transport and middleware APIs. + +Use the resources below if you'd like to learn more about how the generator works under the hood, for example as part of contribututing a new feature to it. + +## Topics + +- diff --git a/Sources/swift-openapi-generator/Documentation.docc/Swift-OpenAPI-Generator.md b/Sources/swift-openapi-generator/Documentation.docc/Swift-OpenAPI-Generator.md index 7da32322..6ef76c61 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Swift-OpenAPI-Generator.md +++ b/Sources/swift-openapi-generator/Documentation.docc/Swift-OpenAPI-Generator.md @@ -75,6 +75,7 @@ The generated code, runtime library, and transports are supported on more platfo ### Getting involved - - +- [openapi]: https://openapis.org [tools]: https://openapi.tools From 7190f479ee892f302526a9768bf5e4ea05523a1e Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Tue, 6 Jun 2023 18:16:14 +0200 Subject: [PATCH 2/3] PR feedback --- .../Development/Converting-between-data-and-Swift-types.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/swift-openapi-generator/Documentation.docc/Development/Converting-between-data-and-Swift-types.md b/Sources/swift-openapi-generator/Documentation.docc/Development/Converting-between-data-and-Swift-types.md index e221ab23..cf5b992b 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Development/Converting-between-data-and-Swift-types.md +++ b/Sources/swift-openapi-generator/Documentation.docc/Development/Converting-between-data-and-Swift-types.md @@ -19,7 +19,7 @@ Some helper methods can be reused between client and server code, such as header As outlined in , we aim to minimize the complexity of the generator and rely on the Swift compiler to help ensure that if generated code compiles, it's likely to work correctly. -To that end, if the input OpenAPI document contains an input that Swift OpenAPI Generator doesn't support, our first preference is to catch it in the generator and emit a descriptive error. However, there are cases where that is prohibitively complex, and we let the Swift compiler ensure that, for example, an array of strings cannot be used as a path parameter. In this example case, the generator emits code with the path parameter being of Swift type `[String]`, but since there doesn't exist a converter method for it, it will fail to build. This is considered expected behavior. +To that end, if the input OpenAPI document contains an input that Swift OpenAPI Generator doesn't support, our first preference is to catch it in the generator and emit a descriptive diagnostic. However, there are cases where that is prohibitively complex, and we let the Swift compiler ensure that, for example, an array of strings cannot be used as a path parameter. In this example case, the generator emits code with the path parameter being of Swift type `[String]`, but since there doesn't exist a converter method for it, it will fail to build. This is considered expected behavior. In the case of the converter, it contains helper methods for all the supported combinations of an HTTP location, a "content type family" and a Swift type. @@ -58,7 +58,7 @@ func headerFieldGetTextRequired( // <<< 1. ) throws -> Date ``` -In `1.`, notice that the method name contains which schema location, content type family, and optionality; whilie in `2.` it contains the Swift type. +In `1.`, notice that the method name contains which schema location, content type family, and optionality; while in `2.` it contains the Swift type. ### Helper method variants From ed1e0ec9426360c8bda312ac6cbba9f8e16fcb7e Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Wed, 7 Jun 2023 13:27:29 +0200 Subject: [PATCH 3/3] Updated Converter docs based on feedback --- ...Converting-between-data-and-Swift-types.md | 313 ++++++------------ 1 file changed, 103 insertions(+), 210 deletions(-) diff --git a/Sources/swift-openapi-generator/Documentation.docc/Development/Converting-between-data-and-Swift-types.md b/Sources/swift-openapi-generator/Documentation.docc/Development/Converting-between-data-and-Swift-types.md index cf5b992b..f8995ccc 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Development/Converting-between-data-and-Swift-types.md +++ b/Sources/swift-openapi-generator/Documentation.docc/Development/Converting-between-data-and-Swift-types.md @@ -1,10 +1,10 @@ # Converting between data and Swift types -Learn about the type responsible for convertering between raw data and Swift types. +Learn about the type responsible for convertering between binary data and Swift types. ## Overview -The [`Converter`](https://github.com/apple/swift-openapi-runtime/blob/main/Sources/OpenAPIRuntime/Conversion/Converter.swift) type is a structure defined in the runtime library and is used by both the client and server generated code to perform conversions between raw data and Swift types. +The [`Converter`](https://github.com/apple/swift-openapi-runtime/blob/main/Sources/OpenAPIRuntime/Conversion/Converter.swift) type is a structure defined in the runtime library and is used by both the client and server generated code to perform conversions between binary data and Swift types. > Note: `Converter` is one of the SPI types, not considered part of the public API of the runtime library. However, because generated code relies on it, SPI stability needs to be considered when making changes to it and to the generator. @@ -21,214 +21,107 @@ As outlined in , we aim to minimize the complexity To that end, if the input OpenAPI document contains an input that Swift OpenAPI Generator doesn't support, our first preference is to catch it in the generator and emit a descriptive diagnostic. However, there are cases where that is prohibitively complex, and we let the Swift compiler ensure that, for example, an array of strings cannot be used as a path parameter. In this example case, the generator emits code with the path parameter being of Swift type `[String]`, but since there doesn't exist a converter method for it, it will fail to build. This is considered expected behavior. -In the case of the converter, it contains helper methods for all the supported combinations of an HTTP location, a "content type family" and a Swift type. - -First, a _schema location_ refers to one of the several places where schemas can be used in OpenAPI documents. For example: -- request path parameters -- request headers -- response bodies -- and more - -Second, a _content type family_ can be one of: -- `structured` - - example: `application/json` - - uses the type's `Codable` implementation -- `text` - - example: `text/plain` - - uses the type's `LosslessStringConvertible` implementation, except for `Foundation.Date`, which uses a system date formatter -- `raw` - - example: `application/octet-stream` - - doesn't transform the raw data, just passes it through - -The content type family is derived from the `content` map in the OpenAPI document, if provided. If none is provided, such as in case of parameters, `text` is used. - -And third, a Swift type is calculated from the JSON schema provided in the OpenAPI document. - -For example, a `string` schema is generated as `Swift.String`, an `object` schema is generated as a Swift structure, and an array schema is generated as a `Swift.Array` generic over the element type. - -Together, the schema location, the content type family, and the Swift type is enough to unambiguously decide which helper method on the converter should be used. - -For example, to use the converter to get a required response header of type `Foundation.Date` using the `text` content type family, look for a method (exact spelling is subject to change) that looks like: - -```swift -func headerFieldGetTextRequired( // <<< 1. - in headerFields: [HeaderField], - name: String, - as type: Date.Type // <<< 2. -) throws -> Date -``` - -In `1.`, notice that the method name contains which schema location, content type family, and optionality; while in `2.` it contains the Swift type. +In the case of the converter, it contains helper methods for all the supported combinations of an schema location, a "coding strategy" and a Swift type. + +### Dimensions of helper methods + +Below is a list of the "dimensions" across which the helper methods differ: + +- **Client/server** represents whether the code is needed by the client, server, or both ("common"). +- **Set/get** represents whether the generated code sets or gets the value. +- **Schema location** refers to one of the several places where schemas can be used in OpenAPI documents. Values: + - request path parameters + - request query items + - request header fields + - request body + - response header fields + - response body +- **Coding strategy** represents the chosen encoder/decoder to convert the Swift type to/from data. Values: + - `JSON` + - example: `application/json` + - uses the type's `Codable` implementation and `JSONEncoder`/`JSONDecoder` + - `text` + - example: `text/plain` + - uses the type's `LosslessStringConvertible` implementation, except for `Foundation.Date`, which uses a system date formatter + - `binary` + - example: `application/octet-stream` + - doesn't transform the binary data, just passes it through + - serves as the fallback for content types that don't have more specific handling +- **Swift type** represents the generated type in Swift that best represents the JSON schema defined in the OpenAPI document. For example, a `string` schema is generated as `Swift.String`, an `object` schema is generated as a Swift structure, and an `array` schema is generated as a `Swift.Array` generic over the element type. For the helper methods, it's important which protocol they conform to, as those are used for serialization. Values: + - _string-convertible_ refers to types that conform to `LosslessStringConvertible` + - _array of string-convertibles_ refers to an array of types that conform to `LosslessStringConvertible` + - _date_ is represented by `Foundation.Date` + - _array of dates_ refers to an array of `Foundation.Date` + - _codable_ refers to types that conform to `Codable` + - _data_ is represented by `Foundation.Data` +- **Optional/required** represents whether the method works with optional values. Values: + - _required_ represents a special overload only for required values + - _optional_ represents a special overload only for optional values + - _both_ represents a special overload that works for optional values without negatively impacting passed-in required values (for example, setters) ### Helper method variants -In the nested list below, each leaf is one helper method. - -"string-convertible" refers to types that conform to `LosslessStringConvertible` (but not `Foundation.Date`, which is handled separately). - - -#### Required by client code - -- request - - set request path [client-only] - - text - - string-convertible - - optional/required - - date - - optional/required - - set request query [client-only] - - text - - string-convertible - - optional/required - - array of string-convertibles - - optional/required - - date - - optional/required - - array of dates - - optional/required - - set request headers [common] - - text - - string-convertible - - optional/required - - array of string-convertibles - - optional/required - - date - - optional/required - - array of dates - - optional/required - - structured - - codable - - optional/required - - set request body [client-only] - - text - - string-convertible - - optional - - required - - date - - optional - - required - - structured - - codable - - optional - - required - - raw - - data - - optional - - required -- response - - get response headers [common] - - text - - string-convertible - - optional - - required - - array of string-convertibles - - optional - - required - - date - - optional - - required - - array of dates - - optional - - required - - structured - - codable - - optional - - required - - get response body [client-only] - - text - - string-convertible - - required - - date - - required - - structured - - codable - - required - - raw - - data - - required - -#### Required by server code - -- request - - get request path [server-only] - - text - - string-convertible - - optional - - required - - date - - optional - - required - - get request query [server-only] - - text - - string-convertible - - optional - - required - - array of string-convertibles - - optional - - required - - date - - optional - - required - - array of dates - - optional - - required - - get request headers [common] - - text - - string-convertible - - optional - - required - - array of string-convertibles - - optional - - required - - date - - optional - - required - - array of dates - - optional - - required - - structured - - codable - - optional - - required - - get request body [server-only] - - text - - string-convertible - - optional - - required - - date - - optional - - required - - structured - - codable - - optional - - required - - raw - - data - - optional - - required -- response - - set response headers [common] - - text - - string-convertible - - optional/required - - array of string-convertibles - - optional/required - - date - - optional/required - - array of dates - - optional/required - - structured - - codable - - optional/required - - set response body [server-only] - - text - - string-convertible - - required - - date - - required - - structured - - codable - - required - - raw - - data - - required +Together, the dimensions are enough to deterministically decide which helper method on the converter should be used. + +In the list below, each row represents one helper method. + +| Client/server | Set/get | Schema location | Coding strategy | Swift type | Optional/required | Method name | +| --------------| ------- | --------------- | --------------- | ---------- | ------------------| ----------- | +| common | set | header field | text | string-convertible | both | TODO | +| common | set | header field | text | array of string-convertibles | both | TODO | +| common | set | header field | text | date | both | TODO | +| common | set | header field | text | array of dates | both | TODO | +| common | set | header field | JSON | codable | both | TODO | +| common | get | header field | text | string-convertible | optional | TODO | +| common | get | header field | text | string-convertible | required | TODO | +| common | get | header field | text | array of string-convertibles | optional | TODO | +| common | get | header field | text | array of string-convertibles | required | TODO | +| common | get | header field | text | date | optional | TODO | +| common | get | header field | text | date | required | TODO | +| common | get | header field | text | array of dates | optional | TODO | +| common | get | header field | text | array of dates | required | TODO | +| common | get | header field | JSON | codable | optional | TODO | +| common | get | header field | JSON | codable | required | TODO | +| client | set | request path | text | string-convertible | both | TODO | +| client | set | request path | text | date | both | TODO | +| client | set | request query | text | string-convertible | both | TODO | +| client | set | request query | text | array of string-convertibles | both | TODO | +| client | set | request query | text | date | both | TODO | +| client | set | request query | text | array of dates | both | TODO | +| client | set | request query | text | array of dates | both | TODO | +| client | set | request body | text | string-convertible | optional | TODO | +| client | set | request body | text | string-convertible | required | TODO | +| client | set | request body | text | date | optional | TODO | +| client | set | request body | text | date | required | TODO | +| client | set | request body | JSON | codable | optional | TODO | +| client | set | request body | JSON | codable | required | TODO | +| client | set | request body | binary | data | optional | TODO | +| client | set | request body | binary | data | required | TODO | +| client | get | response body | text | string-convertible | required | TODO | +| client | get | response body | text | date | required | TODO | +| client | get | response body | JSON | codable | required | TODO | +| client | get | response body | binary | data | required | TODO | +| server | get | request path | text | string-convertible | optional | TODO | +| server | get | request path | text | string-convertible | required | TODO | +| server | get | request path | text | date | optional | TODO | +| server | get | request path | text | date | required | TODO | +| server | get | request query | text | string-convertible | optional | TODO | +| server | get | request query | text | string-convertible | required | TODO | +| server | get | request query | text | array of string-convertibles | optional | TODO | +| server | get | request query | text | array of string-convertibles | required | TODO | +| server | get | request query | text | date | optional | TODO | +| server | get | request query | text | date | required | TODO | +| server | get | request query | text | array of dates | optional | TODO | +| server | get | request query | text | array of dates | required | TODO | +| server | get | request body | text | string-convertible | optional | TODO | +| server | get | request body | text | string-convertible | required | TODO | +| server | get | request body | text | date | optional | TODO | +| server | get | request body | text | date | required | TODO | +| server | get | request body | JSON | codable | optional | TODO | +| server | get | request body | JSON | codable | required | TODO | +| server | get | request body | binary | data | optional | TODO | +| server | get | request body | binary | data | required | TODO | +| server | set | response body | text | string-convertible | required | TODO | +| server | set | response body | text | date | required | TODO | +| server | set | response body | JSON | codable | required | TODO | +| server | set | response body | binary | data | required | TODO |