From ede7b8058096a2b2327be625b67de5abd0e9e49a Mon Sep 17 00:00:00 2001 From: Adam Basfop Cavendish Date: Mon, 15 Jun 2026 12:29:18 +0800 Subject: [PATCH] fix: extend multipart and media-type handling across clients Clients: - Generate multipart bodies for Go, Java, Kotlin, Python, and TypeScript clients - Select exact request and response media types for JSON, +json, text, and binary payloads - Guard optional multipart bodies and report unsupported encodings explicitly Tests: - Add desensitized multipart edge-case, nested-part, and media-selection fixtures - Add runtime smoke checks for wire construction and serialization choices - Regenerate goldens across affected generator suites --- src/generators/go/http/sigil_emit_api.rs | 561 ++++++++++++++++-- .../java/okhttp/runtime/ApiClient.java.txt | 17 +- src/generators/java/okhttp/sigil_emit_api.rs | 493 +++++++++++++-- .../kotlin/okhttp/runtime/api_client.kt.txt | 22 +- .../kotlin/okhttp/sigil_emit_api.rs | 509 ++++++++++++++-- src/generators/python/httpx/emit_api.rs | 425 ++++++++++++- .../python/httpx/runtime/client.py.txt | 6 + src/generators/python/requests/emit_api.rs | 424 ++++++++++++- .../python/requests/runtime/client.py.txt | 4 + src/generators/rust/common/emit_api.rs | 1 + .../fetch/project_files/runtime.ts.txt | 2 +- src/generators/typescript/fetch/sigil_emit.rs | 2 +- .../typescript/fetch/sigil_emit_api.rs | 447 ++++++++++++-- .../fixtures/valid/media-type-selection.yaml | 115 ++++ .../fixtures/valid/multipart-edge-cases.yaml | 72 +++ .../valid/multipart-nested-object-parts.yaml | 42 ++ .../apis/transfer.go.golden | 39 +- .../media-type-selection/README.md.golden | 7 + .../media-type-selection/apis/media.go.golden | 272 +++++++++ .../media-type-selection/go.mod.golden | 3 + .../models/file_envelope.go.golden | 11 + .../models/payload.go.golden | 11 + .../runtime/auth.go.golden | 113 ++++ .../runtime/client.go.golden | 99 ++++ .../runtime/errors.go.golden | 31 + .../multipart-edge-cases/README.md.golden | 7 + .../apis/multipart.go.golden | 156 +++++ .../multipart-edge-cases/go.mod.golden | 3 + .../models/attributes.go.golden | 11 + .../models/optional_upload.go.golden | 14 + .../models/text_fields.go.golden | 12 + .../runtime/auth.go.golden | 113 ++++ .../runtime/client.go.golden | 99 ++++ .../runtime/errors.go.golden | 31 + .../README.md.golden | 7 + .../apis/multipart.go.golden | 85 +++ .../go.mod.golden | 3 + .../models/item_config.go.golden | 11 + .../models/nested_upload.go.golden | 11 + .../runtime/auth.go.golden | 113 ++++ .../runtime/client.go.golden | 99 ++++ .../runtime/errors.go.golden | 31 + .../README.md.golden | 7 + .../apis/transfer.go.golden | 59 ++ .../go.mod.golden | 3 + .../runtime/auth.go.golden | 113 ++++ .../runtime/client.go.golden | 99 ++++ .../runtime/errors.go.golden | 31 + .../apis/default.go.golden | 48 +- .../apis/AdditionalPropertiesApi.java.golden | 30 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/TransferApi.java.golden | 31 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 1 + .../runtime/ApiClient.java.golden | 17 +- .../apis/TestResourceApi.java.golden | 21 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/ItemsApi.java.golden | 12 +- .../apis/UsersApi.java.golden | 6 +- .../runtime/ApiClient.java.golden | 17 +- .../enum-repr/apis/EnumReprApi.java.golden | 48 +- .../enum-repr/runtime/ApiClient.java.golden | 17 +- .../runtime/ApiClient.java.golden | 17 +- .../media-type-selection/README.md.golden | 7 + .../apis/MediaApi.java.golden | 301 ++++++++++ .../media-type-selection/build.gradle.golden | 21 + .../models/FileEnvelope.java.golden | 24 + .../models/Payload.java.golden | 24 + .../runtime/ApiClient.java.golden | 79 +++ .../runtime/ApiException.java.golden | 31 + .../runtime/ApiKeyAuth.java.golden | 42 ++ .../runtime/ApiKeyLocation.java.golden | 11 + .../runtime/Authenticator.java.golden | 12 + .../runtime/BearerAuth.java.golden | 30 + .../minimal/apis/DefaultApi.java.golden | 1 + .../minimal/runtime/ApiClient.java.golden | 17 +- .../multipart-edge-cases/README.md.golden | 7 + .../apis/MultipartApi.java.golden | 135 +++++ .../multipart-edge-cases/build.gradle.golden | 21 + .../models/Attributes.java.golden | 24 + .../models/OptionalUpload.java.golden | 46 ++ .../models/TextFields.java.golden | 33 ++ .../runtime/ApiClient.java.golden | 79 +++ .../runtime/ApiException.java.golden | 31 + .../runtime/ApiKeyAuth.java.golden | 42 ++ .../runtime/ApiKeyLocation.java.golden | 11 + .../runtime/Authenticator.java.golden | 12 + .../runtime/BearerAuth.java.golden | 30 + .../README.md.golden | 7 + .../apis/MultipartApi.java.golden | 77 +++ .../build.gradle.golden | 21 + .../models/ItemConfig.java.golden | 28 + .../models/NestedUpload.java.golden | 27 + .../runtime/ApiClient.java.golden | 79 +++ .../runtime/ApiException.java.golden | 31 + .../runtime/ApiKeyAuth.java.golden | 42 ++ .../runtime/ApiKeyLocation.java.golden | 11 + .../runtime/Authenticator.java.golden | 12 + .../runtime/BearerAuth.java.golden | 30 + .../README.md.golden | 7 + .../apis/TransferApi.java.golden | 72 +++ .../build.gradle.golden | 21 + .../runtime/ApiClient.java.golden | 79 +++ .../runtime/ApiException.java.golden | 31 + .../runtime/ApiKeyAuth.java.golden | 42 ++ .../runtime/ApiKeyLocation.java.golden | 11 + .../runtime/Authenticator.java.golden | 12 + .../runtime/BearerAuth.java.golden | 30 + .../apis/TestApiApi.java.golden | 21 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 12 +- .../runtime/ApiClient.java.golden | 17 +- .../petstore/apis/PetApi.java.golden | 46 +- .../petstore/apis/StoreApi.java.golden | 22 +- .../petstore/apis/UserApi.java.golden | 30 +- .../petstore/runtime/ApiClient.java.golden | 17 +- .../apis/ItemsApi.java.golden | 6 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 1 + .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 1 + .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 1 + .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 1 + .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 1 + .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 1 + .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 1 + .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 1 + .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 1 + .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 1 + .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 1 + .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 1 + .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 1 + .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 1 + .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 1 + .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 1 + .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 48 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/ItemsApi.java.golden | 6 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/WidgetApi.java.golden | 8 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 6 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/WidgetApi.java.golden | 10 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/FooApi.java.golden | 14 +- .../runtime/ApiClient.java.golden | 17 +- .../server-object/apis/DefaultApi.java.golden | 13 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 1 + .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 12 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 12 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 12 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 12 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 12 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 21 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 12 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 12 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 17 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 12 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 12 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 12 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 12 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 12 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 12 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/DefaultApi.java.golden | 12 +- .../runtime/ApiClient.java.golden | 17 +- .../apis/AdditionalPropertiesApi.kt.golden | 26 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/TransferApi.kt.golden | 23 +- .../runtime/ApiClient.kt.golden | 22 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/TestResourceApi.kt.golden | 19 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/ItemsApi.kt.golden | 10 +- .../apis/UsersApi.kt.golden | 5 +- .../runtime/ApiClient.kt.golden | 22 +- .../enum-repr/apis/EnumReprApi.kt.golden | 42 +- .../enum-repr/runtime/ApiClient.kt.golden | 22 +- .../runtime/ApiClient.kt.golden | 22 +- .../media-type-selection/README.md.golden | 7 + .../apis/MediaApi.kt.golden | 174 ++++++ .../build.gradle.kts.golden | 19 + .../models/FileEnvelope.kt.golden | 9 + .../models/Payload.kt.golden | 9 + .../runtime/ApiClient.kt.golden | 62 ++ .../runtime/ApiException.kt.golden | 12 + .../runtime/Auth.kt.golden | 71 +++ .../minimal/runtime/ApiClient.kt.golden | 22 +- .../multipart-edge-cases/README.md.golden | 7 + .../apis/MultipartApi.kt.golden | 90 +++ .../build.gradle.kts.golden | 19 + .../models/Attributes.kt.golden | 9 + .../models/OptionalUpload.kt.golden | 14 + .../models/TextFields.kt.golden | 12 + .../runtime/ApiClient.kt.golden | 62 ++ .../runtime/ApiException.kt.golden | 12 + .../runtime/Auth.kt.golden | 71 +++ .../README.md.golden | 7 + .../apis/MultipartApi.kt.golden | 48 ++ .../build.gradle.kts.golden | 19 + .../models/ItemConfig.kt.golden | 12 + .../models/NestedUpload.kt.golden | 12 + .../runtime/ApiClient.kt.golden | 62 ++ .../runtime/ApiException.kt.golden | 12 + .../runtime/Auth.kt.golden | 71 +++ .../README.md.golden | 7 + .../apis/TransferApi.kt.golden | 43 ++ .../build.gradle.kts.golden | 19 + .../runtime/ApiClient.kt.golden | 62 ++ .../runtime/ApiException.kt.golden | 12 + .../runtime/Auth.kt.golden | 71 +++ .../apis/TestApiApi.kt.golden | 18 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/DefaultApi.kt.golden | 10 +- .../runtime/ApiClient.kt.golden | 22 +- .../petstore/apis/PetApi.kt.golden | 43 +- .../petstore/apis/StoreApi.kt.golden | 20 +- .../petstore/apis/UserApi.kt.golden | 26 +- .../petstore/runtime/ApiClient.kt.golden | 22 +- .../query-param-enum/apis/ItemsApi.kt.golden | 5 +- .../runtime/ApiClient.kt.golden | 22 +- .../runtime/ApiClient.kt.golden | 22 +- .../runtime/ApiClient.kt.golden | 22 +- .../runtime/ApiClient.kt.golden | 22 +- .../runtime/ApiClient.kt.golden | 22 +- .../runtime/ApiClient.kt.golden | 22 +- .../runtime/ApiClient.kt.golden | 22 +- .../runtime/ApiClient.kt.golden | 22 +- .../runtime/ApiClient.kt.golden | 22 +- .../runtime/ApiClient.kt.golden | 22 +- .../runtime/ApiClient.kt.golden | 22 +- .../runtime/ApiClient.kt.golden | 22 +- .../runtime/ApiClient.kt.golden | 22 +- .../runtime/ApiClient.kt.golden | 22 +- .../runtime/ApiClient.kt.golden | 22 +- .../runtime/ApiClient.kt.golden | 22 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/DefaultApi.kt.golden | 34 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/ItemsApi.kt.golden | 5 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/WidgetApi.kt.golden | 7 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/DefaultApi.kt.golden | 5 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/WidgetApi.kt.golden | 9 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/FooApi.kt.golden | 12 +- .../runtime/ApiClient.kt.golden | 22 +- .../server-object/apis/DefaultApi.kt.golden | 12 +- .../server-object/runtime/ApiClient.kt.golden | 22 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/DefaultApi.kt.golden | 10 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/DefaultApi.kt.golden | 10 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/DefaultApi.kt.golden | 10 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/DefaultApi.kt.golden | 10 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/DefaultApi.kt.golden | 10 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/DefaultApi.kt.golden | 18 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/DefaultApi.kt.golden | 10 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/DefaultApi.kt.golden | 10 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/DefaultApi.kt.golden | 15 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/DefaultApi.kt.golden | 10 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/DefaultApi.kt.golden | 10 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/DefaultApi.kt.golden | 10 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/DefaultApi.kt.golden | 10 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/DefaultApi.kt.golden | 10 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/DefaultApi.kt.golden | 10 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/DefaultApi.kt.golden | 10 +- .../runtime/ApiClient.kt.golden | 22 +- .../apis/additional_properties_api.py.golden | 12 +- .../runtime/client.py.golden | 6 + .../apis/transfer_api.py.golden | 12 +- .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../apis/test_resource_api.py.golden | 4 +- .../runtime/client.py.golden | 6 + .../apis/items_api.py.golden | 4 +- .../runtime/client.py.golden | 6 + .../apis/enum_repr_api.py.golden | 20 +- .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../media-type-selection/README.md.golden | 7 + .../media_type_selection/__init__.py.golden | 10 + .../apis/__init__.py.golden | 6 + .../apis/media_api.py.golden | 66 +++ .../models/__init__.py.golden | 7 + .../models/file_envelope.py.golden | 27 + .../models/payload.py.golden | 27 + .../media_type_selection/py.typed.golden | 0 .../runtime/__init__.py.golden | 10 + .../runtime/auth.py.golden | 51 ++ .../runtime/client.py.golden | 71 +++ .../runtime/errors.py.golden | 18 + .../pyproject.toml.golden | 10 + .../minimal_api/runtime/client.py.golden | 6 + .../multipart-edge-cases/README.md.golden | 7 + .../multipart_edge_cases/__init__.py.golden | 10 + .../apis/__init__.py.golden | 6 + .../apis/multipart_api.py.golden | 53 ++ .../models/__init__.py.golden | 8 + .../models/attributes.py.golden | 27 + .../models/optional_upload.py.golden | 42 ++ .../models/text_fields.py.golden | 29 + .../multipart_edge_cases/py.typed.golden | 0 .../runtime/__init__.py.golden | 10 + .../runtime/auth.py.golden | 51 ++ .../runtime/client.py.golden | 71 +++ .../runtime/errors.py.golden | 18 + .../pyproject.toml.golden | 10 + .../README.md.golden | 7 + .../__init__.py.golden | 10 + .../apis/__init__.py.golden | 6 + .../apis/multipart_api.py.golden | 26 + .../models/__init__.py.golden | 7 + .../models/item_config.py.golden | 27 + .../models/nested_upload.py.golden | 28 + .../py.typed.golden | 0 .../runtime/__init__.py.golden | 10 + .../runtime/auth.py.golden | 51 ++ .../runtime/client.py.golden | 71 +++ .../runtime/errors.py.golden | 18 + .../pyproject.toml.golden | 10 + .../README.md.golden | 7 + .../__init__.py.golden | 10 + .../apis/__init__.py.golden | 6 + .../apis/transfer_api.py.golden | 21 + .../models/__init__.py.golden | 5 + .../py.typed.golden | 0 .../runtime/__init__.py.golden | 10 + .../runtime/auth.py.golden | 51 ++ .../runtime/client.py.golden | 71 +++ .../runtime/errors.py.golden | 18 + .../pyproject.toml.golden | 10 + .../apis/test_api_api.py.golden | 8 +- .../runtime/client.py.golden | 6 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 6 + .../petstore_api/apis/pet_api.py.golden | 8 +- .../petstore_api/apis/store_api.py.golden | 4 +- .../petstore_api/apis/user_api.py.golden | 12 +- .../petstore_api/runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../empty_array_test/runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../apis/default_api.py.golden | 33 +- .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../multi_status_api/runtime/client.py.golden | 6 + .../apis/foo_api.py.golden | 4 +- .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../runtime/client.py.golden | 6 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 6 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 6 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 6 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 6 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 6 + .../apis/default_api.py.golden | 8 +- .../runtime/client.py.golden | 6 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 6 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 6 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 6 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 6 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 6 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 6 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 6 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 6 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 6 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 6 + .../apis/additional_properties_api.py.golden | 12 +- .../runtime/client.py.golden | 4 + .../apis/transfer_api.py.golden | 12 +- .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../apis/test_resource_api.py.golden | 4 +- .../runtime/client.py.golden | 4 + .../apis/items_api.py.golden | 4 +- .../runtime/client.py.golden | 4 + .../apis/enum_repr_api.py.golden | 20 +- .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../media-type-selection/README.md.golden | 7 + .../media_type_selection/__init__.py.golden | 10 + .../apis/__init__.py.golden | 6 + .../apis/media_api.py.golden | 66 +++ .../models/__init__.py.golden | 7 + .../models/file_envelope.py.golden | 27 + .../models/payload.py.golden | 27 + .../media_type_selection/py.typed.golden | 0 .../runtime/__init__.py.golden | 10 + .../runtime/auth.py.golden | 51 ++ .../runtime/client.py.golden | 99 ++++ .../runtime/errors.py.golden | 18 + .../pyproject.toml.golden | 10 + .../minimal_api/runtime/client.py.golden | 4 + .../multipart-edge-cases/README.md.golden | 7 + .../multipart_edge_cases/__init__.py.golden | 10 + .../apis/__init__.py.golden | 6 + .../apis/multipart_api.py.golden | 53 ++ .../models/__init__.py.golden | 8 + .../models/attributes.py.golden | 27 + .../models/optional_upload.py.golden | 42 ++ .../models/text_fields.py.golden | 29 + .../multipart_edge_cases/py.typed.golden | 0 .../runtime/__init__.py.golden | 10 + .../runtime/auth.py.golden | 51 ++ .../runtime/client.py.golden | 99 ++++ .../runtime/errors.py.golden | 18 + .../pyproject.toml.golden | 10 + .../README.md.golden | 7 + .../__init__.py.golden | 10 + .../apis/__init__.py.golden | 6 + .../apis/multipart_api.py.golden | 26 + .../models/__init__.py.golden | 7 + .../models/item_config.py.golden | 27 + .../models/nested_upload.py.golden | 28 + .../py.typed.golden | 0 .../runtime/__init__.py.golden | 10 + .../runtime/auth.py.golden | 51 ++ .../runtime/client.py.golden | 99 ++++ .../runtime/errors.py.golden | 18 + .../pyproject.toml.golden | 10 + .../README.md.golden | 7 + .../__init__.py.golden | 10 + .../apis/__init__.py.golden | 6 + .../apis/transfer_api.py.golden | 21 + .../models/__init__.py.golden | 5 + .../py.typed.golden | 0 .../runtime/__init__.py.golden | 10 + .../runtime/auth.py.golden | 51 ++ .../runtime/client.py.golden | 99 ++++ .../runtime/errors.py.golden | 18 + .../pyproject.toml.golden | 10 + .../apis/test_api_api.py.golden | 8 +- .../runtime/client.py.golden | 4 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 4 + .../petstore_api/apis/pet_api.py.golden | 8 +- .../petstore_api/apis/store_api.py.golden | 4 +- .../petstore_api/apis/user_api.py.golden | 12 +- .../petstore_api/runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../empty_array_test/runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../apis/default_api.py.golden | 33 +- .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../multi_status_api/runtime/client.py.golden | 4 + .../apis/foo_api.py.golden | 4 +- .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../runtime/client.py.golden | 4 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 4 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 4 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 4 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 4 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 4 + .../apis/default_api.py.golden | 8 +- .../runtime/client.py.golden | 4 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 4 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 4 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 4 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 4 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 4 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 4 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 4 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 4 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 4 + .../apis/default_api.py.golden | 4 +- .../runtime/client.py.golden | 4 + .../media-type-selection/Cargo.toml.golden | 12 + .../media-type-selection/README.md.golden | 7 + .../src/apis/media.rs.golden | 162 +++++ .../src/apis/mod.rs.golden | 8 + .../media-type-selection/src/lib.rs.golden | 11 + .../src/models/file_envelope.rs.golden | 14 + .../src/models/mod.rs.golden | 10 + .../src/models/payload.rs.golden | 14 + .../src/runtime/auth.rs.golden | 180 ++++++ .../src/runtime/client.rs.golden | 112 ++++ .../src/runtime/error.rs.golden | 60 ++ .../src/runtime/mod.rs.golden | 9 + .../multipart-edge-cases/Cargo.toml.golden | 12 + .../multipart-edge-cases/README.md.golden | 7 + .../src/apis/mod.rs.golden | 8 + .../src/apis/multipart.rs.golden | 80 +++ .../multipart-edge-cases/src/lib.rs.golden | 11 + .../src/models/attributes.rs.golden | 14 + .../src/models/mod.rs.golden | 12 + .../src/models/optional_upload.rs.golden | 21 + .../src/models/text_fields.rs.golden | 14 + .../src/runtime/auth.rs.golden | 180 ++++++ .../src/runtime/client.rs.golden | 112 ++++ .../src/runtime/error.rs.golden | 60 ++ .../src/runtime/mod.rs.golden | 9 + .../Cargo.toml.golden | 12 + .../README.md.golden | 7 + .../src/apis/mod.rs.golden | 8 + .../src/apis/multipart.rs.golden | 44 ++ .../src/lib.rs.golden | 11 + .../src/models/item_config.rs.golden | 14 + .../src/models/mod.rs.golden | 10 + .../src/models/nested_upload.rs.golden | 13 + .../src/runtime/auth.rs.golden | 180 ++++++ .../src/runtime/client.rs.golden | 112 ++++ .../src/runtime/error.rs.golden | 60 ++ .../src/runtime/mod.rs.golden | 9 + .../media-type-selection/Cargo.toml.golden | 12 + .../media-type-selection/README.md.golden | 7 + .../src/apis/media.rs.golden | 162 +++++ .../src/apis/mod.rs.golden | 8 + .../media-type-selection/src/lib.rs.golden | 11 + .../src/models/file_envelope.rs.golden | 14 + .../src/models/mod.rs.golden | 10 + .../src/models/payload.rs.golden | 14 + .../src/runtime/auth.rs.golden | 189 ++++++ .../src/runtime/client.rs.golden | 73 +++ .../src/runtime/error.rs.golden | 65 ++ .../src/runtime/mod.rs.golden | 9 + .../multipart-edge-cases/Cargo.toml.golden | 12 + .../multipart-edge-cases/README.md.golden | 7 + .../src/apis/mod.rs.golden | 8 + .../src/apis/multipart.rs.golden | 80 +++ .../multipart-edge-cases/src/lib.rs.golden | 11 + .../src/models/attributes.rs.golden | 14 + .../src/models/mod.rs.golden | 12 + .../src/models/optional_upload.rs.golden | 21 + .../src/models/text_fields.rs.golden | 14 + .../src/runtime/auth.rs.golden | 189 ++++++ .../src/runtime/client.rs.golden | 73 +++ .../src/runtime/error.rs.golden | 65 ++ .../src/runtime/mod.rs.golden | 9 + .../Cargo.toml.golden | 12 + .../README.md.golden | 7 + .../src/apis/mod.rs.golden | 8 + .../src/apis/multipart.rs.golden | 44 ++ .../src/lib.rs.golden | 11 + .../src/models/item_config.rs.golden | 14 + .../src/models/mod.rs.golden | 10 + .../src/models/nested_upload.rs.golden | 13 + .../src/runtime/auth.rs.golden | 189 ++++++ .../src/runtime/client.rs.golden | 73 +++ .../src/runtime/error.rs.golden | 65 ++ .../src/runtime/mod.rs.golden | 9 + .../media-type-selection/Cargo.toml.golden | 12 + .../media-type-selection/README.md.golden | 7 + .../src/apis/media.rs.golden | 168 ++++++ .../src/apis/mod.rs.golden | 8 + .../media-type-selection/src/lib.rs.golden | 11 + .../src/models/file_envelope.rs.golden | 14 + .../src/models/mod.rs.golden | 10 + .../src/models/payload.rs.golden | 14 + .../src/runtime/auth.rs.golden | 169 ++++++ .../src/runtime/client.rs.golden | 94 +++ .../src/runtime/error.rs.golden | 64 ++ .../src/runtime/mod.rs.golden | 9 + .../multipart-edge-cases/Cargo.toml.golden | 12 + .../multipart-edge-cases/README.md.golden | 7 + .../src/apis/mod.rs.golden | 8 + .../src/apis/multipart.rs.golden | 108 ++++ .../multipart-edge-cases/src/lib.rs.golden | 11 + .../src/models/attributes.rs.golden | 14 + .../src/models/mod.rs.golden | 12 + .../src/models/optional_upload.rs.golden | 21 + .../src/models/text_fields.rs.golden | 14 + .../src/runtime/auth.rs.golden | 169 ++++++ .../src/runtime/client.rs.golden | 94 +++ .../src/runtime/error.rs.golden | 64 ++ .../src/runtime/mod.rs.golden | 9 + .../Cargo.toml.golden | 12 + .../README.md.golden | 7 + .../src/apis/mod.rs.golden | 8 + .../src/apis/multipart.rs.golden | 52 ++ .../src/lib.rs.golden | 11 + .../src/models/item_config.rs.golden | 14 + .../src/models/mod.rs.golden | 10 + .../src/models/nested_upload.rs.golden | 13 + .../src/runtime/auth.rs.golden | 169 ++++++ .../src/runtime/client.rs.golden | 94 +++ .../src/runtime/error.rs.golden | 64 ++ .../src/runtime/mod.rs.golden | 9 + .../runtime/runtime.ts.golden | 2 +- .../apis/TransferApi.ts.golden | 6 +- .../models/UploadAssetRequest.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../models/BinaryString.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../enum-repr/runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../media-type-selection/README.md.golden | 248 ++++++++ .../apis/MediaApi.ts.golden | 299 ++++++++++ .../media-type-selection/apis/index.ts.golden | 19 + .../media-type-selection/index.ts.golden | 9 + .../models/FileEnvelope.ts.golden | 10 + .../models/Payload.ts.golden | 10 + .../models/index.ts.golden | 8 + .../media-type-selection/package.json.golden | 26 + .../runtime/runtime.ts.golden | 461 ++++++++++++++ .../tsconfig.esm.json.golden | 7 + .../media-type-selection/tsconfig.json.golden | 31 + .../minimal/runtime/runtime.ts.golden | 2 +- .../multipart-edge-cases/README.md.golden | 248 ++++++++ .../apis/MultipartApi.ts.golden | 142 +++++ .../multipart-edge-cases/apis/index.ts.golden | 14 + .../multipart-edge-cases/index.ts.golden | 9 + .../models/Attributes.ts.golden | 10 + .../models/OptionalUpload.ts.golden | 15 + .../models/TextFields.ts.golden | 11 + .../models/index.ts.golden | 9 + .../multipart-edge-cases/package.json.golden | 26 + .../runtime/runtime.ts.golden | 461 ++++++++++++++ .../tsconfig.esm.json.golden | 7 + .../multipart-edge-cases/tsconfig.json.golden | 31 + .../README.md.golden | 248 ++++++++ .../apis/MultipartApi.ts.golden | 76 +++ .../apis/index.ts.golden | 12 + .../index.ts.golden | 9 + .../models/ItemConfig.ts.golden | 10 + .../models/NestedUpload.ts.golden | 12 + .../models/index.ts.golden | 8 + .../package.json.golden | 26 + .../runtime/runtime.ts.golden | 461 ++++++++++++++ .../tsconfig.esm.json.golden | 7 + .../tsconfig.json.golden | 31 + .../README.md.golden | 248 ++++++++ .../apis/TransferApi.ts.golden | 73 +++ .../apis/index.ts.golden | 12 + .../index.ts.golden | 8 + .../package.json.golden | 26 + .../runtime/runtime.ts.golden | 461 ++++++++++++++ .../tsconfig.esm.json.golden | 7 + .../tsconfig.json.golden | 31 + .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../petstore/runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../apis/DefaultApi.ts.golden | 6 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../server-object/runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../ts-enum-const/runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../README.md.golden | 248 ++++++++ .../apis/MultipartApi.ts.golden | 77 +++ .../apis/index.ts.golden | 12 + .../index.ts.golden | 9 + .../models/ItemConfig.ts.golden | 29 + .../models/NestedUpload.ts.golden | 32 + .../models/index.ts.golden | 10 + .../package.json.golden | 26 + .../runtime/runtime.ts.golden | 461 ++++++++++++++ .../tsconfig.esm.json.golden | 7 + .../tsconfig.json.golden | 31 + .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../models/BinaryString.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../ts-toolchain-vp/runtime/runtime.ts.golden | 2 +- .../ts-type-guards/runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- .../runtime/runtime.ts.golden | 2 +- tests/golden_tests_go_http.rs | 8 + tests/golden_tests_java_okhttp.rs | 8 + tests/golden_tests_kotlin_okhttp.rs | 8 + tests/golden_tests_python_httpx.rs | 8 + tests/golden_tests_python_requests.rs | 8 + tests/golden_tests_rust_aioduct.rs | 6 + tests/golden_tests_rust_reqwest.rs | 6 + tests/golden_tests_rust_ureq.rs | 6 + tests/golden_tests_typescript_fetch.rs | 27 + tests/multipart_runtime_smoke.rs | 256 ++++++++ 822 files changed, 22955 insertions(+), 1258 deletions(-) create mode 100644 tests/fixtures/valid/media-type-selection.yaml create mode 100644 tests/fixtures/valid/multipart-edge-cases.yaml create mode 100644 tests/fixtures/valid/multipart-nested-object-parts.yaml create mode 100644 tests/golden/go/go-http/media-type-selection/README.md.golden create mode 100644 tests/golden/go/go-http/media-type-selection/apis/media.go.golden create mode 100644 tests/golden/go/go-http/media-type-selection/go.mod.golden create mode 100644 tests/golden/go/go-http/media-type-selection/models/file_envelope.go.golden create mode 100644 tests/golden/go/go-http/media-type-selection/models/payload.go.golden create mode 100644 tests/golden/go/go-http/media-type-selection/runtime/auth.go.golden create mode 100644 tests/golden/go/go-http/media-type-selection/runtime/client.go.golden create mode 100644 tests/golden/go/go-http/media-type-selection/runtime/errors.go.golden create mode 100644 tests/golden/go/go-http/multipart-edge-cases/README.md.golden create mode 100644 tests/golden/go/go-http/multipart-edge-cases/apis/multipart.go.golden create mode 100644 tests/golden/go/go-http/multipart-edge-cases/go.mod.golden create mode 100644 tests/golden/go/go-http/multipart-edge-cases/models/attributes.go.golden create mode 100644 tests/golden/go/go-http/multipart-edge-cases/models/optional_upload.go.golden create mode 100644 tests/golden/go/go-http/multipart-edge-cases/models/text_fields.go.golden create mode 100644 tests/golden/go/go-http/multipart-edge-cases/runtime/auth.go.golden create mode 100644 tests/golden/go/go-http/multipart-edge-cases/runtime/client.go.golden create mode 100644 tests/golden/go/go-http/multipart-edge-cases/runtime/errors.go.golden create mode 100644 tests/golden/go/go-http/multipart-nested-object-parts/README.md.golden create mode 100644 tests/golden/go/go-http/multipart-nested-object-parts/apis/multipart.go.golden create mode 100644 tests/golden/go/go-http/multipart-nested-object-parts/go.mod.golden create mode 100644 tests/golden/go/go-http/multipart-nested-object-parts/models/item_config.go.golden create mode 100644 tests/golden/go/go-http/multipart-nested-object-parts/models/nested_upload.go.golden create mode 100644 tests/golden/go/go-http/multipart-nested-object-parts/runtime/auth.go.golden create mode 100644 tests/golden/go/go-http/multipart-nested-object-parts/runtime/client.go.golden create mode 100644 tests/golden/go/go-http/multipart-nested-object-parts/runtime/errors.go.golden create mode 100644 tests/golden/go/go-http/multipart-unsupported-schema/README.md.golden create mode 100644 tests/golden/go/go-http/multipart-unsupported-schema/apis/transfer.go.golden create mode 100644 tests/golden/go/go-http/multipart-unsupported-schema/go.mod.golden create mode 100644 tests/golden/go/go-http/multipart-unsupported-schema/runtime/auth.go.golden create mode 100644 tests/golden/go/go-http/multipart-unsupported-schema/runtime/client.go.golden create mode 100644 tests/golden/go/go-http/multipart-unsupported-schema/runtime/errors.go.golden create mode 100644 tests/golden/java/java-okhttp/media-type-selection/README.md.golden create mode 100644 tests/golden/java/java-okhttp/media-type-selection/apis/MediaApi.java.golden create mode 100644 tests/golden/java/java-okhttp/media-type-selection/build.gradle.golden create mode 100644 tests/golden/java/java-okhttp/media-type-selection/models/FileEnvelope.java.golden create mode 100644 tests/golden/java/java-okhttp/media-type-selection/models/Payload.java.golden create mode 100644 tests/golden/java/java-okhttp/media-type-selection/runtime/ApiClient.java.golden create mode 100644 tests/golden/java/java-okhttp/media-type-selection/runtime/ApiException.java.golden create mode 100644 tests/golden/java/java-okhttp/media-type-selection/runtime/ApiKeyAuth.java.golden create mode 100644 tests/golden/java/java-okhttp/media-type-selection/runtime/ApiKeyLocation.java.golden create mode 100644 tests/golden/java/java-okhttp/media-type-selection/runtime/Authenticator.java.golden create mode 100644 tests/golden/java/java-okhttp/media-type-selection/runtime/BearerAuth.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-edge-cases/README.md.golden create mode 100644 tests/golden/java/java-okhttp/multipart-edge-cases/apis/MultipartApi.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-edge-cases/build.gradle.golden create mode 100644 tests/golden/java/java-okhttp/multipart-edge-cases/models/Attributes.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-edge-cases/models/OptionalUpload.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-edge-cases/models/TextFields.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-edge-cases/runtime/ApiClient.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-edge-cases/runtime/ApiException.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-edge-cases/runtime/ApiKeyAuth.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-edge-cases/runtime/ApiKeyLocation.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-edge-cases/runtime/Authenticator.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-edge-cases/runtime/BearerAuth.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-nested-object-parts/README.md.golden create mode 100644 tests/golden/java/java-okhttp/multipart-nested-object-parts/apis/MultipartApi.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-nested-object-parts/build.gradle.golden create mode 100644 tests/golden/java/java-okhttp/multipart-nested-object-parts/models/ItemConfig.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-nested-object-parts/models/NestedUpload.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/ApiClient.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/ApiException.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/ApiKeyAuth.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/ApiKeyLocation.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/Authenticator.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/BearerAuth.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-unsupported-schema/README.md.golden create mode 100644 tests/golden/java/java-okhttp/multipart-unsupported-schema/apis/TransferApi.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-unsupported-schema/build.gradle.golden create mode 100644 tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/ApiClient.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/ApiException.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/ApiKeyAuth.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/ApiKeyLocation.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/Authenticator.java.golden create mode 100644 tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/BearerAuth.java.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/media-type-selection/README.md.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/media-type-selection/apis/MediaApi.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/media-type-selection/build.gradle.kts.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/media-type-selection/models/FileEnvelope.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/media-type-selection/models/Payload.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/media-type-selection/runtime/ApiClient.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/media-type-selection/runtime/ApiException.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/media-type-selection/runtime/Auth.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/README.md.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/apis/MultipartApi.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/build.gradle.kts.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/models/Attributes.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/models/OptionalUpload.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/models/TextFields.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/runtime/ApiClient.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/runtime/ApiException.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/runtime/Auth.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/README.md.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/apis/MultipartApi.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/build.gradle.kts.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/models/ItemConfig.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/models/NestedUpload.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/runtime/ApiClient.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/runtime/ApiException.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/runtime/Auth.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/README.md.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/apis/TransferApi.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/build.gradle.kts.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/runtime/ApiClient.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/runtime/ApiException.kt.golden create mode 100644 tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/runtime/Auth.kt.golden create mode 100644 tests/golden/python/python-httpx/media-type-selection/README.md.golden create mode 100644 tests/golden/python/python-httpx/media-type-selection/media_type_selection/__init__.py.golden create mode 100644 tests/golden/python/python-httpx/media-type-selection/media_type_selection/apis/__init__.py.golden create mode 100644 tests/golden/python/python-httpx/media-type-selection/media_type_selection/apis/media_api.py.golden create mode 100644 tests/golden/python/python-httpx/media-type-selection/media_type_selection/models/__init__.py.golden create mode 100644 tests/golden/python/python-httpx/media-type-selection/media_type_selection/models/file_envelope.py.golden create mode 100644 tests/golden/python/python-httpx/media-type-selection/media_type_selection/models/payload.py.golden create mode 100644 tests/golden/python/python-httpx/media-type-selection/media_type_selection/py.typed.golden create mode 100644 tests/golden/python/python-httpx/media-type-selection/media_type_selection/runtime/__init__.py.golden create mode 100644 tests/golden/python/python-httpx/media-type-selection/media_type_selection/runtime/auth.py.golden create mode 100644 tests/golden/python/python-httpx/media-type-selection/media_type_selection/runtime/client.py.golden create mode 100644 tests/golden/python/python-httpx/media-type-selection/media_type_selection/runtime/errors.py.golden create mode 100644 tests/golden/python/python-httpx/media-type-selection/pyproject.toml.golden create mode 100644 tests/golden/python/python-httpx/multipart-edge-cases/README.md.golden create mode 100644 tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/__init__.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/apis/__init__.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/apis/multipart_api.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/models/__init__.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/models/attributes.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/models/optional_upload.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/models/text_fields.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/py.typed.golden create mode 100644 tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/runtime/__init__.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/runtime/auth.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/runtime/client.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/runtime/errors.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-edge-cases/pyproject.toml.golden create mode 100644 tests/golden/python/python-httpx/multipart-nested-object-parts/README.md.golden create mode 100644 tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/__init__.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/apis/__init__.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/apis/multipart_api.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/models/__init__.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/models/item_config.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/models/nested_upload.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/py.typed.golden create mode 100644 tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/runtime/__init__.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/runtime/auth.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/runtime/client.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/runtime/errors.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-nested-object-parts/pyproject.toml.golden create mode 100644 tests/golden/python/python-httpx/multipart-unsupported-schema/README.md.golden create mode 100644 tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/__init__.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/apis/__init__.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/apis/transfer_api.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/models/__init__.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/py.typed.golden create mode 100644 tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/runtime/__init__.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/runtime/auth.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/runtime/client.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/runtime/errors.py.golden create mode 100644 tests/golden/python/python-httpx/multipart-unsupported-schema/pyproject.toml.golden create mode 100644 tests/golden/python/python-requests/media-type-selection/README.md.golden create mode 100644 tests/golden/python/python-requests/media-type-selection/media_type_selection/__init__.py.golden create mode 100644 tests/golden/python/python-requests/media-type-selection/media_type_selection/apis/__init__.py.golden create mode 100644 tests/golden/python/python-requests/media-type-selection/media_type_selection/apis/media_api.py.golden create mode 100644 tests/golden/python/python-requests/media-type-selection/media_type_selection/models/__init__.py.golden create mode 100644 tests/golden/python/python-requests/media-type-selection/media_type_selection/models/file_envelope.py.golden create mode 100644 tests/golden/python/python-requests/media-type-selection/media_type_selection/models/payload.py.golden create mode 100644 tests/golden/python/python-requests/media-type-selection/media_type_selection/py.typed.golden create mode 100644 tests/golden/python/python-requests/media-type-selection/media_type_selection/runtime/__init__.py.golden create mode 100644 tests/golden/python/python-requests/media-type-selection/media_type_selection/runtime/auth.py.golden create mode 100644 tests/golden/python/python-requests/media-type-selection/media_type_selection/runtime/client.py.golden create mode 100644 tests/golden/python/python-requests/media-type-selection/media_type_selection/runtime/errors.py.golden create mode 100644 tests/golden/python/python-requests/media-type-selection/pyproject.toml.golden create mode 100644 tests/golden/python/python-requests/multipart-edge-cases/README.md.golden create mode 100644 tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/__init__.py.golden create mode 100644 tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/apis/__init__.py.golden create mode 100644 tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/apis/multipart_api.py.golden create mode 100644 tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/models/__init__.py.golden create mode 100644 tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/models/attributes.py.golden create mode 100644 tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/models/optional_upload.py.golden create mode 100644 tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/models/text_fields.py.golden create mode 100644 tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/py.typed.golden create mode 100644 tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/runtime/__init__.py.golden create mode 100644 tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/runtime/auth.py.golden create mode 100644 tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/runtime/client.py.golden create mode 100644 tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/runtime/errors.py.golden create mode 100644 tests/golden/python/python-requests/multipart-edge-cases/pyproject.toml.golden create mode 100644 tests/golden/python/python-requests/multipart-nested-object-parts/README.md.golden create mode 100644 tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/__init__.py.golden create mode 100644 tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/apis/__init__.py.golden create mode 100644 tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/apis/multipart_api.py.golden create mode 100644 tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/models/__init__.py.golden create mode 100644 tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/models/item_config.py.golden create mode 100644 tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/models/nested_upload.py.golden create mode 100644 tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/py.typed.golden create mode 100644 tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/runtime/__init__.py.golden create mode 100644 tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/runtime/auth.py.golden create mode 100644 tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/runtime/client.py.golden create mode 100644 tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/runtime/errors.py.golden create mode 100644 tests/golden/python/python-requests/multipart-nested-object-parts/pyproject.toml.golden create mode 100644 tests/golden/python/python-requests/multipart-unsupported-schema/README.md.golden create mode 100644 tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/__init__.py.golden create mode 100644 tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/apis/__init__.py.golden create mode 100644 tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/apis/transfer_api.py.golden create mode 100644 tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/models/__init__.py.golden create mode 100644 tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/py.typed.golden create mode 100644 tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/runtime/__init__.py.golden create mode 100644 tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/runtime/auth.py.golden create mode 100644 tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/runtime/client.py.golden create mode 100644 tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/runtime/errors.py.golden create mode 100644 tests/golden/python/python-requests/multipart-unsupported-schema/pyproject.toml.golden create mode 100644 tests/golden/rust/rust-aioduct/media-type-selection/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-aioduct/media-type-selection/README.md.golden create mode 100644 tests/golden/rust/rust-aioduct/media-type-selection/src/apis/media.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/media-type-selection/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/media-type-selection/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/media-type-selection/src/models/file_envelope.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/media-type-selection/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/media-type-selection/src/models/payload.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/media-type-selection/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/media-type-selection/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/media-type-selection/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/media-type-selection/src/runtime/mod.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-edge-cases/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-edge-cases/README.md.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-edge-cases/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-edge-cases/src/apis/multipart.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-edge-cases/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-edge-cases/src/models/attributes.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-edge-cases/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-edge-cases/src/models/optional_upload.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-edge-cases/src/models/text_fields.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-edge-cases/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-edge-cases/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-edge-cases/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-edge-cases/src/runtime/mod.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-nested-object-parts/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-nested-object-parts/README.md.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/apis/multipart.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/models/item_config.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/models/nested_upload.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/runtime/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/media-type-selection/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-reqwest/media-type-selection/README.md.golden create mode 100644 tests/golden/rust/rust-reqwest/media-type-selection/src/apis/media.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/media-type-selection/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/media-type-selection/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/media-type-selection/src/models/file_envelope.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/media-type-selection/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/media-type-selection/src/models/payload.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/media-type-selection/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/media-type-selection/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/media-type-selection/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/media-type-selection/src/runtime/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-edge-cases/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-edge-cases/README.md.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-edge-cases/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-edge-cases/src/apis/multipart.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-edge-cases/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-edge-cases/src/models/attributes.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-edge-cases/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-edge-cases/src/models/optional_upload.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-edge-cases/src/models/text_fields.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-edge-cases/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-edge-cases/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-edge-cases/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-edge-cases/src/runtime/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-nested-object-parts/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-nested-object-parts/README.md.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/apis/multipart.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/models/item_config.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/models/nested_upload.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/runtime/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/media-type-selection/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-ureq/media-type-selection/README.md.golden create mode 100644 tests/golden/rust/rust-ureq/media-type-selection/src/apis/media.rs.golden create mode 100644 tests/golden/rust/rust-ureq/media-type-selection/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/media-type-selection/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-ureq/media-type-selection/src/models/file_envelope.rs.golden create mode 100644 tests/golden/rust/rust-ureq/media-type-selection/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/media-type-selection/src/models/payload.rs.golden create mode 100644 tests/golden/rust/rust-ureq/media-type-selection/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-ureq/media-type-selection/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-ureq/media-type-selection/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-ureq/media-type-selection/src/runtime/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-edge-cases/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-edge-cases/README.md.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-edge-cases/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-edge-cases/src/apis/multipart.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-edge-cases/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-edge-cases/src/models/attributes.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-edge-cases/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-edge-cases/src/models/optional_upload.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-edge-cases/src/models/text_fields.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-edge-cases/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-edge-cases/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-edge-cases/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-edge-cases/src/runtime/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-nested-object-parts/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-nested-object-parts/README.md.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/apis/multipart.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/models/item_config.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/models/nested_upload.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/runtime/mod.rs.golden create mode 100644 tests/golden/typescript/typescript-fetch/media-type-selection/README.md.golden create mode 100644 tests/golden/typescript/typescript-fetch/media-type-selection/apis/MediaApi.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/media-type-selection/apis/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/media-type-selection/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/media-type-selection/models/FileEnvelope.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/media-type-selection/models/Payload.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/media-type-selection/models/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/media-type-selection/package.json.golden create mode 100644 tests/golden/typescript/typescript-fetch/media-type-selection/runtime/runtime.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/media-type-selection/tsconfig.esm.json.golden create mode 100644 tests/golden/typescript/typescript-fetch/media-type-selection/tsconfig.json.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-edge-cases/README.md.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-edge-cases/apis/MultipartApi.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-edge-cases/apis/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-edge-cases/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-edge-cases/models/Attributes.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-edge-cases/models/OptionalUpload.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-edge-cases/models/TextFields.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-edge-cases/models/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-edge-cases/package.json.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-edge-cases/runtime/runtime.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-edge-cases/tsconfig.esm.json.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-edge-cases/tsconfig.json.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/README.md.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/apis/MultipartApi.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/apis/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/models/ItemConfig.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/models/NestedUpload.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/models/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/package.json.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/runtime/runtime.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/tsconfig.esm.json.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/tsconfig.json.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/README.md.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/apis/TransferApi.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/apis/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/package.json.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/runtime/runtime.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/tsconfig.esm.json.golden create mode 100644 tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/tsconfig.json.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/README.md.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/apis/MultipartApi.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/apis/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/models/ItemConfig.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/models/NestedUpload.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/models/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/package.json.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/runtime/runtime.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/tsconfig.esm.json.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/tsconfig.json.golden create mode 100644 tests/multipart_runtime_smoke.rs diff --git a/src/generators/go/http/sigil_emit_api.rs b/src/generators/go/http/sigil_emit_api.rs index 579603af7..f0dfe170d 100644 --- a/src/generators/go/http/sigil_emit_api.rs +++ b/src/generators/go/http/sigil_emit_api.rs @@ -20,8 +20,8 @@ use std::collections::{BTreeMap, BTreeSet, HashSet}; use crate::codegen::traits::file_writer::FileInfo; use crate::ir::types::{ - IrOperation, IrParameter, IrPrimitive, IrRequestBody, IrResponse, IrSpec, IrTypeExpr, - ParameterLocation, + IrObject, IrOperation, IrParameter, IrPrimitive, IrRequestBody, IrResponse, IrSchemaKind, + IrSpec, IrTypeExpr, ParameterLocation, }; use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase}; use sigil_stitch::code_block::CodeBlock; @@ -54,7 +54,7 @@ pub fn generate_api_files( } else { format!("{stem}.go") }; - let body = emit_api_file(tag, ops, module_path); + let body = emit_api_file(tag, ops, ir, module_path); let content = format!("{header}{body}"); files.push(FileInfo::api(filename, content)); } @@ -80,11 +80,11 @@ fn group_by_tag(operations: &[IrOperation]) -> BTreeMap String { +fn emit_api_file(tag: &str, ops: &[&IrOperation], ir: &IrSpec, module_path: &str) -> String { let struct_name = format!("{}API", tag.to_pascal_case()); // Pre-plan each operation so we can build specs from the plans. - let plans: Vec = ops.iter().map(|op| plan_operation(op)).collect(); + let plans: Vec = ops.iter().map(|op| plan_operation(op, ir)).collect(); let filename = format!("{}.go", tag.to_snake_case()); let mut fb = FileSpec::builder(&filename) @@ -134,15 +134,52 @@ fn collect_body_imports(plans: &[OpPlan<'_>], module_path: &str) -> Vec { + pkgs.insert("fmt".to_string()); + if let Some(parts) = &body.multipart_parts { + pkgs.insert("bytes".to_string()); + pkgs.insert("mime/multipart".to_string()); + for part in parts { + collect_stringify_imports(&part.type_expr, &mut pkgs); + } + if parts + .iter() + .any(|part| part.value_encoding == MultipartValueEncoding::Json) + { + pkgs.insert("encoding/json".to_string()); + } + } + } + BodyEncoding::Json => { + pkgs.insert("bytes".to_string()); + pkgs.insert("encoding/json".to_string()); + pkgs.insert("fmt".to_string()); + } + BodyEncoding::TextPlain => { + pkgs.insert("strings".to_string()); + } + BodyEncoding::OctetStream => { + pkgs.insert("bytes".to_string()); + } + BodyEncoding::FormUrlEncoded | BodyEncoding::Xml | BodyEncoding::Other => { + pkgs.insert("fmt".to_string()); + } + } + } } // io.ReadAll used in error handling (4xx branch) pkgs.insert("io".to_string()); if has_typed_responses { - pkgs.insert("encoding/json".to_string()); pkgs.insert("fmt".to_string()); + if plan + .typed_responses + .iter() + .any(|tr| tr.decoding == ResponseDecoding::Json) + { + pkgs.insert("encoding/json".to_string()); + } } // Check if any param stringification needs strconv or fmt @@ -406,15 +443,52 @@ struct ParamBinding<'a> { struct BodyBinding { var_name: String, go_type: String, + media_type: String, + encoding: BodyEncoding, + multipart_parts: Option>, +} + +struct MultipartPart { + wire_name: String, + field_name: String, + type_expr: IrTypeExpr, + is_binary: bool, + required: bool, + value_encoding: MultipartValueEncoding, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum MultipartValueEncoding { + Text, + Json, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum BodyEncoding { + Json, + Multipart, + FormUrlEncoded, + Xml, + TextPlain, + OctetStream, + Other, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum ResponseDecoding { + Json, + Text, + Bytes, } struct TypedResponse { status: String, field_name: String, go_type: String, + decoding: ResponseDecoding, } -fn plan_operation<'a>(op: &'a IrOperation) -> OpPlan<'a> { +fn plan_operation<'a>(op: &'a IrOperation, ir: &IrSpec) -> OpPlan<'a> { let op_id = sanitize_operation_id(&op.operation_id, &op.method, &op.path); let method_name = op_id.to_pascal_case(); let response_type = format!("{method_name}Response"); @@ -446,7 +520,7 @@ fn plan_operation<'a>(op: &'a IrOperation) -> OpPlan<'a> { let body = op .request_body .as_ref() - .and_then(|b| plan_body(b, &mut used_names)); + .and_then(|b| plan_body(b, ir, &mut used_names)); let typed_responses = op.responses.iter().filter_map(plan_response).collect(); @@ -462,9 +536,18 @@ fn plan_operation<'a>(op: &'a IrOperation) -> OpPlan<'a> { } } -fn plan_body(b: &IrRequestBody, used_names: &mut HashSet) -> Option { - let t = pick_body_type(b)?; - let base_ty = go_type_str(&t); +fn plan_body( + b: &IrRequestBody, + ir: &IrSpec, + used_names: &mut HashSet, +) -> Option { + let (media_type, t) = pick_body_content(b)?; + let encoding = body_encoding(&media_type); + let base_ty = match encoding { + BodyEncoding::TextPlain => "string".to_string(), + BodyEncoding::OctetStream => "[]byte".to_string(), + _ => go_type_str(&t), + }; let go_type = if base_ty.starts_with('[') || base_ty.starts_with("map[") || base_ty.starts_with('*') { base_ty @@ -472,16 +555,33 @@ fn plan_body(b: &IrRequestBody, used_names: &mut HashSet) -> Option Option { - let t = pick_response_type(r)?; - let go_type = go_type_str(&t); + let (media_type, t) = pick_response_content(r)?; + let decoding = response_decoding(&media_type); + let go_type = match decoding { + ResponseDecoding::Json => go_type_str(&t), + ResponseDecoding::Text => "string".to_string(), + ResponseDecoding::Bytes => "[]byte".to_string(), + }; Some(TypedResponse { status: r.status.clone(), field_name: response_field_name(&r.status), go_type, + decoding, }) } @@ -564,16 +664,61 @@ fn emit_method_body(plan: &OpPlan<'_>) -> CodeBlock { if let Some(body) = body { cb.add("var bodyReader io.Reader", ()); cb.add_line(); - cb.begin_control_flow(&format!("if {} != nil", body.var_name), ()); - cb.add(&format!("buf, err := json.Marshal({})", body.var_name), ()); - cb.add_line(); - cb.begin_control_flow("if err != nil", ()); - cb.add("return nil, fmt.Errorf(\"marshal body: %%w\", err)", ()); - cb.add_line(); - cb.end_control_flow(); - cb.add("bodyReader = bytes.NewReader(buf)", ()); - cb.add_line(); - cb.end_control_flow(); + match body.encoding { + BodyEncoding::Multipart => { + cb.add("var multipartContentType string", ()); + cb.add_line(); + if let Some(parts) = &body.multipart_parts { + emit_multipart_body(&mut cb, body, parts); + } else { + cb.add( + "return nil, fmt.Errorf(\"unsupported multipart request body: schema must be object-shaped\")", + (), + ); + cb.add_line(); + } + } + BodyEncoding::Json => { + cb.begin_control_flow(&format!("if {} != nil", body.var_name), ()); + cb.add(&format!("buf, err := json.Marshal({})", body.var_name), ()); + cb.add_line(); + cb.begin_control_flow("if err != nil", ()); + cb.add("return nil, fmt.Errorf(\"marshal body: %%w\", err)", ()); + cb.add_line(); + cb.end_control_flow(); + cb.add("bodyReader = bytes.NewReader(buf)", ()); + cb.add_line(); + cb.end_control_flow(); + } + BodyEncoding::TextPlain => { + cb.begin_control_flow(&format!("if {} != nil", body.var_name), ()); + cb.add( + &format!("bodyReader = strings.NewReader(*{})", body.var_name), + (), + ); + cb.add_line(); + cb.end_control_flow(); + } + BodyEncoding::OctetStream => { + cb.begin_control_flow(&format!("if {} != nil", body.var_name), ()); + cb.add( + &format!("bodyReader = bytes.NewReader({})", body.var_name), + (), + ); + cb.add_line(); + cb.end_control_flow(); + } + BodyEncoding::FormUrlEncoded | BodyEncoding::Xml | BodyEncoding::Other => { + cb.add( + &format!( + "return nil, fmt.Errorf(\"unsupported request body media type: {}\")", + body.media_type + ), + (), + ); + cb.add_line(); + } + } } // Build request. @@ -613,9 +758,19 @@ fn emit_method_body(plan: &OpPlan<'_>) -> CodeBlock { } } - if body.is_some() { - cb.add("req.Header.Set(\"Content-Type\", \"application/json\")", ()); - cb.add_line(); + if let Some(body) = body { + if body.encoding == BodyEncoding::Multipart { + cb.begin_control_flow("if multipartContentType != \"\"", ()); + cb.add("req.Header.Set(\"Content-Type\", multipartContentType)", ()); + cb.add_line(); + cb.end_control_flow(); + } else { + cb.add( + &format!("req.Header.Set(\"Content-Type\", \"{}\")", body.media_type), + (), + ); + cb.add_line(); + } } cb.add("req.Header.Set(\"Accept\", \"application/json\")", ()); cb.add_line(); @@ -653,7 +808,10 @@ fn emit_method_body(plan: &OpPlan<'_>) -> CodeBlock { let code = tr.status.parse::().unwrap(); cb.add(&format!("case {code}:"), ()); cb.add_line(); - cb.add("%L", emit_decode_into(&tr.field_name, &tr.go_type)); + cb.add( + "%L", + emit_decode_into(&tr.field_name, &tr.go_type, tr.decoding), + ); } cb.add("default:", ()); cb.add_line(); @@ -667,7 +825,10 @@ fn emit_method_body(plan: &OpPlan<'_>) -> CodeBlock { ), (), ); - cb.add("%L", emit_decode_into(&tr.field_name, &tr.go_type)); + cb.add( + "%L", + emit_decode_into(&tr.field_name, &tr.go_type, tr.decoding), + ); cb.add( "return resp, &runtime.APIError{StatusCode: httpResp.StatusCode, Status: httpResp.Status}", (), @@ -702,7 +863,113 @@ fn emit_method_body(plan: &OpPlan<'_>) -> CodeBlock { cb.build().expect("method body builds") } -fn emit_decode_into(field: &str, go_ty: &str) -> CodeBlock { +fn emit_multipart_body( + cb: &mut sigil_stitch::code_block::CodeBlockBuilder, + body: &BodyBinding, + parts: &[MultipartPart], +) { + cb.begin_control_flow(&format!("if {} != nil", body.var_name), ()); + cb.add("buf := &bytes.Buffer{}", ()); + cb.add_line(); + cb.add("writer := multipart.NewWriter(buf)", ()); + cb.add_line(); + for part in parts { + let value_expr = format!("{}.{}", body.var_name, part.field_name); + if part.required { + emit_required_multipart_part(cb, part, &value_expr); + } else { + cb.begin_control_flow(&format!("if {value_expr} != nil"), ()); + emit_required_multipart_part(cb, part, &format!("*{value_expr}")); + cb.end_control_flow(); + } + } + cb.begin_control_flow("if err := writer.Close(); err != nil", ()); + cb.add( + "return nil, fmt.Errorf(\"close multipart writer: %%w\", err)", + (), + ); + cb.add_line(); + cb.end_control_flow(); + cb.add("bodyReader = buf", ()); + cb.add_line(); + cb.add("multipartContentType = writer.FormDataContentType()", ()); + cb.add_line(); + cb.end_control_flow(); +} + +fn emit_required_multipart_part( + cb: &mut sigil_stitch::code_block::CodeBlockBuilder, + part: &MultipartPart, + value_expr: &str, +) { + if part.is_binary { + cb.add( + &format!( + "partWriter, err := writer.CreateFormFile(\"{}\", \"{}\")", + part.wire_name, part.wire_name + ), + (), + ); + cb.add_line(); + cb.begin_control_flow("if err != nil", ()); + cb.add( + "return nil, fmt.Errorf(\"create multipart file: %%w\", err)", + (), + ); + cb.add_line(); + cb.end_control_flow(); + cb.begin_control_flow( + &format!("if _, err := partWriter.Write({value_expr}); err != nil"), + (), + ); + cb.add( + "return nil, fmt.Errorf(\"write multipart file: %%w\", err)", + (), + ); + cb.add_line(); + cb.end_control_flow(); + } else if part.value_encoding == MultipartValueEncoding::Json { + cb.add(&format!("partValue, err := json.Marshal({value_expr})"), ()); + cb.add_line(); + cb.begin_control_flow("if err != nil", ()); + cb.add( + "return nil, fmt.Errorf(\"marshal multipart field: %%w\", err)", + (), + ); + cb.add_line(); + cb.end_control_flow(); + cb.begin_control_flow( + &format!( + "if err := writer.WriteField(\"{}\", string(partValue)); err != nil", + part.wire_name + ), + (), + ); + cb.add( + "return nil, fmt.Errorf(\"write multipart field: %%w\", err)", + (), + ); + cb.add_line(); + cb.end_control_flow(); + } else { + cb.begin_control_flow( + &format!( + "if err := writer.WriteField(\"{}\", {}); err != nil", + part.wire_name, + render_value_as_string(value_expr, &part.type_expr) + ), + (), + ); + cb.add( + "return nil, fmt.Errorf(\"write multipart field: %%w\", err)", + (), + ); + cb.add_line(); + cb.end_control_flow(); + } +} + +fn emit_decode_into(field: &str, go_ty: &str, decoding: ResponseDecoding) -> CodeBlock { let (elem_ty, assignment) = if go_ty.starts_with('[') || go_ty.starts_with("map[") { (go_ty.to_string(), format!("resp.{field} = payload")) } else { @@ -711,16 +978,39 @@ fn emit_decode_into(field: &str, go_ty: &str) -> CodeBlock { format!("resp.{field} = &payload"), ) }; - sigil_quote!(GoLang { - $> - var $L("payload @{elem_ty}") - if err := json.NewDecoder(httpResp.Body).Decode(&payload); err != nil { - return nil, fmt.Errorf("decode response: %w", err) - } - $L(assignment) - $< - }) - .expect("decode body builds") + match decoding { + ResponseDecoding::Json => sigil_quote!(GoLang { + $> + var $L("payload @{elem_ty}") + if err := json.NewDecoder(httpResp.Body).Decode(&payload); err != nil { + return nil, fmt.Errorf("decode response: %w", err) + } + $L(assignment) + $< + }) + .expect("decode JSON body builds"), + ResponseDecoding::Text => sigil_quote!(GoLang { + $> + bodyBytes, err := io.ReadAll(httpResp.Body) + if err != nil { + return nil, fmt.Errorf("read response: %w", err) + } + payload := string(bodyBytes) + $L(assignment) + $< + }) + .expect("decode text body builds"), + ResponseDecoding::Bytes => sigil_quote!(GoLang { + $> + payload, err := io.ReadAll(httpResp.Body) + if err != nil { + return nil, fmt.Errorf("read response: %w", err) + } + $L(assignment) + $< + }) + .expect("decode bytes body builds"), + } } fn deref_if_pointer(var: &str, is_pointer: bool) -> String { @@ -768,18 +1058,179 @@ fn wildcard_range(status: &str) -> (u16, u16) { } } -fn pick_response_type(r: &IrResponse) -> Option { - r.content - .get("application/json") - .cloned() - .or_else(|| r.content.values().next().cloned()) +fn body_encoding(media_type: &str) -> BodyEncoding { + let base = media_type_base(media_type); + if base == "multipart/form-data" { + BodyEncoding::Multipart + } else if is_json_media_type(media_type) { + BodyEncoding::Json + } else if base == "application/x-www-form-urlencoded" { + BodyEncoding::FormUrlEncoded + } else if is_xml_media_type(media_type) { + BodyEncoding::Xml + } else if base == "text/plain" { + BodyEncoding::TextPlain + } else if base == "application/octet-stream" { + BodyEncoding::OctetStream + } else { + BodyEncoding::Other + } +} + +fn response_decoding(media_type: &str) -> ResponseDecoding { + let base = media_type_base(media_type); + if is_json_media_type(media_type) { + ResponseDecoding::Json + } else if base == "text/plain" || is_xml_media_type(media_type) { + ResponseDecoding::Text + } else { + ResponseDecoding::Bytes + } +} + +fn pick_body_content(body: &IrRequestBody) -> Option<(String, IrTypeExpr)> { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "application/json" + }) + .or_else(|| pick_media_type(&body.content, is_json_media_type)) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "multipart/form-data" + }) + }) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "application/x-www-form-urlencoded" + }) + }) + .or_else(|| pick_media_type(&body.content, is_xml_media_type)) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "text/plain" + }) + }) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "application/octet-stream" + }) + }) + .or_else(|| pick_first_content(&body.content)) +} + +fn pick_response_content(r: &IrResponse) -> Option<(String, IrTypeExpr)> { + pick_media_type(&r.content, |media_type| { + media_type_base(media_type) == "application/json" + }) + .or_else(|| pick_media_type(&r.content, is_json_media_type)) + .or_else(|| { + pick_media_type(&r.content, |media_type| { + media_type_base(media_type) == "application/octet-stream" + }) + }) + .or_else(|| { + pick_media_type(&r.content, |media_type| { + media_type_base(media_type) == "text/plain" + }) + }) + .or_else(|| pick_media_type(&r.content, is_xml_media_type)) + .or_else(|| pick_first_content(&r.content)) +} + +fn pick_media_type( + content: &indexmap::IndexMap, + predicate: impl Fn(&str) -> bool, +) -> Option<(String, IrTypeExpr)> { + content + .iter() + .find(|(media_type, _)| predicate(media_type)) + .map(|(media_type, t)| (media_type.clone(), t.clone())) +} + +fn pick_first_content( + content: &indexmap::IndexMap, +) -> Option<(String, IrTypeExpr)> { + content + .iter() + .next() + .map(|(media_type, t)| (media_type.clone(), t.clone())) +} + +fn media_type_base(media_type: &str) -> String { + media_type + .split(';') + .next() + .unwrap_or(media_type) + .trim() + .to_ascii_lowercase() +} + +fn is_json_media_type(media_type: &str) -> bool { + let base = media_type_base(media_type); + base == "application/json" || base.ends_with("+json") +} + +fn is_xml_media_type(media_type: &str) -> bool { + let base = media_type_base(media_type); + base == "application/xml" || base == "text/xml" || base.ends_with("+xml") +} + +fn multipart_parts_for(t: &IrTypeExpr, ir: &IrSpec) -> Option> { + // TODO: Honor OpenAPI multipart encoding metadata once it is represented in the IR. + resolve_object(t, ir).map(|obj| { + obj.properties + .iter() + .map(|(wire_name, prop)| MultipartPart { + wire_name: wire_name.clone(), + field_name: go_field_name(wire_name), + type_expr: prop.type_expr.clone(), + is_binary: is_binary_type(&prop.type_expr, ir), + required: prop.required && !prop.nullable, + value_encoding: multipart_value_encoding(&prop.type_expr, ir), + }) + .collect() + }) +} + +fn resolve_object<'a>(expr: &IrTypeExpr, ir: &'a IrSpec) -> Option<&'a IrObject> { + match expr { + IrTypeExpr::Named(name) => match ir.schemas.get(name).map(|schema| &schema.kind) { + Some(IrSchemaKind::Object(obj)) => Some(obj), + Some(IrSchemaKind::Alias(inner)) => resolve_object(inner, ir), + _ => None, + }, + IrTypeExpr::Nullable(inner) => resolve_object(inner, ir), + _ => None, + } +} + +fn is_binary_type(expr: &IrTypeExpr, ir: &IrSpec) -> bool { + match expr { + IrTypeExpr::Primitive(IrPrimitive::Binary) => true, + IrTypeExpr::Nullable(inner) => is_binary_type(inner, ir), + IrTypeExpr::Named(name) => ir.schemas.get(name).is_some_and(|schema| { + matches!(&schema.kind, IrSchemaKind::Alias(inner) if is_binary_type(inner, ir)) + }), + _ => false, + } } -fn pick_body_type(body: &IrRequestBody) -> Option { - body.content - .get("application/json") - .cloned() - .or_else(|| body.content.values().next().cloned()) +fn multipart_value_encoding(expr: &IrTypeExpr, ir: &IrSpec) -> MultipartValueEncoding { + if is_multipart_text_type(expr, ir) { + MultipartValueEncoding::Text + } else { + MultipartValueEncoding::Json + } +} + +fn is_multipart_text_type(expr: &IrTypeExpr, ir: &IrSpec) -> bool { + match expr { + IrTypeExpr::Primitive(_) | IrTypeExpr::StringLiteral(_) | IrTypeExpr::StringEnum(_) => true, + IrTypeExpr::Nullable(inner) => is_multipart_text_type(inner, ir), + IrTypeExpr::Named(name) => ir.schemas.get(name).is_some_and(|schema| { + matches!(&schema.kind, IrSchemaKind::Alias(inner) if is_multipart_text_type(inner, ir)) + }), + _ => false, + } } // --------------------------------------------------------------------------- @@ -801,6 +1252,10 @@ fn go_ident(name: &str) -> String { } } +fn go_field_name(name: &str) -> String { + name.to_pascal_case() +} + fn render_value_as_string(value_expr: &str, t: &IrTypeExpr) -> String { match t { IrTypeExpr::Primitive( diff --git a/src/generators/java/okhttp/runtime/ApiClient.java.txt b/src/generators/java/okhttp/runtime/ApiClient.java.txt index 8028c414f..7afe110e4 100644 --- a/src/generators/java/okhttp/runtime/ApiClient.java.txt +++ b/src/generators/java/okhttp/runtime/ApiClient.java.txt @@ -30,6 +30,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -42,11 +54,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/src/generators/java/okhttp/sigil_emit_api.rs b/src/generators/java/okhttp/sigil_emit_api.rs index daa0e7909..764f00b3c 100644 --- a/src/generators/java/okhttp/sigil_emit_api.rs +++ b/src/generators/java/okhttp/sigil_emit_api.rs @@ -2,15 +2,16 @@ use std::collections::{BTreeMap, HashSet}; use crate::codegen::traits::file_writer::FileInfo; use crate::ir::types::{ - IrOperation, IrParameter, IrRequestBody, IrResponse, IrSpec, IrTypeExpr, ParameterLocation, + IrObject, IrOperation, IrParameter, IrPrimitive, IrRequestBody, IrResponse, IrSchemaKind, + IrSpec, IrTypeExpr, ParameterLocation, }; use heck::{ToLowerCamelCase, ToPascalCase}; use sigil_stitch::lang::java::Java; use sigil_stitch::prelude::*; use super::util::{ - build_java_getter, java_boxed_type_str, java_ident, java_type_str, render_value_as_string, - sanitize_operation_id, unique_name, + build_java_getter, java_boxed_type_str, java_field_name, java_ident, java_type_str, + render_value_as_string, sanitize_operation_id, unique_name, }; const RENDER_WIDTH: usize = 100; @@ -25,7 +26,7 @@ pub fn generate_api_files( for (tag, ops) in &by_tag { let class_name = format!("{}Api", tag.to_pascal_case()); let filename = format!("{class_name}.java"); - let body = emit_api_file(tag, ops, package_name); + let body = emit_api_file(tag, ops, ir, package_name); let content = format!("{header}{body}"); files.push(FileInfo::api(filename, content)); } @@ -51,9 +52,9 @@ fn group_by_tag(operations: &[IrOperation]) -> BTreeMap String { +fn emit_api_file(tag: &str, ops: &[&IrOperation], ir: &IrSpec, package_name: &str) -> String { let class_name = format!("{}Api", tag.to_pascal_case()); - let plans: Vec = ops.iter().map(|op| plan_operation(op)).collect(); + let plans: Vec = ops.iter().map(|op| plan_operation(op, ir)).collect(); let filename = format!("{class_name}.java"); let mut fb = FileSpec::builder_with(&filename, Java::new()) @@ -70,12 +71,36 @@ fn emit_api_file(tag: &str, ops: &[&IrOperation], package_name: &str) -> String .add_import(ImportSpec::named("com.google.gson", "Gson")) .add_import(ImportSpec::named("com.google.gson.reflect", "TypeToken")) .add_import(ImportSpec::named("java.io", "IOException")) + .add_import(ImportSpec::named("java.nio.charset", "StandardCharsets")) .add_import(ImportSpec::named("java.util", "HashMap")) .add_import(ImportSpec::named("java.util", "List")) .add_import(ImportSpec::named("java.util", "Map")) .add_import(ImportSpec::named("java.util.stream", "Collectors")) .add_import(ImportSpec::named("okhttp3", "Request")) .add_import(ImportSpec::named("okhttp3", "Response")); + let has_supported_multipart_body = plans.iter().any(|plan| { + plan.body.as_ref().is_some_and(|body| { + media_type_base(&body.media_type) == "multipart/form-data" + && body.multipart_parts.is_some() + }) + }); + let has_binary_multipart_part = plans.iter().any(|plan| { + plan.body.as_ref().is_some_and(|body| { + body.multipart_parts + .as_ref() + .is_some_and(|parts| parts.iter().any(|part| part.is_binary)) + }) + }); + let has_raw_request_body = plans.iter().any(|plan| plan.body.is_some()); + if has_supported_multipart_body { + fb = fb.add_import(ImportSpec::named("okhttp3", "MultipartBody")); + } + if has_raw_request_body { + fb = fb.add_import(ImportSpec::named("okhttp3", "RequestBody")); + } + if has_binary_multipart_part || has_raw_request_body { + fb = fb.add_import(ImportSpec::named("okhttp3", "MediaType")); + } // Response classes for plan in &plans { @@ -331,26 +356,41 @@ fn emit_method_body(plan: &OpPlan<'_>) -> CodeBlock { } } - // Body serialization - let body_arg = if let Some(body) = &plan.body { + // Build request + let query_arg = if has_query { "query" } else { "null" }; + let method = plan.op.method.to_uppercase(); + if let Some(body) = &plan.body { + cb.add_statement("Request request", ()); + if body.encoding == BodyEncoding::Multipart { + if let Some(parts) = &body.multipart_parts { + emit_multipart_body(&mut cb, body, parts); + cb.add_statement( + &format!( + "request = client.newRequestWithBody(\"{method}\", path, {query_arg}, multipartBody)" + ), + (), + ); + } else { + cb.add_statement( + "throw new IllegalArgumentException(\"unsupported multipart request body: schema must be object-shaped\")", + (), + ); + } + } else { + emit_request_body(&mut cb, body); + cb.add_statement( + &format!( + "request = client.newRequestWithBody(\"{method}\", path, {query_arg}, requestBody)" + ), + (), + ); + } + } else { cb.add_statement( - &format!("String jsonBody = gson.toJson({})", body.var_name), + &format!("Request request = client.newRequest(\"{method}\", path, {query_arg}, null)"), (), ); - "jsonBody" - } else { - "null" - }; - - // Build request - let query_arg = if has_query { "query" } else { "null" }; - cb.add_statement( - &format!( - "Request request = client.newRequest(\"{}\", path, {query_arg}, {body_arg})", - plan.op.method.to_uppercase(), - ), - (), - ); + } // Headers for p in &plan.header_params { @@ -393,7 +433,11 @@ fn emit_method_body(plan: &OpPlan<'_>) -> CodeBlock { // Response parsing if !plan.typed_responses.is_empty() { cb.add_statement( - "String responseBody = response.body() != null ? response.body().string() : \"null\"", + "byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]", + (), + ); + cb.add_statement( + "String responseText = new String(responseBytes, StandardCharsets.UTF_8)", (), ); let mut seen: HashSet = HashSet::new(); @@ -403,15 +447,11 @@ fn emit_method_body(plan: &OpPlan<'_>) -> CodeBlock { if !seen.insert(tr.field_name.clone()) { continue; } - let type_token = format!("new TypeToken<{}>() {{}}.getType()", tr.java_type); cb.add_statement(&format!("{} {} = null", tr.java_type, tr.field_name), ()); if let Ok(code) = tr.status.parse::() { cb.begin_control_flow(&format!("if (response.code() == {code})"), ()); cb.add_statement( - &format!( - "{} = gson.fromJson(responseBody, {})", - tr.field_name, type_token - ), + &format!("{} = {}", tr.field_name, response_decode_expr(tr)), (), ); cb.end_control_flow(); @@ -420,10 +460,7 @@ fn emit_method_body(plan: &OpPlan<'_>) -> CodeBlock { let guard = wildcard_status_guard_java(&tr.status); cb.begin_control_flow(&format!("if ({guard})"), ()); cb.add_statement( - &format!( - "{} = gson.fromJson(responseBody, {})", - tr.field_name, type_token - ), + &format!("{} = {}", tr.field_name, response_decode_expr(tr)), (), ); cb.end_control_flow(); @@ -464,6 +501,141 @@ fn emit_method_body(plan: &OpPlan<'_>) -> CodeBlock { cb.build().expect("method body builds") } +fn emit_multipart_body( + cb: &mut sigil_stitch::code_block::CodeBlockBuilder, + body: &BodyBinding, + parts: &[MultipartPart], +) { + if !body.required { + cb.add_statement( + "RequestBody multipartBody = RequestBody.create(new byte[0], null)", + (), + ); + cb.begin_control_flow(&format!("if ({} != null)", body.var_name), ()); + } + cb.add_statement("MultipartBody.Builder multipartBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM)", ()); + for part in parts { + let access = format!( + "{}.get{}()", + body.var_name, + part.field_name.to_pascal_case() + ); + if part.required { + emit_required_multipart_part(cb, part, &access); + } else { + cb.begin_control_flow(&format!("if ({access} != null)"), ()); + emit_required_multipart_part(cb, part, &access); + cb.end_control_flow(); + } + } + if body.required { + cb.add_statement("RequestBody multipartBody = multipartBuilder.build()", ()); + } else { + cb.add_statement("multipartBody = multipartBuilder.build()", ()); + cb.end_control_flow(); + } +} + +fn emit_request_body(cb: &mut sigil_stitch::code_block::CodeBlockBuilder, body: &BodyBinding) { + if !body.required { + cb.add_statement( + "RequestBody requestBody = RequestBody.create(new byte[0], null)", + (), + ); + cb.begin_control_flow(&format!("if ({} != null)", body.var_name), ()); + } + match body.encoding { + BodyEncoding::Json => { + cb.add_statement( + &format!("String jsonBody = gson.toJson({})", body.var_name), + (), + ); + let prefix = if body.required { + "RequestBody requestBody =" + } else { + "requestBody =" + }; + cb.add_statement( + &format!( + "{prefix} RequestBody.create(jsonBody, MediaType.get(\"{}\"))", + body.media_type + ), + (), + ); + } + BodyEncoding::TextPlain | BodyEncoding::OctetStream => { + let prefix = if body.required { + "RequestBody requestBody =" + } else { + "requestBody =" + }; + cb.add_statement( + &format!( + "{prefix} RequestBody.create({}, MediaType.get(\"{}\"))", + body.var_name, body.media_type + ), + (), + ); + } + BodyEncoding::FormUrlEncoded | BodyEncoding::Xml | BodyEncoding::Other => { + cb.add_statement( + &format!( + "throw new IllegalArgumentException(\"unsupported request body media type: {}\")", + body.media_type + ), + (), + ); + } + BodyEncoding::Multipart => unreachable!("multipart handled separately"), + } + if !body.required { + cb.end_control_flow(); + } +} + +fn response_decode_expr(tr: &TypedResponse) -> String { + match tr.decoding { + ResponseDecoding::Json => { + let type_token = format!("new TypeToken<{}>() {{}}.getType()", tr.java_type); + format!("gson.fromJson(responseText.isEmpty() ? \"null\" : responseText, {type_token})") + } + ResponseDecoding::Text => "responseText".to_string(), + ResponseDecoding::Bytes => "responseBytes".to_string(), + } +} + +fn emit_required_multipart_part( + cb: &mut sigil_stitch::code_block::CodeBlockBuilder, + part: &MultipartPart, + access: &str, +) { + if part.is_binary { + cb.add_statement( + &format!( + "multipartBuilder.addFormDataPart(\"{}\", \"{}\", RequestBody.create({}, MediaType.get(\"application/octet-stream\")))", + part.wire_name, part.wire_name, access + ), + (), + ); + } else if part.value_encoding == MultipartValueEncoding::Json { + cb.add_statement( + &format!( + "multipartBuilder.addFormDataPart(\"{}\", gson.toJson({access}))", + part.wire_name + ), + (), + ); + } else { + cb.add_statement( + &format!( + "multipartBuilder.addFormDataPart(\"{}\", String.valueOf({access}))", + part.wire_name + ), + (), + ); + } +} + // --------------------------------------------------------------------------- // Planning // --------------------------------------------------------------------------- @@ -488,15 +660,52 @@ struct ParamBinding<'a> { struct BodyBinding { var_name: String, java_type: String, + media_type: String, + required: bool, + encoding: BodyEncoding, + multipart_parts: Option>, +} + +struct MultipartPart { + wire_name: String, + field_name: String, + is_binary: bool, + required: bool, + value_encoding: MultipartValueEncoding, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum MultipartValueEncoding { + Text, + Json, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum BodyEncoding { + Json, + Multipart, + FormUrlEncoded, + Xml, + TextPlain, + OctetStream, + Other, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum ResponseDecoding { + Json, + Text, + Bytes, } struct TypedResponse { status: String, field_name: String, java_type: String, + decoding: ResponseDecoding, } -fn plan_operation<'a>(op: &'a IrOperation) -> OpPlan<'a> { +fn plan_operation<'a>(op: &'a IrOperation, ir: &IrSpec) -> OpPlan<'a> { let op_id = sanitize_operation_id(&op.operation_id, &op.method, &op.path); let method_name = op_id.to_lower_camel_case(); let response_type = format!("{}Response", op_id.to_pascal_case()); @@ -529,7 +738,7 @@ fn plan_operation<'a>(op: &'a IrOperation) -> OpPlan<'a> { let body = op .request_body .as_ref() - .and_then(|b| plan_body(b, &mut used_names)); + .and_then(|b| plan_body(b, ir, &mut used_names)); let typed_responses = op.responses.iter().filter_map(plan_response).collect(); @@ -545,23 +754,47 @@ fn plan_operation<'a>(op: &'a IrOperation) -> OpPlan<'a> { } } -fn plan_body(b: &IrRequestBody, used_names: &mut HashSet) -> Option { - let t = pick_body_type(b)?; - let java_type = java_type_str(&t); +fn plan_body( + b: &IrRequestBody, + ir: &IrSpec, + used_names: &mut HashSet, +) -> Option { + let (media_type, t) = pick_body_content(b)?; + let encoding = body_encoding(&media_type); + let java_type = match encoding { + BodyEncoding::TextPlain => "String".to_string(), + BodyEncoding::OctetStream => "byte[]".to_string(), + _ => java_type_str(&t), + }; let var_name = unique_name("body", used_names); + let multipart_parts = if media_type_base(&media_type) == "multipart/form-data" { + multipart_parts_for(&t, ir) + } else { + None + }; Some(BodyBinding { var_name, java_type, + media_type, + required: b.required, + encoding, + multipart_parts, }) } fn plan_response(r: &IrResponse) -> Option { - let t = pick_response_type(r)?; - let java_type = java_type_str(&t); + let (media_type, t) = pick_response_content(r)?; + let decoding = response_decoding(&media_type); + let java_type = match decoding { + ResponseDecoding::Json => java_type_str(&t), + ResponseDecoding::Text => "String".to_string(), + ResponseDecoding::Bytes => "byte[]".to_string(), + }; Some(TypedResponse { status: r.status.clone(), field_name: response_field_name(&r.status), java_type, + decoding, }) } @@ -587,16 +820,176 @@ fn wildcard_status_guard_java(status: &str) -> String { } } -fn pick_response_type(r: &IrResponse) -> Option { - r.content - .get("application/json") - .cloned() - .or_else(|| r.content.values().next().cloned()) +fn body_encoding(media_type: &str) -> BodyEncoding { + let base = media_type_base(media_type); + if base == "multipart/form-data" { + BodyEncoding::Multipart + } else if is_json_media_type(media_type) { + BodyEncoding::Json + } else if base == "application/x-www-form-urlencoded" { + BodyEncoding::FormUrlEncoded + } else if is_xml_media_type(media_type) { + BodyEncoding::Xml + } else if base == "text/plain" { + BodyEncoding::TextPlain + } else if base == "application/octet-stream" { + BodyEncoding::OctetStream + } else { + BodyEncoding::Other + } } -fn pick_body_type(body: &IrRequestBody) -> Option { - body.content - .get("application/json") - .cloned() - .or_else(|| body.content.values().next().cloned()) +fn response_decoding(media_type: &str) -> ResponseDecoding { + let base = media_type_base(media_type); + if is_json_media_type(media_type) { + ResponseDecoding::Json + } else if base == "text/plain" || is_xml_media_type(media_type) { + ResponseDecoding::Text + } else { + ResponseDecoding::Bytes + } +} + +fn pick_body_content(body: &IrRequestBody) -> Option<(String, IrTypeExpr)> { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "application/json" + }) + .or_else(|| pick_media_type(&body.content, is_json_media_type)) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "multipart/form-data" + }) + }) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "application/x-www-form-urlencoded" + }) + }) + .or_else(|| pick_media_type(&body.content, is_xml_media_type)) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "text/plain" + }) + }) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "application/octet-stream" + }) + }) + .or_else(|| pick_first_content(&body.content)) +} + +fn pick_response_content(r: &IrResponse) -> Option<(String, IrTypeExpr)> { + pick_media_type(&r.content, |media_type| { + media_type_base(media_type) == "application/json" + }) + .or_else(|| pick_media_type(&r.content, is_json_media_type)) + .or_else(|| { + pick_media_type(&r.content, |media_type| { + media_type_base(media_type) == "application/octet-stream" + }) + }) + .or_else(|| { + pick_media_type(&r.content, |media_type| { + media_type_base(media_type) == "text/plain" + }) + }) + .or_else(|| pick_media_type(&r.content, is_xml_media_type)) + .or_else(|| pick_first_content(&r.content)) +} + +fn pick_media_type( + content: &indexmap::IndexMap, + predicate: impl Fn(&str) -> bool, +) -> Option<(String, IrTypeExpr)> { + content + .iter() + .find(|(media_type, _)| predicate(media_type)) + .map(|(media_type, t)| (media_type.clone(), t.clone())) +} + +fn pick_first_content( + content: &indexmap::IndexMap, +) -> Option<(String, IrTypeExpr)> { + content + .iter() + .next() + .map(|(media_type, t)| (media_type.clone(), t.clone())) +} + +fn media_type_base(media_type: &str) -> String { + media_type + .split(';') + .next() + .unwrap_or(media_type) + .trim() + .to_ascii_lowercase() +} + +fn is_json_media_type(media_type: &str) -> bool { + let base = media_type_base(media_type); + base == "application/json" || base.ends_with("+json") +} + +fn is_xml_media_type(media_type: &str) -> bool { + let base = media_type_base(media_type); + base == "application/xml" || base == "text/xml" || base.ends_with("+xml") +} + +fn multipart_parts_for(t: &IrTypeExpr, ir: &IrSpec) -> Option> { + // TODO: Honor OpenAPI multipart encoding metadata once it is represented in the IR. + resolve_object(t, ir).map(|obj| { + obj.properties + .iter() + .map(|(wire_name, prop)| MultipartPart { + wire_name: wire_name.clone(), + field_name: java_field_name(wire_name), + is_binary: is_binary_type(&prop.type_expr, ir), + required: prop.required && !prop.nullable, + value_encoding: multipart_value_encoding(&prop.type_expr, ir), + }) + .collect() + }) +} + +fn resolve_object<'a>(expr: &IrTypeExpr, ir: &'a IrSpec) -> Option<&'a IrObject> { + match expr { + IrTypeExpr::Named(name) => match ir.schemas.get(name).map(|schema| &schema.kind) { + Some(IrSchemaKind::Object(obj)) => Some(obj), + Some(IrSchemaKind::Alias(inner)) => resolve_object(inner, ir), + _ => None, + }, + IrTypeExpr::Nullable(inner) => resolve_object(inner, ir), + _ => None, + } +} + +fn is_binary_type(expr: &IrTypeExpr, ir: &IrSpec) -> bool { + match expr { + IrTypeExpr::Primitive(IrPrimitive::Binary) => true, + IrTypeExpr::Nullable(inner) => is_binary_type(inner, ir), + IrTypeExpr::Named(name) => ir.schemas.get(name).is_some_and(|schema| { + matches!(&schema.kind, IrSchemaKind::Alias(inner) if is_binary_type(inner, ir)) + }), + _ => false, + } +} + +fn multipart_value_encoding(expr: &IrTypeExpr, ir: &IrSpec) -> MultipartValueEncoding { + if is_multipart_text_type(expr, ir) { + MultipartValueEncoding::Text + } else { + MultipartValueEncoding::Json + } +} + +fn is_multipart_text_type(expr: &IrTypeExpr, ir: &IrSpec) -> bool { + match expr { + IrTypeExpr::Primitive(_) | IrTypeExpr::StringLiteral(_) | IrTypeExpr::StringEnum(_) => true, + IrTypeExpr::Nullable(inner) => is_multipart_text_type(inner, ir), + IrTypeExpr::Named(name) => ir.schemas.get(name).is_some_and(|schema| { + matches!(&schema.kind, IrSchemaKind::Alias(inner) if is_multipart_text_type(inner, ir)) + }), + _ => false, + } } diff --git a/src/generators/kotlin/okhttp/runtime/api_client.kt.txt b/src/generators/kotlin/okhttp/runtime/api_client.kt.txt index 5d0d96e42..da4b01ec8 100644 --- a/src/generators/kotlin/okhttp/runtime/api_client.kt.txt +++ b/src/generators/kotlin/okhttp/runtime/api_client.kt.txt @@ -19,15 +19,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/src/generators/kotlin/okhttp/sigil_emit_api.rs b/src/generators/kotlin/okhttp/sigil_emit_api.rs index cd2a9e1f2..97096371c 100644 --- a/src/generators/kotlin/okhttp/sigil_emit_api.rs +++ b/src/generators/kotlin/okhttp/sigil_emit_api.rs @@ -2,14 +2,16 @@ use std::collections::{BTreeMap, HashSet}; use crate::codegen::traits::file_writer::FileInfo; use crate::ir::types::{ - IrOperation, IrParameter, IrRequestBody, IrResponse, IrSpec, IrTypeExpr, ParameterLocation, + IrObject, IrOperation, IrParameter, IrPrimitive, IrRequestBody, IrResponse, IrSchemaKind, + IrSpec, IrTypeExpr, ParameterLocation, }; use heck::{ToLowerCamelCase, ToPascalCase}; use sigil_stitch::lang::kotlin::Kotlin; use sigil_stitch::prelude::*; use super::util::{ - kt_ident, kt_type_str, render_value_as_string, sanitize_operation_id, unique_name, + kt_field_name, kt_ident, kt_type_str, render_value_as_string, sanitize_operation_id, + unique_name, }; const RENDER_WIDTH: usize = 100; @@ -24,7 +26,7 @@ pub fn generate_api_files( for (tag, ops) in &by_tag { let class_name = format!("{}Api", tag.to_pascal_case()); let filename = format!("{class_name}.kt"); - let body = emit_api_file(tag, ops, package_name); + let body = emit_api_file(tag, ops, ir, package_name); let content = format!("{header}{body}"); files.push(FileInfo::api(filename, content)); } @@ -50,9 +52,9 @@ fn group_by_tag(operations: &[IrOperation]) -> BTreeMap String { +fn emit_api_file(tag: &str, ops: &[&IrOperation], ir: &IrSpec, package_name: &str) -> String { let class_name = format!("{}Api", tag.to_pascal_case()); - let plans: Vec = ops.iter().map(|op| plan_operation(op)).collect(); + let plans: Vec = ops.iter().map(|op| plan_operation(op, ir)).collect(); let filename = format!("{class_name}.kt"); let mut fb = FileSpec::builder_with(&filename, Kotlin::new()) @@ -69,6 +71,34 @@ fn emit_api_file(tag: &str, ops: &[&IrOperation], package_name: &str) -> String .add_import(ImportSpec::named("com.google.gson", "Gson")) .add_import(ImportSpec::named("com.google.gson.reflect", "TypeToken")) .add_import(ImportSpec::named("okhttp3", "Response")); + let has_supported_multipart_body = plans.iter().any(|plan| { + plan.body.as_ref().is_some_and(|body| { + media_type_base(&body.media_type) == "multipart/form-data" + && body.multipart_parts.is_some() + }) + }); + let has_binary_multipart_part = plans.iter().any(|plan| { + plan.body.as_ref().is_some_and(|body| { + body.multipart_parts + .as_ref() + .is_some_and(|parts| parts.iter().any(|part| part.is_binary)) + }) + }); + let has_raw_request_body = plans.iter().any(|plan| plan.body.is_some()); + if has_supported_multipart_body { + fb = fb.add_import(ImportSpec::named("okhttp3", "MultipartBody")); + } + if has_binary_multipart_part || has_raw_request_body { + fb = fb + .add_import(ImportSpec::named( + "okhttp3.MediaType.Companion", + "toMediaType", + )) + .add_import(ImportSpec::named( + "okhttp3.RequestBody.Companion", + "toRequestBody", + )); + } // API class let mut cls = TypeSpec::builder(&class_name, TypeKind::Class).visibility(Visibility::Public); @@ -238,28 +268,44 @@ fn emit_method_body(plan: &OpPlan<'_>) -> CodeBlock { } } - // Body serialization - let body_arg = if let Some(body) = &plan.body { + // Build request + let query_arg = if has_query { "query" } else { "null" }; + let method = plan.op.method.to_uppercase(); + if let Some(body) = &plan.body { + if body.encoding == BodyEncoding::Multipart { + if let Some(parts) = &body.multipart_parts { + emit_multipart_body(&mut cb, body, parts); + cb.add( + &format!( + "val request = client.newRequestWithBody(\"{method}\", path, {query_arg}, multipartBody)" + ), + (), + ); + cb.add_line(); + } else { + cb.add( + "val request: okhttp3.Request = throw IllegalArgumentException(\"unsupported multipart request body: schema must be object-shaped\")", + (), + ); + cb.add_line(); + } + } else { + emit_request_body(&mut cb, body); + cb.add( + &format!( + "val request = client.newRequestWithBody(\"{method}\", path, {query_arg}, requestBody)" + ), + (), + ); + cb.add_line(); + } + } else { cb.add( - &format!("val jsonBody = gson.toJson({})", body.var_name), + &format!("val request = client.newRequest(\"{method}\", path, {query_arg}, null)"), (), ); cb.add_line(); - "jsonBody" - } else { - "null" - }; - - // Build request - let query_arg = if has_query { "query" } else { "null" }; - cb.add( - &format!( - "val request = client.newRequest(\"{}\", path, {query_arg}, {body_arg})", - plan.op.method.to_uppercase(), - ), - (), - ); - cb.add_line(); + } // Headers if !plan.header_params.is_empty() { @@ -313,7 +359,15 @@ fn emit_method_body(plan: &OpPlan<'_>) -> CodeBlock { // Response parsing if !plan.typed_responses.is_empty() { - cb.add("val responseBody = response.body?.string()", ()); + cb.add( + "val responseBytes = response.body?.bytes() ?: ByteArray(0)", + (), + ); + cb.add_line(); + cb.add( + "val responseText = responseBytes.toString(Charsets.UTF_8)", + (), + ); cb.add_line(); let mut seen: HashSet = HashSet::new(); for tr in &plan.typed_responses { @@ -321,10 +375,7 @@ fn emit_method_body(plan: &OpPlan<'_>) -> CodeBlock { continue; } cb.add_line(); - let deserialize_expr = format!( - "gson.fromJson<{}>(responseBody, object : TypeToken<{}>() {{}}.type)", - tr.kt_type, tr.kt_type - ); + let deserialize_expr = response_decode_expr(tr); if let Ok(code) = tr.status.parse::() { cb.add( &format!( @@ -382,6 +433,145 @@ fn emit_method_body(plan: &OpPlan<'_>) -> CodeBlock { cb.build().expect("method body builds") } +fn emit_multipart_body( + cb: &mut sigil_stitch::code_block::CodeBlockBuilder, + body: &BodyBinding, + parts: &[MultipartPart], +) { + if !body.required { + cb.add("var multipartBody = ByteArray(0).toRequestBody(null)", ()); + cb.add_line(); + cb.begin_control_flow(&format!("if ({} != null)", body.var_name), ()); + } + cb.add( + "val multipartBuilder = MultipartBody.Builder().setType(MultipartBody.FORM)", + (), + ); + cb.add_line(); + for part in parts { + let access = format!("{}.{}", body.var_name, part.field_name); + if part.required { + emit_required_multipart_part(cb, part, &access); + } else { + cb.begin_control_flow(&format!("if ({access} != null)"), ()); + emit_required_multipart_part(cb, part, &access); + cb.end_control_flow(); + } + } + if body.required { + cb.add("val multipartBody = multipartBuilder.build()", ()); + } else { + cb.add("multipartBody = multipartBuilder.build()", ()); + } + cb.add_line(); + if !body.required { + cb.end_control_flow(); + } +} + +fn emit_request_body(cb: &mut sigil_stitch::code_block::CodeBlockBuilder, body: &BodyBinding) { + if !body.required { + cb.add("var requestBody = ByteArray(0).toRequestBody(null)", ()); + cb.add_line(); + cb.begin_control_flow(&format!("if ({} != null)", body.var_name), ()); + } + match body.encoding { + BodyEncoding::Json => { + cb.add( + &format!("val jsonBody = gson.toJson({})", body.var_name), + (), + ); + cb.add_line(); + let prefix = if body.required { + "val requestBody =" + } else { + "requestBody =" + }; + cb.add( + &format!( + "{prefix} jsonBody.toRequestBody(\"{}\".toMediaType())", + body.media_type + ), + (), + ); + cb.add_line(); + } + BodyEncoding::TextPlain | BodyEncoding::OctetStream => { + let prefix = if body.required { + "val requestBody =" + } else { + "requestBody =" + }; + cb.add( + &format!( + "{prefix} {}.toRequestBody(\"{}\".toMediaType())", + body.var_name, body.media_type + ), + (), + ); + cb.add_line(); + } + BodyEncoding::FormUrlEncoded | BodyEncoding::Xml | BodyEncoding::Other => { + cb.add( + &format!( + "throw IllegalArgumentException(\"unsupported request body media type: {}\")", + body.media_type + ), + (), + ); + cb.add_line(); + } + BodyEncoding::Multipart => unreachable!("multipart handled separately"), + } + if !body.required { + cb.end_control_flow(); + } +} + +fn response_decode_expr(tr: &TypedResponse) -> String { + match tr.decoding { + ResponseDecoding::Json => format!( + "gson.fromJson<{}>(responseText.ifEmpty {{ \"null\" }}, object : TypeToken<{}>() {{}}.type)", + tr.kt_type, tr.kt_type + ), + ResponseDecoding::Text => "responseText".to_string(), + ResponseDecoding::Bytes => "responseBytes".to_string(), + } +} + +fn emit_required_multipart_part( + cb: &mut sigil_stitch::code_block::CodeBlockBuilder, + part: &MultipartPart, + access: &str, +) { + if part.is_binary { + cb.add( + &format!( + "multipartBuilder.addFormDataPart(\"{}\", \"{}\", {}.toRequestBody(\"application/octet-stream\".toMediaType()))", + part.wire_name, part.wire_name, access + ), + (), + ); + } else if part.value_encoding == MultipartValueEncoding::Json { + cb.add( + &format!( + "multipartBuilder.addFormDataPart(\"{}\", gson.toJson({access}))", + part.wire_name + ), + (), + ); + } else { + cb.add( + &format!( + "multipartBuilder.addFormDataPart(\"{}\", {}.toString())", + part.wire_name, access + ), + (), + ); + } + cb.add_line(); +} + // --------------------------------------------------------------------------- // Planning // --------------------------------------------------------------------------- @@ -406,15 +596,52 @@ struct ParamBinding<'a> { struct BodyBinding { var_name: String, kt_type: String, + media_type: String, + required: bool, + encoding: BodyEncoding, + multipart_parts: Option>, +} + +struct MultipartPart { + wire_name: String, + field_name: String, + is_binary: bool, + required: bool, + value_encoding: MultipartValueEncoding, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum MultipartValueEncoding { + Text, + Json, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum BodyEncoding { + Json, + Multipart, + FormUrlEncoded, + Xml, + TextPlain, + OctetStream, + Other, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum ResponseDecoding { + Json, + Text, + Bytes, } struct TypedResponse { status: String, field_name: String, kt_type: String, + decoding: ResponseDecoding, } -fn plan_operation<'a>(op: &'a IrOperation) -> OpPlan<'a> { +fn plan_operation<'a>(op: &'a IrOperation, ir: &IrSpec) -> OpPlan<'a> { let op_id = sanitize_operation_id(&op.operation_id, &op.method, &op.path); let method_name = op_id.to_lower_camel_case(); let response_type = format!("{}Response", op_id.to_pascal_case()); @@ -447,7 +674,7 @@ fn plan_operation<'a>(op: &'a IrOperation) -> OpPlan<'a> { let body = op .request_body .as_ref() - .and_then(|b| plan_body(b, &mut used_names)); + .and_then(|b| plan_body(b, ir, &mut used_names)); let typed_responses = op.responses.iter().filter_map(plan_response).collect(); @@ -463,20 +690,50 @@ fn plan_operation<'a>(op: &'a IrOperation) -> OpPlan<'a> { } } -fn plan_body(b: &IrRequestBody, used_names: &mut HashSet) -> Option { - let t = pick_body_type(b)?; - let kt_type = kt_type_str(&t); +fn plan_body( + b: &IrRequestBody, + ir: &IrSpec, + used_names: &mut HashSet, +) -> Option { + let (media_type, t) = pick_body_content(b)?; + let encoding = body_encoding(&media_type); + let mut kt_type = match encoding { + BodyEncoding::TextPlain => "String".to_string(), + BodyEncoding::OctetStream => "ByteArray".to_string(), + _ => kt_type_str(&t), + }; + if !b.required { + kt_type = format!("{kt_type}?"); + } let var_name = unique_name("body", used_names); - Some(BodyBinding { var_name, kt_type }) + let multipart_parts = if media_type_base(&media_type) == "multipart/form-data" { + multipart_parts_for(&t, ir) + } else { + None + }; + Some(BodyBinding { + var_name, + kt_type, + media_type, + required: b.required, + encoding, + multipart_parts, + }) } fn plan_response(r: &IrResponse) -> Option { - let t = pick_response_type(r)?; - let kt_type = kt_type_str(&t); + let (media_type, t) = pick_response_content(r)?; + let decoding = response_decoding(&media_type); + let kt_type = match decoding { + ResponseDecoding::Json => kt_type_str(&t), + ResponseDecoding::Text => "String".to_string(), + ResponseDecoding::Bytes => "ByteArray".to_string(), + }; Some(TypedResponse { status: r.status.clone(), field_name: response_field_name(&r.status), kt_type, + decoding, }) } @@ -502,16 +759,176 @@ fn wildcard_status_guard(status: &str) -> String { } } -fn pick_response_type(r: &IrResponse) -> Option { - r.content - .get("application/json") - .cloned() - .or_else(|| r.content.values().next().cloned()) +fn body_encoding(media_type: &str) -> BodyEncoding { + let base = media_type_base(media_type); + if base == "multipart/form-data" { + BodyEncoding::Multipart + } else if is_json_media_type(media_type) { + BodyEncoding::Json + } else if base == "application/x-www-form-urlencoded" { + BodyEncoding::FormUrlEncoded + } else if is_xml_media_type(media_type) { + BodyEncoding::Xml + } else if base == "text/plain" { + BodyEncoding::TextPlain + } else if base == "application/octet-stream" { + BodyEncoding::OctetStream + } else { + BodyEncoding::Other + } +} + +fn response_decoding(media_type: &str) -> ResponseDecoding { + let base = media_type_base(media_type); + if is_json_media_type(media_type) { + ResponseDecoding::Json + } else if base == "text/plain" || is_xml_media_type(media_type) { + ResponseDecoding::Text + } else { + ResponseDecoding::Bytes + } +} + +fn pick_body_content(body: &IrRequestBody) -> Option<(String, IrTypeExpr)> { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "application/json" + }) + .or_else(|| pick_media_type(&body.content, is_json_media_type)) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "multipart/form-data" + }) + }) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "application/x-www-form-urlencoded" + }) + }) + .or_else(|| pick_media_type(&body.content, is_xml_media_type)) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "text/plain" + }) + }) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "application/octet-stream" + }) + }) + .or_else(|| pick_first_content(&body.content)) +} + +fn pick_response_content(r: &IrResponse) -> Option<(String, IrTypeExpr)> { + pick_media_type(&r.content, |media_type| { + media_type_base(media_type) == "application/json" + }) + .or_else(|| pick_media_type(&r.content, is_json_media_type)) + .or_else(|| { + pick_media_type(&r.content, |media_type| { + media_type_base(media_type) == "application/octet-stream" + }) + }) + .or_else(|| { + pick_media_type(&r.content, |media_type| { + media_type_base(media_type) == "text/plain" + }) + }) + .or_else(|| pick_media_type(&r.content, is_xml_media_type)) + .or_else(|| pick_first_content(&r.content)) +} + +fn pick_media_type( + content: &indexmap::IndexMap, + predicate: impl Fn(&str) -> bool, +) -> Option<(String, IrTypeExpr)> { + content + .iter() + .find(|(media_type, _)| predicate(media_type)) + .map(|(media_type, t)| (media_type.clone(), t.clone())) +} + +fn pick_first_content( + content: &indexmap::IndexMap, +) -> Option<(String, IrTypeExpr)> { + content + .iter() + .next() + .map(|(media_type, t)| (media_type.clone(), t.clone())) } -fn pick_body_type(body: &IrRequestBody) -> Option { - body.content - .get("application/json") - .cloned() - .or_else(|| body.content.values().next().cloned()) +fn media_type_base(media_type: &str) -> String { + media_type + .split(';') + .next() + .unwrap_or(media_type) + .trim() + .to_ascii_lowercase() +} + +fn is_json_media_type(media_type: &str) -> bool { + let base = media_type_base(media_type); + base == "application/json" || base.ends_with("+json") +} + +fn is_xml_media_type(media_type: &str) -> bool { + let base = media_type_base(media_type); + base == "application/xml" || base == "text/xml" || base.ends_with("+xml") +} + +fn multipart_parts_for(t: &IrTypeExpr, ir: &IrSpec) -> Option> { + // TODO: Honor OpenAPI multipart encoding metadata once it is represented in the IR. + resolve_object(t, ir).map(|obj| { + obj.properties + .iter() + .map(|(wire_name, prop)| MultipartPart { + wire_name: wire_name.clone(), + field_name: kt_field_name(wire_name), + is_binary: is_binary_type(&prop.type_expr, ir), + required: prop.required && !prop.nullable, + value_encoding: multipart_value_encoding(&prop.type_expr, ir), + }) + .collect() + }) +} + +fn resolve_object<'a>(expr: &IrTypeExpr, ir: &'a IrSpec) -> Option<&'a IrObject> { + match expr { + IrTypeExpr::Named(name) => match ir.schemas.get(name).map(|schema| &schema.kind) { + Some(IrSchemaKind::Object(obj)) => Some(obj), + Some(IrSchemaKind::Alias(inner)) => resolve_object(inner, ir), + _ => None, + }, + IrTypeExpr::Nullable(inner) => resolve_object(inner, ir), + _ => None, + } +} + +fn is_binary_type(expr: &IrTypeExpr, ir: &IrSpec) -> bool { + match expr { + IrTypeExpr::Primitive(IrPrimitive::Binary) => true, + IrTypeExpr::Nullable(inner) => is_binary_type(inner, ir), + IrTypeExpr::Named(name) => ir.schemas.get(name).is_some_and(|schema| { + matches!(&schema.kind, IrSchemaKind::Alias(inner) if is_binary_type(inner, ir)) + }), + _ => false, + } +} + +fn multipart_value_encoding(expr: &IrTypeExpr, ir: &IrSpec) -> MultipartValueEncoding { + if is_multipart_text_type(expr, ir) { + MultipartValueEncoding::Text + } else { + MultipartValueEncoding::Json + } +} + +fn is_multipart_text_type(expr: &IrTypeExpr, ir: &IrSpec) -> bool { + match expr { + IrTypeExpr::Primitive(_) | IrTypeExpr::StringLiteral(_) | IrTypeExpr::StringEnum(_) => true, + IrTypeExpr::Nullable(inner) => is_multipart_text_type(inner, ir), + IrTypeExpr::Named(name) => ir.schemas.get(name).is_some_and(|schema| { + matches!(&schema.kind, IrSchemaKind::Alias(inner) if is_multipart_text_type(inner, ir)) + }), + _ => false, + } } diff --git a/src/generators/python/httpx/emit_api.rs b/src/generators/python/httpx/emit_api.rs index 457a4977e..25d125362 100644 --- a/src/generators/python/httpx/emit_api.rs +++ b/src/generators/python/httpx/emit_api.rs @@ -8,15 +8,17 @@ use std::collections::{BTreeMap, HashSet}; use crate::codegen::traits::file_writer::FileInfo; use crate::ir::types::{ - IrOperation, IrParameter, IrPrimitive, IrRequestBody, IrResponse, IrSpec, IrTypeExpr, - ParameterLocation, + IrObject, IrOperation, IrParameter, IrPrimitive, IrRequestBody, IrResponse, IrSchemaKind, + IrSpec, IrTypeExpr, ParameterLocation, }; use heck::{ToPascalCase, ToSnakeCase}; use sigil_stitch::code_block::CodeBlock; use sigil_stitch::lang::python::Python; use sigil_stitch::prelude::*; -use super::emit_models::{api_type_name, future_annotations_header, is_object_schema}; +use super::emit_models::{ + api_type_name, future_annotations_header, is_object_schema, python_field_name, +}; /// Generate every API file from the IR. pub fn generate_api_files(ir: &IrSpec, header: &str) -> Result, String> { @@ -48,7 +50,7 @@ fn group_by_tag(operations: &[IrOperation]) -> BTreeMap String { let class_name = format!("{}Api", tag.to_pascal_case()); - let plans: Vec = ops.iter().map(|op| plan_operation(op)).collect(); + let plans: Vec = ops.iter().map(|op| plan_operation(op, ir)).collect(); let client_type = TypeName::importable("..runtime.client", "Client"); let error_type = TypeName::importable("..runtime.errors", "ApiError"); @@ -69,11 +71,21 @@ fn emit_api_file(tag: &str, ops: &[&IrOperation], ir: &IrSpec, header: &str) -> cls = cls.add_method(build_api_method(plan, ir, &error_type)); } - let file = FileSpec::builder_with(&format!("{}_api.py", tag.to_snake_case()), Python::new()) + let mut fb = FileSpec::builder_with(&format!("{}_api.py", tag.to_snake_case()), Python::new()) .header(future_annotations_header()) - .add_type(cls.build().expect("API TypeSpec builds")) - .build() - .expect("API FileSpec builds"); + .add_type(cls.build().expect("API TypeSpec builds")); + if plans.iter().any(|plan| { + plan.body.as_ref().is_some_and(|body| { + body.multipart_parts.as_ref().is_some_and(|parts| { + parts + .iter() + .any(|part| part.value_encoding == MultipartValueEncoding::Json) + }) + }) + }) { + fb = fb.add_import(ImportSpec::side_effect("json")); + } + let file = fb.build().expect("API FileSpec builds"); let body = file.render(120).unwrap_or_default(); let mut content = String::with_capacity(header.len() + body.len()); @@ -150,7 +162,7 @@ fn build_api_method(plan: &OpPlan<'_>, ir: &IrSpec, error_type: &TypeName) -> Fu let return_type = if plan.typed_responses.is_empty() { TypeName::primitive("None") } else { - api_type_name(&plan.typed_responses[0].type_expr) + response_type_name(&plan.typed_responses[0]) }; fun = fun.returns(return_type); @@ -197,9 +209,20 @@ fn build_method_body(plan: &OpPlan<'_>, ir: &IrSpec, error_type: &TypeName) -> C } // Header params - let has_headers = !plan.header_params.is_empty(); + let body_content_type = plan.body.as_ref().and_then(|body| { + let base = media_type_base(&body.media_type); + if base != "multipart/form-data" { + Some(body.media_type.as_str()) + } else { + None + } + }); + let has_headers = !plan.header_params.is_empty() || body_content_type.is_some(); if has_headers { cb.add_statement("headers: dict[str, str] = {}", ()); + if let Some(media_type) = body_content_type { + cb.add_statement(&format!("headers[\"Content-Type\"] = \"{media_type}\""), ()); + } for p in &plan.header_params { let stringify = render_stringify(&p.var_name, &p.param.type_expr); if p.param.required { @@ -249,8 +272,47 @@ fn build_method_body(plan: &OpPlan<'_>, ir: &IrSpec, error_type: &TypeName) -> C if has_query { request_args.push("params=params".to_string()); } - if plan.body.is_some() { - request_args.push(format!("json={body_expr}")); + if let Some(body) = &plan.body { + if media_type_base(&body.media_type) == "multipart/form-data" { + if let Some(parts) = &body.multipart_parts { + emit_multipart_data(&mut cb, body, parts, ir); + request_args.push("files=files if files else None".to_string()); + } else { + cb.add_statement( + "raise ValueError(\"unsupported multipart request body: schema must be object-shaped\")", + (), + ); + } + } else { + match body.encoding { + BodyEncoding::Json => request_args.push(format!("json={body_expr}")), + BodyEncoding::FormUrlEncoded => request_args.push(format!("data={body_expr}")), + BodyEncoding::TextPlain | BodyEncoding::OctetStream => { + request_args.push(format!("content={body_expr}")); + } + BodyEncoding::Xml | BodyEncoding::Other => { + if body.required { + cb.add_statement( + &format!( + "raise ValueError(\"unsupported request body media type: {}\")", + body.media_type + ), + (), + ); + } else { + cb.add_statement(&format!("if {} is not None:%>", body.var_name), ()); + cb.add_statement( + &format!( + "raise ValueError(\"unsupported request body media type: {}\")%<", + body.media_type + ), + (), + ); + } + } + BodyEncoding::Multipart => unreachable!("multipart handled separately"), + } + } } if has_headers { request_args.push("headers=headers".to_string()); @@ -274,7 +336,7 @@ fn build_method_body(plan: &OpPlan<'_>, ir: &IrSpec, error_type: &TypeName) -> C // Response parsing if !plan.typed_responses.is_empty() { let tr = &plan.typed_responses[0]; - let parse_expr = render_response_parse(&tr.type_expr, ir); + let parse_expr = render_response_parse(tr, ir); cb.add_statement(&format!("return {parse_expr}"), ()); } else { cb.add_statement("return None", ()); @@ -283,6 +345,79 @@ fn build_method_body(plan: &OpPlan<'_>, ir: &IrSpec, error_type: &TypeName) -> C cb.build().expect("API method body builds") } +fn emit_multipart_data( + cb: &mut sigil_stitch::code_block::CodeBlockBuilder, + body: &BodyBinding, + parts: &[MultipartPart], + ir: &IrSpec, +) { + cb.add_statement("files: dict[str, object] = {}", ()); + if !body.required { + cb.add_statement(&format!("if {} is not None:%>", body.var_name), ()); + } + for part in parts { + let access = format!("{}.{}", body.var_name, part.field_name); + if part.required { + emit_required_multipart_part(cb, part, &access, ir); + } else { + cb.add_statement(&format!("if {access} is not None:%>"), ()); + emit_required_multipart_part(cb, part, &access, ir); + cb.add_statement("%<", ()); + } + } + if !body.required { + cb.add_statement("%<", ()); + } +} + +fn emit_required_multipart_part( + cb: &mut sigil_stitch::code_block::CodeBlockBuilder, + part: &MultipartPart, + access: &str, + ir: &IrSpec, +) { + if part.is_binary { + cb.add_statement( + &format!( + "files[\"{}\"] = (\"{}\", {access}, \"application/octet-stream\")", + part.wire_name, part.wire_name + ), + (), + ); + } else if part.value_encoding == MultipartValueEncoding::Json { + let json_value = render_multipart_json_value(access, &part.type_expr, ir); + cb.add_statement( + &format!( + "files[\"{}\"] = (None, json.dumps({json_value}), \"application/json\")", + part.wire_name + ), + (), + ); + } else { + cb.add_statement( + &format!("files[\"{}\"] = (None, str({access}))", part.wire_name), + (), + ); + } +} + +fn render_multipart_json_value(access: &str, expr: &IrTypeExpr, ir: &IrSpec) -> String { + match expr { + IrTypeExpr::Named(name) if is_object_schema(name, ir) => format!("{access}.to_dict()"), + IrTypeExpr::Nullable(inner) => render_multipart_json_value(access, inner, ir), + IrTypeExpr::Array(inner) => { + if let IrTypeExpr::Named(name) = inner.as_ref() + && is_object_schema(name, ir) + { + format!("[item.to_dict() for item in {access}]") + } else { + access.to_string() + } + } + _ => access.to_string(), + } +} + fn render_stringify(var: &str, type_expr: &IrTypeExpr) -> String { match type_expr { IrTypeExpr::Primitive( @@ -308,7 +443,23 @@ fn render_stringify(var: &str, type_expr: &IrTypeExpr) -> String { } } -fn render_response_parse(type_expr: &IrTypeExpr, ir: &IrSpec) -> String { +fn response_type_name(response: &TypedResponse) -> TypeName { + match response.decoding { + ResponseDecoding::Json => api_type_name(&response.type_expr), + ResponseDecoding::Text => TypeName::primitive("str"), + ResponseDecoding::Bytes => TypeName::primitive("bytes"), + } +} + +fn render_response_parse(response: &TypedResponse, ir: &IrSpec) -> String { + match response.decoding { + ResponseDecoding::Json => render_json_response_parse(&response.type_expr, ir), + ResponseDecoding::Text => "response.text".to_string(), + ResponseDecoding::Bytes => "response.content".to_string(), + } +} + +fn render_json_response_parse(type_expr: &IrTypeExpr, ir: &IrSpec) -> String { match type_expr { IrTypeExpr::Named(name) => { let py_name = name.to_pascal_case(); @@ -373,13 +524,50 @@ struct BodyBinding { var_name: String, type_expr: IrTypeExpr, required: bool, + media_type: String, + encoding: BodyEncoding, + multipart_parts: Option>, +} + +struct MultipartPart { + wire_name: String, + field_name: String, + type_expr: IrTypeExpr, + is_binary: bool, + required: bool, + value_encoding: MultipartValueEncoding, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum MultipartValueEncoding { + Text, + Json, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum BodyEncoding { + Json, + Multipart, + FormUrlEncoded, + Xml, + TextPlain, + OctetStream, + Other, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum ResponseDecoding { + Json, + Text, + Bytes, } struct TypedResponse { type_expr: IrTypeExpr, + decoding: ResponseDecoding, } -fn plan_operation<'a>(op: &'a IrOperation) -> OpPlan<'a> { +fn plan_operation<'a>(op: &'a IrOperation, ir: &IrSpec) -> OpPlan<'a> { let op_id = sanitize_operation_id(&op.operation_id, &op.method, &op.path); let method_name = op_id.to_snake_case(); @@ -404,7 +592,7 @@ fn plan_operation<'a>(op: &'a IrOperation) -> OpPlan<'a> { let body = op .request_body .as_ref() - .and_then(|b| plan_body(b, &mut used_names)); + .and_then(|b| plan_body(b, ir, &mut used_names)); let typed_responses = op.responses.iter().filter_map(plan_response).collect(); @@ -419,33 +607,210 @@ fn plan_operation<'a>(op: &'a IrOperation) -> OpPlan<'a> { } } -fn plan_body(b: &IrRequestBody, used_names: &mut HashSet) -> Option { - let t = pick_body_type(b)?; +fn plan_body( + b: &IrRequestBody, + ir: &IrSpec, + used_names: &mut HashSet, +) -> Option { + let (media_type, t) = pick_body_content(b)?; + let encoding = body_encoding(&media_type); let var_name = unique_name("body", used_names); + let multipart_parts = if media_type_base(&media_type) == "multipart/form-data" { + multipart_parts_for(&t, ir) + } else { + None + }; Some(BodyBinding { var_name, type_expr: t, required: b.required, + media_type, + encoding, + multipart_parts, }) } fn plan_response(r: &IrResponse) -> Option { - let t = pick_response_type(r)?; - Some(TypedResponse { type_expr: t }) + let (media_type, t) = pick_response_content(r)?; + Some(TypedResponse { + type_expr: t, + decoding: response_decoding(&media_type), + }) } -fn pick_body_type(body: &IrRequestBody) -> Option { - body.content - .get("application/json") - .cloned() - .or_else(|| body.content.values().next().cloned()) +fn body_encoding(media_type: &str) -> BodyEncoding { + let base = media_type_base(media_type); + if base == "multipart/form-data" { + BodyEncoding::Multipart + } else if is_json_media_type(media_type) { + BodyEncoding::Json + } else if base == "application/x-www-form-urlencoded" { + BodyEncoding::FormUrlEncoded + } else if is_xml_media_type(media_type) { + BodyEncoding::Xml + } else if base == "text/plain" { + BodyEncoding::TextPlain + } else if base == "application/octet-stream" { + BodyEncoding::OctetStream + } else { + BodyEncoding::Other + } } -fn pick_response_type(r: &IrResponse) -> Option { - r.content - .get("application/json") - .cloned() - .or_else(|| r.content.values().next().cloned()) +fn response_decoding(media_type: &str) -> ResponseDecoding { + let base = media_type_base(media_type); + if is_json_media_type(media_type) { + ResponseDecoding::Json + } else if base == "text/plain" || is_xml_media_type(media_type) { + ResponseDecoding::Text + } else { + ResponseDecoding::Bytes + } +} + +fn pick_body_content(body: &IrRequestBody) -> Option<(String, IrTypeExpr)> { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "application/json" + }) + .or_else(|| pick_media_type(&body.content, is_json_media_type)) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "multipart/form-data" + }) + }) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "application/x-www-form-urlencoded" + }) + }) + .or_else(|| pick_media_type(&body.content, is_xml_media_type)) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "text/plain" + }) + }) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "application/octet-stream" + }) + }) + .or_else(|| pick_first_content(&body.content)) +} + +fn pick_response_content(r: &IrResponse) -> Option<(String, IrTypeExpr)> { + pick_media_type(&r.content, |media_type| { + media_type_base(media_type) == "application/json" + }) + .or_else(|| pick_media_type(&r.content, is_json_media_type)) + .or_else(|| { + pick_media_type(&r.content, |media_type| { + media_type_base(media_type) == "application/octet-stream" + }) + }) + .or_else(|| { + pick_media_type(&r.content, |media_type| { + media_type_base(media_type) == "text/plain" + }) + }) + .or_else(|| pick_media_type(&r.content, is_xml_media_type)) + .or_else(|| pick_first_content(&r.content)) +} + +fn pick_media_type( + content: &indexmap::IndexMap, + predicate: impl Fn(&str) -> bool, +) -> Option<(String, IrTypeExpr)> { + content + .iter() + .find(|(media_type, _)| predicate(media_type)) + .map(|(media_type, t)| (media_type.clone(), t.clone())) +} + +fn pick_first_content( + content: &indexmap::IndexMap, +) -> Option<(String, IrTypeExpr)> { + content + .iter() + .next() + .map(|(media_type, t)| (media_type.clone(), t.clone())) +} + +fn media_type_base(media_type: &str) -> String { + media_type + .split(';') + .next() + .unwrap_or(media_type) + .trim() + .to_ascii_lowercase() +} + +fn is_json_media_type(media_type: &str) -> bool { + let base = media_type_base(media_type); + base == "application/json" || base.ends_with("+json") +} + +fn is_xml_media_type(media_type: &str) -> bool { + let base = media_type_base(media_type); + base == "application/xml" || base == "text/xml" || base.ends_with("+xml") +} + +fn multipart_parts_for(t: &IrTypeExpr, ir: &IrSpec) -> Option> { + // TODO: Honor OpenAPI multipart encoding metadata once it is represented in the IR. + resolve_object(t, ir).map(|obj| { + obj.properties + .iter() + .map(|(wire_name, prop)| MultipartPart { + wire_name: wire_name.clone(), + field_name: python_field_name(wire_name), + type_expr: prop.type_expr.clone(), + is_binary: is_binary_type(&prop.type_expr, ir), + required: prop.required && !prop.nullable, + value_encoding: multipart_value_encoding(&prop.type_expr, ir), + }) + .collect() + }) +} + +fn resolve_object<'a>(expr: &IrTypeExpr, ir: &'a IrSpec) -> Option<&'a IrObject> { + match expr { + IrTypeExpr::Named(name) => match ir.schemas.get(name).map(|schema| &schema.kind) { + Some(IrSchemaKind::Object(obj)) => Some(obj), + Some(IrSchemaKind::Alias(inner)) => resolve_object(inner, ir), + _ => None, + }, + IrTypeExpr::Nullable(inner) => resolve_object(inner, ir), + _ => None, + } +} + +fn is_binary_type(expr: &IrTypeExpr, ir: &IrSpec) -> bool { + match expr { + IrTypeExpr::Primitive(IrPrimitive::Binary) => true, + IrTypeExpr::Nullable(inner) => is_binary_type(inner, ir), + IrTypeExpr::Named(name) => ir.schemas.get(name).is_some_and(|schema| { + matches!(&schema.kind, IrSchemaKind::Alias(inner) if is_binary_type(inner, ir)) + }), + _ => false, + } +} + +fn multipart_value_encoding(expr: &IrTypeExpr, ir: &IrSpec) -> MultipartValueEncoding { + if is_multipart_text_type(expr, ir) { + MultipartValueEncoding::Text + } else { + MultipartValueEncoding::Json + } +} + +fn is_multipart_text_type(expr: &IrTypeExpr, ir: &IrSpec) -> bool { + match expr { + IrTypeExpr::Primitive(_) | IrTypeExpr::StringLiteral(_) | IrTypeExpr::StringEnum(_) => true, + IrTypeExpr::Nullable(inner) => is_multipart_text_type(inner, ir), + IrTypeExpr::Named(name) => ir.schemas.get(name).is_some_and(|schema| { + matches!(&schema.kind, IrSchemaKind::Alias(inner) if is_multipart_text_type(inner, ir)) + }), + _ => false, + } } fn python_param_name(name: &str) -> String { diff --git a/src/generators/python/httpx/runtime/client.py.txt b/src/generators/python/httpx/runtime/client.py.txt index 0de24e311..6efe8d73a 100644 --- a/src/generators/python/httpx/runtime/client.py.txt +++ b/src/generators/python/httpx/runtime/client.py.txt @@ -30,6 +30,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -46,6 +49,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/src/generators/python/requests/emit_api.rs b/src/generators/python/requests/emit_api.rs index e0a44c948..7978ee07d 100644 --- a/src/generators/python/requests/emit_api.rs +++ b/src/generators/python/requests/emit_api.rs @@ -8,15 +8,17 @@ use std::collections::{BTreeMap, HashSet}; use crate::codegen::traits::file_writer::FileInfo; use crate::ir::types::{ - IrOperation, IrParameter, IrPrimitive, IrRequestBody, IrResponse, IrSpec, IrTypeExpr, - ParameterLocation, + IrObject, IrOperation, IrParameter, IrPrimitive, IrRequestBody, IrResponse, IrSchemaKind, + IrSpec, IrTypeExpr, ParameterLocation, }; use heck::{ToPascalCase, ToSnakeCase}; use sigil_stitch::code_block::CodeBlock; use sigil_stitch::lang::python::Python; use sigil_stitch::prelude::*; -use super::emit_models::{api_type_name, future_annotations_header, is_object_schema}; +use super::emit_models::{ + api_type_name, future_annotations_header, is_object_schema, python_field_name, +}; /// Generate every API file from the IR. pub fn generate_api_files(ir: &IrSpec, header: &str) -> Result, String> { @@ -48,7 +50,7 @@ fn group_by_tag(operations: &[IrOperation]) -> BTreeMap String { let class_name = format!("{}Api", tag.to_pascal_case()); - let plans: Vec = ops.iter().map(|op| plan_operation(op)).collect(); + let plans: Vec = ops.iter().map(|op| plan_operation(op, ir)).collect(); let client_type = TypeName::importable("..runtime.client", "Client"); let error_type = TypeName::importable("..runtime.errors", "ApiError"); @@ -69,11 +71,21 @@ fn emit_api_file(tag: &str, ops: &[&IrOperation], ir: &IrSpec, header: &str) -> cls = cls.add_method(build_api_method(plan, ir, &error_type)); } - let file = FileSpec::builder_with(&format!("{}_api.py", tag.to_snake_case()), Python::new()) + let mut fb = FileSpec::builder_with(&format!("{}_api.py", tag.to_snake_case()), Python::new()) .header(future_annotations_header()) - .add_type(cls.build().expect("API TypeSpec builds")) - .build() - .expect("API FileSpec builds"); + .add_type(cls.build().expect("API TypeSpec builds")); + if plans.iter().any(|plan| { + plan.body.as_ref().is_some_and(|body| { + body.multipart_parts.as_ref().is_some_and(|parts| { + parts + .iter() + .any(|part| part.value_encoding == MultipartValueEncoding::Json) + }) + }) + }) { + fb = fb.add_import(ImportSpec::side_effect("json")); + } + let file = fb.build().expect("API FileSpec builds"); let body = file.render(120).unwrap_or_default(); let mut content = String::with_capacity(header.len() + body.len()); @@ -150,7 +162,7 @@ fn build_api_method(plan: &OpPlan<'_>, ir: &IrSpec, error_type: &TypeName) -> Fu let return_type = if plan.typed_responses.is_empty() { TypeName::primitive("None") } else { - api_type_name(&plan.typed_responses[0].type_expr) + response_type_name(&plan.typed_responses[0]) }; fun = fun.returns(return_type); @@ -197,9 +209,20 @@ fn build_method_body(plan: &OpPlan<'_>, ir: &IrSpec, error_type: &TypeName) -> C } // Header params - let has_headers = !plan.header_params.is_empty(); + let body_content_type = plan.body.as_ref().and_then(|body| { + let base = media_type_base(&body.media_type); + if base != "multipart/form-data" { + Some(body.media_type.as_str()) + } else { + None + } + }); + let has_headers = !plan.header_params.is_empty() || body_content_type.is_some(); if has_headers { cb.add_statement("headers: dict[str, str] = {}", ()); + if let Some(media_type) = body_content_type { + cb.add_statement(&format!("headers[\"Content-Type\"] = \"{media_type}\""), ()); + } for p in &plan.header_params { let stringify = render_stringify(&p.var_name, &p.param.type_expr); if p.param.required { @@ -249,8 +272,46 @@ fn build_method_body(plan: &OpPlan<'_>, ir: &IrSpec, error_type: &TypeName) -> C if has_query { request_args.push("params=params".to_string()); } - if plan.body.is_some() { - request_args.push(format!("json={body_expr}")); + if let Some(body) = &plan.body { + if media_type_base(&body.media_type) == "multipart/form-data" { + if let Some(parts) = &body.multipart_parts { + emit_multipart_data(&mut cb, body, parts, ir); + request_args.push("files=files if files else None".to_string()); + } else { + cb.add_statement( + "raise ValueError(\"unsupported multipart request body: schema must be object-shaped\")", + (), + ); + } + } else { + match body.encoding { + BodyEncoding::Json => request_args.push(format!("json={body_expr}")), + BodyEncoding::FormUrlEncoded + | BodyEncoding::TextPlain + | BodyEncoding::OctetStream => request_args.push(format!("data={body_expr}")), + BodyEncoding::Xml | BodyEncoding::Other => { + if body.required { + cb.add_statement( + &format!( + "raise ValueError(\"unsupported request body media type: {}\")", + body.media_type + ), + (), + ); + } else { + cb.add_statement(&format!("if {} is not None:%>", body.var_name), ()); + cb.add_statement( + &format!( + "raise ValueError(\"unsupported request body media type: {}\")%<", + body.media_type + ), + (), + ); + } + } + BodyEncoding::Multipart => unreachable!("multipart handled separately"), + } + } } if has_headers { request_args.push("headers=headers".to_string()); @@ -274,7 +335,7 @@ fn build_method_body(plan: &OpPlan<'_>, ir: &IrSpec, error_type: &TypeName) -> C // Response parsing if !plan.typed_responses.is_empty() { let tr = &plan.typed_responses[0]; - let parse_expr = render_response_parse(&tr.type_expr, ir); + let parse_expr = render_response_parse(tr, ir); cb.add_statement(&format!("return {parse_expr}"), ()); } else { cb.add_statement("return None", ()); @@ -283,6 +344,79 @@ fn build_method_body(plan: &OpPlan<'_>, ir: &IrSpec, error_type: &TypeName) -> C cb.build().expect("API method body builds") } +fn emit_multipart_data( + cb: &mut sigil_stitch::code_block::CodeBlockBuilder, + body: &BodyBinding, + parts: &[MultipartPart], + ir: &IrSpec, +) { + cb.add_statement("files: dict[str, object] = {}", ()); + if !body.required { + cb.add_statement(&format!("if {} is not None:%>", body.var_name), ()); + } + for part in parts { + let access = format!("{}.{}", body.var_name, part.field_name); + if part.required { + emit_required_multipart_part(cb, part, &access, ir); + } else { + cb.add_statement(&format!("if {access} is not None:%>"), ()); + emit_required_multipart_part(cb, part, &access, ir); + cb.add_statement("%<", ()); + } + } + if !body.required { + cb.add_statement("%<", ()); + } +} + +fn emit_required_multipart_part( + cb: &mut sigil_stitch::code_block::CodeBlockBuilder, + part: &MultipartPart, + access: &str, + ir: &IrSpec, +) { + if part.is_binary { + cb.add_statement( + &format!( + "files[\"{}\"] = (\"{}\", {access}, \"application/octet-stream\")", + part.wire_name, part.wire_name + ), + (), + ); + } else if part.value_encoding == MultipartValueEncoding::Json { + let json_value = render_multipart_json_value(access, &part.type_expr, ir); + cb.add_statement( + &format!( + "files[\"{}\"] = (None, json.dumps({json_value}), \"application/json\")", + part.wire_name + ), + (), + ); + } else { + cb.add_statement( + &format!("files[\"{}\"] = (None, str({access}))", part.wire_name), + (), + ); + } +} + +fn render_multipart_json_value(access: &str, expr: &IrTypeExpr, ir: &IrSpec) -> String { + match expr { + IrTypeExpr::Named(name) if is_object_schema(name, ir) => format!("{access}.to_dict()"), + IrTypeExpr::Nullable(inner) => render_multipart_json_value(access, inner, ir), + IrTypeExpr::Array(inner) => { + if let IrTypeExpr::Named(name) = inner.as_ref() + && is_object_schema(name, ir) + { + format!("[item.to_dict() for item in {access}]") + } else { + access.to_string() + } + } + _ => access.to_string(), + } +} + fn render_stringify(var: &str, type_expr: &IrTypeExpr) -> String { match type_expr { IrTypeExpr::Primitive( @@ -308,7 +442,23 @@ fn render_stringify(var: &str, type_expr: &IrTypeExpr) -> String { } } -fn render_response_parse(type_expr: &IrTypeExpr, ir: &IrSpec) -> String { +fn response_type_name(response: &TypedResponse) -> TypeName { + match response.decoding { + ResponseDecoding::Json => api_type_name(&response.type_expr), + ResponseDecoding::Text => TypeName::primitive("str"), + ResponseDecoding::Bytes => TypeName::primitive("bytes"), + } +} + +fn render_response_parse(response: &TypedResponse, ir: &IrSpec) -> String { + match response.decoding { + ResponseDecoding::Json => render_json_response_parse(&response.type_expr, ir), + ResponseDecoding::Text => "response.text".to_string(), + ResponseDecoding::Bytes => "response.content".to_string(), + } +} + +fn render_json_response_parse(type_expr: &IrTypeExpr, ir: &IrSpec) -> String { match type_expr { IrTypeExpr::Named(name) => { let py_name = name.to_pascal_case(); @@ -373,13 +523,50 @@ struct BodyBinding { var_name: String, type_expr: IrTypeExpr, required: bool, + media_type: String, + encoding: BodyEncoding, + multipart_parts: Option>, +} + +struct MultipartPart { + wire_name: String, + field_name: String, + type_expr: IrTypeExpr, + is_binary: bool, + required: bool, + value_encoding: MultipartValueEncoding, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum MultipartValueEncoding { + Text, + Json, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum BodyEncoding { + Json, + Multipart, + FormUrlEncoded, + Xml, + TextPlain, + OctetStream, + Other, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum ResponseDecoding { + Json, + Text, + Bytes, } struct TypedResponse { type_expr: IrTypeExpr, + decoding: ResponseDecoding, } -fn plan_operation<'a>(op: &'a IrOperation) -> OpPlan<'a> { +fn plan_operation<'a>(op: &'a IrOperation, ir: &IrSpec) -> OpPlan<'a> { let op_id = sanitize_operation_id(&op.operation_id, &op.method, &op.path); let method_name = op_id.to_snake_case(); @@ -404,7 +591,7 @@ fn plan_operation<'a>(op: &'a IrOperation) -> OpPlan<'a> { let body = op .request_body .as_ref() - .and_then(|b| plan_body(b, &mut used_names)); + .and_then(|b| plan_body(b, ir, &mut used_names)); let typed_responses = op.responses.iter().filter_map(plan_response).collect(); @@ -419,33 +606,210 @@ fn plan_operation<'a>(op: &'a IrOperation) -> OpPlan<'a> { } } -fn plan_body(b: &IrRequestBody, used_names: &mut HashSet) -> Option { - let t = pick_body_type(b)?; +fn plan_body( + b: &IrRequestBody, + ir: &IrSpec, + used_names: &mut HashSet, +) -> Option { + let (media_type, t) = pick_body_content(b)?; + let encoding = body_encoding(&media_type); let var_name = unique_name("body", used_names); + let multipart_parts = if media_type_base(&media_type) == "multipart/form-data" { + multipart_parts_for(&t, ir) + } else { + None + }; Some(BodyBinding { var_name, type_expr: t, required: b.required, + media_type, + encoding, + multipart_parts, }) } fn plan_response(r: &IrResponse) -> Option { - let t = pick_response_type(r)?; - Some(TypedResponse { type_expr: t }) + let (media_type, t) = pick_response_content(r)?; + Some(TypedResponse { + type_expr: t, + decoding: response_decoding(&media_type), + }) } -fn pick_body_type(body: &IrRequestBody) -> Option { - body.content - .get("application/json") - .cloned() - .or_else(|| body.content.values().next().cloned()) +fn body_encoding(media_type: &str) -> BodyEncoding { + let base = media_type_base(media_type); + if base == "multipart/form-data" { + BodyEncoding::Multipart + } else if is_json_media_type(media_type) { + BodyEncoding::Json + } else if base == "application/x-www-form-urlencoded" { + BodyEncoding::FormUrlEncoded + } else if is_xml_media_type(media_type) { + BodyEncoding::Xml + } else if base == "text/plain" { + BodyEncoding::TextPlain + } else if base == "application/octet-stream" { + BodyEncoding::OctetStream + } else { + BodyEncoding::Other + } } -fn pick_response_type(r: &IrResponse) -> Option { - r.content - .get("application/json") - .cloned() - .or_else(|| r.content.values().next().cloned()) +fn response_decoding(media_type: &str) -> ResponseDecoding { + let base = media_type_base(media_type); + if is_json_media_type(media_type) { + ResponseDecoding::Json + } else if base == "text/plain" || is_xml_media_type(media_type) { + ResponseDecoding::Text + } else { + ResponseDecoding::Bytes + } +} + +fn pick_body_content(body: &IrRequestBody) -> Option<(String, IrTypeExpr)> { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "application/json" + }) + .or_else(|| pick_media_type(&body.content, is_json_media_type)) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "multipart/form-data" + }) + }) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "application/x-www-form-urlencoded" + }) + }) + .or_else(|| pick_media_type(&body.content, is_xml_media_type)) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "text/plain" + }) + }) + .or_else(|| { + pick_media_type(&body.content, |media_type| { + media_type_base(media_type) == "application/octet-stream" + }) + }) + .or_else(|| pick_first_content(&body.content)) +} + +fn pick_response_content(r: &IrResponse) -> Option<(String, IrTypeExpr)> { + pick_media_type(&r.content, |media_type| { + media_type_base(media_type) == "application/json" + }) + .or_else(|| pick_media_type(&r.content, is_json_media_type)) + .or_else(|| { + pick_media_type(&r.content, |media_type| { + media_type_base(media_type) == "application/octet-stream" + }) + }) + .or_else(|| { + pick_media_type(&r.content, |media_type| { + media_type_base(media_type) == "text/plain" + }) + }) + .or_else(|| pick_media_type(&r.content, is_xml_media_type)) + .or_else(|| pick_first_content(&r.content)) +} + +fn pick_media_type( + content: &indexmap::IndexMap, + predicate: impl Fn(&str) -> bool, +) -> Option<(String, IrTypeExpr)> { + content + .iter() + .find(|(media_type, _)| predicate(media_type)) + .map(|(media_type, t)| (media_type.clone(), t.clone())) +} + +fn pick_first_content( + content: &indexmap::IndexMap, +) -> Option<(String, IrTypeExpr)> { + content + .iter() + .next() + .map(|(media_type, t)| (media_type.clone(), t.clone())) +} + +fn media_type_base(media_type: &str) -> String { + media_type + .split(';') + .next() + .unwrap_or(media_type) + .trim() + .to_ascii_lowercase() +} + +fn is_json_media_type(media_type: &str) -> bool { + let base = media_type_base(media_type); + base == "application/json" || base.ends_with("+json") +} + +fn is_xml_media_type(media_type: &str) -> bool { + let base = media_type_base(media_type); + base == "application/xml" || base == "text/xml" || base.ends_with("+xml") +} + +fn multipart_parts_for(t: &IrTypeExpr, ir: &IrSpec) -> Option> { + // TODO: Honor OpenAPI multipart encoding metadata once it is represented in the IR. + resolve_object(t, ir).map(|obj| { + obj.properties + .iter() + .map(|(wire_name, prop)| MultipartPart { + wire_name: wire_name.clone(), + field_name: python_field_name(wire_name), + type_expr: prop.type_expr.clone(), + is_binary: is_binary_type(&prop.type_expr, ir), + required: prop.required && !prop.nullable, + value_encoding: multipart_value_encoding(&prop.type_expr, ir), + }) + .collect() + }) +} + +fn resolve_object<'a>(expr: &IrTypeExpr, ir: &'a IrSpec) -> Option<&'a IrObject> { + match expr { + IrTypeExpr::Named(name) => match ir.schemas.get(name).map(|schema| &schema.kind) { + Some(IrSchemaKind::Object(obj)) => Some(obj), + Some(IrSchemaKind::Alias(inner)) => resolve_object(inner, ir), + _ => None, + }, + IrTypeExpr::Nullable(inner) => resolve_object(inner, ir), + _ => None, + } +} + +fn is_binary_type(expr: &IrTypeExpr, ir: &IrSpec) -> bool { + match expr { + IrTypeExpr::Primitive(IrPrimitive::Binary) => true, + IrTypeExpr::Nullable(inner) => is_binary_type(inner, ir), + IrTypeExpr::Named(name) => ir.schemas.get(name).is_some_and(|schema| { + matches!(&schema.kind, IrSchemaKind::Alias(inner) if is_binary_type(inner, ir)) + }), + _ => false, + } +} + +fn multipart_value_encoding(expr: &IrTypeExpr, ir: &IrSpec) -> MultipartValueEncoding { + if is_multipart_text_type(expr, ir) { + MultipartValueEncoding::Text + } else { + MultipartValueEncoding::Json + } +} + +fn is_multipart_text_type(expr: &IrTypeExpr, ir: &IrSpec) -> bool { + match expr { + IrTypeExpr::Primitive(_) | IrTypeExpr::StringLiteral(_) | IrTypeExpr::StringEnum(_) => true, + IrTypeExpr::Nullable(inner) => is_multipart_text_type(inner, ir), + IrTypeExpr::Named(name) => ir.schemas.get(name).is_some_and(|schema| { + matches!(&schema.kind, IrSchemaKind::Alias(inner) if is_multipart_text_type(inner, ir)) + }), + _ => false, + } } fn python_param_name(name: &str) -> String { diff --git a/src/generators/python/requests/runtime/client.py.txt b/src/generators/python/requests/runtime/client.py.txt index 046d006c7..398573763 100644 --- a/src/generators/python/requests/runtime/client.py.txt +++ b/src/generators/python/requests/runtime/client.py.txt @@ -56,6 +56,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -74,6 +76,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/src/generators/rust/common/emit_api.rs b/src/generators/rust/common/emit_api.rs index de165ddc2..28a4a3c87 100644 --- a/src/generators/rust/common/emit_api.rs +++ b/src/generators/rust/common/emit_api.rs @@ -682,6 +682,7 @@ fn is_xml_media_type(media_type: &str) -> bool { } fn multipart_parts_for(t: &IrTypeExpr, ir: &IrSpec) -> Option> { + // TODO: Honor OpenAPI multipart encoding metadata once it is represented in the IR. resolve_object(t, ir).map(|obj| multipart_parts_from_object(obj, ir)) } diff --git a/src/generators/typescript/fetch/project_files/runtime.ts.txt b/src/generators/typescript/fetch/project_files/runtime.ts.txt index e54cd2d7c..548b67f21 100644 --- a/src/generators/typescript/fetch/project_files/runtime.ts.txt +++ b/src/generators/typescript/fetch/project_files/runtime.ts.txt @@ -291,7 +291,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/src/generators/typescript/fetch/sigil_emit.rs b/src/generators/typescript/fetch/sigil_emit.rs index 4e3e18e01..dc40bded6 100644 --- a/src/generators/typescript/fetch/sigil_emit.rs +++ b/src/generators/typescript/fetch/sigil_emit.rs @@ -1852,8 +1852,8 @@ pub fn generate_model_files( fn primitive_to_ts(p: &IrPrimitive) -> &'static str { match p { + IrPrimitive::Binary => "Blob | File", IrPrimitive::String - | IrPrimitive::Binary | IrPrimitive::Date | IrPrimitive::DateTime | IrPrimitive::Uuid diff --git a/src/generators/typescript/fetch/sigil_emit_api.rs b/src/generators/typescript/fetch/sigil_emit_api.rs index 6b12f1a9d..70682aa72 100644 --- a/src/generators/typescript/fetch/sigil_emit_api.rs +++ b/src/generators/typescript/fetch/sigil_emit_api.rs @@ -20,8 +20,8 @@ use std::collections::{BTreeMap, BTreeSet, HashSet}; use crate::codegen::traits::file_writer::FileInfo; use crate::ir::types::{ - IrOperation, IrParameter, IrPrimitive, IrRequestBody, IrResponse, IrSpec, IrTypeExpr, - ParameterLocation as IrParameterLocation, + IrObject, IrOperation, IrParameter, IrPrimitive, IrRequestBody, IrResponse, IrSchemaKind, + IrSpec, IrTypeExpr, ParameterLocation as IrParameterLocation, }; use heck::{ToLowerCamelCase as _, ToPascalCase as _}; use sigil_stitch::code_block::{Arg, CodeBlock}; @@ -55,7 +55,7 @@ pub fn generate_api_files( let mut files = Vec::with_capacity(by_tag.len()); for (tag, ops) in &by_tag { - let file_spec = emit_api_file(tag, ops, property_naming_camel_case, &convertible, ts)?; + let file_spec = emit_api_file(tag, ops, ir, property_naming_camel_case, &convertible, ts)?; let body = file_spec .render(100) .map_err(|e| format!("sigil_emit_api: render {tag}: {e}"))?; @@ -129,6 +129,7 @@ fn group_by_tag(operations: &[IrOperation]) -> BTreeMap, ts: &TypeScript, @@ -160,7 +161,8 @@ fn emit_api_file( // When property_naming_camel_case is enabled, add explicit imports for // converter functions and $Wire types referenced in raw code blocks. if property_naming_camel_case { - let request_types = collect_request_named_types(ops, convertible); + let request_types = + collect_request_named_types(ops, ir, property_naming_camel_case, convertible); let response_types = collect_response_named_types(ops, convertible); for name in request_types.union(&response_types) { @@ -195,6 +197,7 @@ fn emit_api_file( &class_name, &interface_name, ops, + ir, property_naming_camel_case, convertible, )?); @@ -312,17 +315,38 @@ fn build_param_field(param: &IrParameter, name: &str) -> FieldSpec { /// `build_body_field`'s type-selection logic so the emitted `Content-Type` /// agrees with the schema we typed the body as. Prefers `application/json` /// if declared, otherwise the first media type in spec order. -fn preferred_request_media_type(rb: &IrRequestBody) -> Option<&str> { - if rb.content.contains_key("application/json") { - Some("application/json") - } else { - rb.content.keys().next().map(String::as_str) - } +fn preferred_request_media_type(rb: &IrRequestBody) -> Option { + pick_media_type(&rb.content, |media_type| { + media_type_base(media_type) == "application/json" + }) + .or_else(|| pick_media_type(&rb.content, is_json_media_type)) + .or_else(|| { + pick_media_type(&rb.content, |media_type| { + media_type_base(media_type) == "multipart/form-data" + }) + }) + .or_else(|| { + pick_media_type(&rb.content, |media_type| { + media_type_base(media_type) == "application/x-www-form-urlencoded" + }) + }) + .or_else(|| pick_media_type(&rb.content, is_xml_media_type)) + .or_else(|| { + pick_media_type(&rb.content, |media_type| { + media_type_base(media_type) == "text/plain" + }) + }) + .or_else(|| { + pick_media_type(&rb.content, |media_type| { + media_type_base(media_type) == "application/octet-stream" + }) + }) + .or_else(|| pick_first_media_type(&rb.content)) } fn build_body_field(rb: &IrRequestBody, name: &str) -> FieldSpec { let ty = preferred_request_media_type(rb) - .and_then(|mt| rb.content.get(mt)) + .and_then(|mt| rb.content.get(mt.as_str())) .map(type_expr_to_typename) .unwrap_or_else(|| TypeName::primitive("unknown")); let mut fb = FieldSpec::builder(name, ty); @@ -411,6 +435,7 @@ fn build_api_class( class_name: &str, interface_name: &str, ops: &[&IrOperation], + ir: &IrSpec, property_naming_camel_case: bool, convertible: &HashSet, ) -> Result { @@ -423,6 +448,7 @@ fn build_api_class( for op in ops { tb = tb.add_method(build_raw_method( op, + ir, property_naming_camel_case, convertible, )?); @@ -458,6 +484,7 @@ fn build_constructor() -> FunSpec { fn build_raw_method( op: &IrOperation, + ir: &IrSpec, property_naming_camel_case: bool, convertible: &HashSet, ) -> Result { @@ -476,7 +503,7 @@ fn build_raw_method( emit_url_path(&mut body, op); emit_query_params(&mut body, op); emit_headers(&mut body, op); - emit_request_body(&mut body, op, property_naming_camel_case, convertible); + emit_request_body(&mut body, op, ir, property_naming_camel_case, convertible); emit_make_request(&mut body, op, op.request_body.is_some()); emit_response_handler(&mut body, op, property_naming_camel_case, convertible); @@ -601,6 +628,7 @@ fn emit_headers(cb: &mut sigil_stitch::code_block::CodeBlockBuilder, op: &IrOper ); if let Some(rb) = &op.request_body && let Some(media_type) = preferred_request_media_type(rb) + && media_type_base(&media_type) != "multipart/form-data" { cb.add(&format!(" 'Content-Type': '{}',\n", media_type), vec![]); } @@ -626,6 +654,7 @@ fn emit_headers(cb: &mut sigil_stitch::code_block::CodeBlockBuilder, op: &IrOper fn emit_request_body( cb: &mut sigil_stitch::code_block::CodeBlockBuilder, op: &IrOperation, + ir: &IrSpec, property_naming_camel_case: bool, convertible: &HashSet, ) { @@ -635,8 +664,70 @@ fn emit_request_body( let body_name = resolved_body(&names); let access = request_parameters_access(&body_name); - if property_naming_camel_case { - let json_type = body.content.get("application/json"); + if let Some(media_type) = preferred_request_media_type(body) + && media_type_base(&media_type) == "multipart/form-data" + { + if let Some(parts) = body + .content + .get(media_type.as_str()) + .and_then(|ty| multipart_parts_for(ty, ir, property_naming_camel_case)) + { + if !body.required { + cb.add( + "let requestBody: FormData | undefined = undefined;\n", + vec![], + ); + cb.add( + &format!("if ({access} !== undefined && {access} !== null) {{\n"), + vec![], + ); + cb.add("requestBody = new FormData();\n", vec![]); + } else { + cb.add("const requestBody = new FormData();\n", vec![]); + } + for part in parts { + let part_access = format!("{access}{}", ts_property_access(&part.field_name)); + if part.required { + emit_form_data_append(cb, &part, &part_access, convertible); + } else { + cb.add( + &format!( + "if ({part_access} !== undefined && {part_access} !== null) {{\n" + ), + vec![], + ); + emit_form_data_append(cb, &part, &part_access, convertible); + cb.add("}\n", vec![]); + } + } + if !body.required { + cb.add("}\n", vec![]); + } + } else { + emit_unsupported_ts_body( + cb, + &access, + body.required, + "unsupported multipart request body: schema must be object-shaped", + ); + } + } else if let Some(media_type) = preferred_request_media_type(body) + && is_unsupported_ts_request_media_type(&media_type) + { + emit_unsupported_ts_body( + cb, + &access, + body.required, + &format!("unsupported request body media type: {media_type}"), + ); + } else if property_naming_camel_case { + let json_type = preferred_request_media_type(body).and_then(|media_type| { + if is_json_media_type(&media_type) { + body.content.get(media_type.as_str()) + } else { + None + } + }); if let Some(to_json) = json_type.and_then(|ty| body_to_json_expr(ty, &access, convertible)) { @@ -650,6 +741,58 @@ fn emit_request_body( } } +fn emit_unsupported_ts_body( + cb: &mut sigil_stitch::code_block::CodeBlockBuilder, + access: &str, + required: bool, + message: &str, +) { + if required { + cb.add_code( + sigil_quote!(TypeScript { + const requestBody = (() => { throw new Error($S(message)); })(); + }) + .expect("unsupported request body block builds"), + ); + } else { + cb.add_code( + sigil_quote!(TypeScript { + const requestBody = $L(access) === undefined || $L(access) === null ? undefined : (() => { throw new Error($S(message)); })(); + }) + .expect("optional unsupported request body block builds"), + ); + } +} + +fn is_unsupported_ts_request_media_type(media_type: &str) -> bool { + let base = media_type_base(media_type); + is_xml_media_type(media_type) || base == "application/x-www-form-urlencoded" +} + +fn emit_form_data_append( + cb: &mut sigil_stitch::code_block::CodeBlockBuilder, + part: &MultipartPart, + part_access: &str, + convertible: &HashSet, +) { + let value_expr = if part.is_binary { + part_access.to_string() + } else if part.value_encoding == MultipartValueEncoding::Json { + let json_value = multipart_part_to_json_expr(part, part_access, convertible) + .unwrap_or_else(|| part_access.to_string()); + format!("JSON.stringify({json_value})") + } else { + format!("String({part_access})") + }; + cb.add( + &format!( + "requestBody.append({}, {value_expr});\n", + ts_string_literal(&part.wire_name) + ), + vec![], + ); +} + fn emit_make_request( cb: &mut sigil_stitch::code_block::CodeBlockBuilder, op: &IrOperation, @@ -1053,31 +1196,21 @@ enum ResponseKind { } fn classify_response(resp: &IrResponse) -> ResponseKind { - if let Some(ty) = resp.content.get("application/json") { + let Some((media_type, ty)) = pick_response_content(resp) else { + return ResponseKind::None; + }; + if is_json_media_type(&media_type) { return ResponseKind::Json(Some(type_expr_to_typename(ty))); } - if resp.content.keys().any(|k| k.contains("json")) { - return ResponseKind::Json(None); - } - let text_types = [ - "text/plain", - "text/html", - "application/xml", - "text/xml", - "application/x-www-form-urlencoded", - "text/event-stream", - ]; - if resp - .content - .keys() - .any(|k| text_types.iter().any(|t| k.contains(t))) - { - return ResponseKind::Text; - } - if !resp.content.is_empty() { - return ResponseKind::Blob; + match media_type_base(&media_type).as_str() { + "text/plain" + | "text/html" + | "application/xml" + | "text/xml" + | "application/x-www-form-urlencoded" + | "text/event-stream" => ResponseKind::Text, + _ => ResponseKind::Blob, } - ResponseKind::None } // ============================================================================ @@ -1217,8 +1350,8 @@ fn type_expr_to_typename(expr: &IrTypeExpr) -> TypeName { fn primitive_to_ts(p: &IrPrimitive) -> &'static str { match p { + IrPrimitive::Binary => "Blob | File", IrPrimitive::String - | IrPrimitive::Binary | IrPrimitive::Date | IrPrimitive::DateTime | IrPrimitive::Uuid @@ -1243,7 +1376,10 @@ fn response_from_json_transformer( resp: &IrResponse, convertible: &HashSet, ) -> Option { - let type_expr = resp.content.get("application/json")?; + let (media_type, type_expr) = pick_response_content(resp)?; + if !is_json_media_type(&media_type) { + return None; + } match type_expr { IrTypeExpr::Named(name) if convertible.contains(name) => { let pascal = name.to_pascal_case(); @@ -1290,31 +1426,213 @@ fn body_to_json_expr( } } +fn multipart_part_to_json_expr( + part: &MultipartPart, + access: &str, + convertible: &HashSet, +) -> Option { + body_to_json_expr(&part.type_expr, access, convertible) +} + +struct MultipartPart { + wire_name: String, + field_name: String, + type_expr: IrTypeExpr, + is_binary: bool, + required: bool, + value_encoding: MultipartValueEncoding, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum MultipartValueEncoding { + Text, + Json, +} + +fn multipart_parts_for( + t: &IrTypeExpr, + ir: &IrSpec, + property_naming_camel_case: bool, +) -> Option> { + // TODO: Honor OpenAPI multipart encoding metadata once it is represented in the IR. + resolve_object(t, ir).map(|obj| { + obj.properties + .iter() + .map(|(wire_name, prop)| MultipartPart { + wire_name: wire_name.clone(), + field_name: if property_naming_camel_case { + wire_name.to_lower_camel_case() + } else { + wire_name.clone() + }, + type_expr: prop.type_expr.clone(), + is_binary: is_binary_type(&prop.type_expr, ir), + required: prop.required && !prop.nullable, + value_encoding: multipart_value_encoding(&prop.type_expr, ir), + }) + .collect() + }) +} + +fn resolve_object<'a>(expr: &IrTypeExpr, ir: &'a IrSpec) -> Option<&'a IrObject> { + match expr { + IrTypeExpr::Named(name) => match ir.schemas.get(name).map(|schema| &schema.kind) { + Some(IrSchemaKind::Object(obj)) => Some(obj), + Some(IrSchemaKind::Alias(inner)) => resolve_object(inner, ir), + _ => None, + }, + IrTypeExpr::Nullable(inner) => resolve_object(inner, ir), + _ => None, + } +} + +fn is_binary_type(expr: &IrTypeExpr, ir: &IrSpec) -> bool { + match expr { + IrTypeExpr::Primitive(IrPrimitive::Binary) => true, + IrTypeExpr::Nullable(inner) => is_binary_type(inner, ir), + IrTypeExpr::Named(name) => ir.schemas.get(name).is_some_and(|schema| { + matches!(&schema.kind, IrSchemaKind::Alias(inner) if is_binary_type(inner, ir)) + }), + _ => false, + } +} + +fn multipart_value_encoding(expr: &IrTypeExpr, ir: &IrSpec) -> MultipartValueEncoding { + if is_multipart_text_type(expr, ir) { + MultipartValueEncoding::Text + } else { + MultipartValueEncoding::Json + } +} + +fn is_multipart_text_type(expr: &IrTypeExpr, ir: &IrSpec) -> bool { + match expr { + IrTypeExpr::Primitive(_) | IrTypeExpr::StringLiteral(_) | IrTypeExpr::StringEnum(_) => true, + IrTypeExpr::Nullable(inner) => is_multipart_text_type(inner, ir), + IrTypeExpr::Named(name) => ir.schemas.get(name).is_some_and(|schema| { + matches!(&schema.kind, IrSchemaKind::Alias(inner) if is_multipart_text_type(inner, ir)) + }), + _ => false, + } +} + +fn ts_property_access(field_name: &str) -> String { + if is_js_identifier(field_name) { + format!(".{field_name}") + } else { + format!("[{}]", ts_string_literal(field_name)) + } +} + +fn ts_string_literal(value: &str) -> String { + format!("'{}'", value.replace('\\', "\\\\").replace('\'', "\\'")) +} + /// Collect unique Named type references from request bodies only /// that are in the convertible set (i.e., have $Wire/fromJSON/toJSON emitted). fn collect_request_named_types( ops: &[&IrOperation], + ir: &IrSpec, + property_naming_camel_case: bool, convertible: &HashSet, ) -> BTreeSet { let mut names = BTreeSet::new(); for op in ops { - if let Some(ref body) = op.request_body { - if let Some(IrTypeExpr::Named(n)) = body.content.get("application/json") - && convertible.contains(n) - { - names.insert(n.clone()); - } - if let Some(IrTypeExpr::Array(inner)) = body.content.get("application/json") - && let IrTypeExpr::Named(n) = inner.as_ref() - && convertible.contains(n) + if let Some(ref body) = op.request_body + && let Some(media_type) = preferred_request_media_type(body) + { + if is_json_media_type(&media_type) { + if let Some(ty) = body.content.get(media_type.as_str()) { + collect_convertible_named_refs(ty, convertible, &mut names); + } + } else if media_type_base(&media_type) == "multipart/form-data" + && let Some(parts) = body + .content + .get(media_type.as_str()) + .and_then(|ty| multipart_parts_for(ty, ir, property_naming_camel_case)) { - names.insert(n.clone()); + for part in parts { + if part.value_encoding == MultipartValueEncoding::Json { + collect_convertible_named_refs(&part.type_expr, convertible, &mut names); + } + } } } } names } +fn pick_response_content(resp: &IrResponse) -> Option<(String, &IrTypeExpr)> { + pick_media_type_ref(&resp.content, |media_type| { + media_type_base(media_type) == "application/json" + }) + .or_else(|| pick_media_type_ref(&resp.content, is_json_media_type)) + .or_else(|| { + pick_media_type_ref(&resp.content, |media_type| { + media_type_base(media_type) == "application/octet-stream" + }) + }) + .or_else(|| { + pick_media_type_ref(&resp.content, |media_type| { + media_type_base(media_type) == "text/plain" + }) + }) + .or_else(|| pick_media_type_ref(&resp.content, is_xml_media_type)) + .or_else(|| pick_first_media_type_ref(&resp.content)) +} + +fn pick_media_type( + content: &indexmap::IndexMap, + predicate: impl Fn(&str) -> bool, +) -> Option { + content + .keys() + .find(|media_type| predicate(media_type)) + .cloned() +} + +fn pick_first_media_type(content: &indexmap::IndexMap) -> Option { + content.keys().next().cloned() +} + +fn pick_media_type_ref( + content: &indexmap::IndexMap, + predicate: impl Fn(&str) -> bool, +) -> Option<(String, &IrTypeExpr)> { + content + .iter() + .find(|(media_type, _)| predicate(media_type)) + .map(|(media_type, t)| (media_type.clone(), t)) +} + +fn pick_first_media_type_ref( + content: &indexmap::IndexMap, +) -> Option<(String, &IrTypeExpr)> { + content + .iter() + .next() + .map(|(media_type, t)| (media_type.clone(), t)) +} + +fn media_type_base(media_type: &str) -> String { + media_type + .split(';') + .next() + .unwrap_or(media_type) + .trim() + .to_ascii_lowercase() +} + +fn is_json_media_type(media_type: &str) -> bool { + let base = media_type_base(media_type); + base == "application/json" || base.ends_with("+json") +} + +fn is_xml_media_type(media_type: &str) -> bool { + let base = media_type_base(media_type); + base == "application/xml" || base == "text/xml" || base.ends_with("+xml") +} + /// Collect unique Named type references from JSON responses only /// that are in the convertible set (i.e., have $Wire/fromJSON/toJSON emitted). fn collect_response_named_types( @@ -1324,18 +1642,33 @@ fn collect_response_named_types( let mut names = BTreeSet::new(); for op in ops { for resp in &op.responses { - if let Some(IrTypeExpr::Named(n)) = resp.content.get("application/json") - && convertible.contains(n) - { - names.insert(n.clone()); - } - if let Some(IrTypeExpr::Array(inner)) = resp.content.get("application/json") - && let IrTypeExpr::Named(n) = inner.as_ref() - && convertible.contains(n) + if let Some((media_type, ty)) = pick_response_content(resp) + && is_json_media_type(&media_type) { - names.insert(n.clone()); + collect_convertible_named_refs(ty, convertible, &mut names); } } } names } + +fn collect_convertible_named_refs( + expr: &IrTypeExpr, + convertible: &HashSet, + names: &mut BTreeSet, +) { + match expr { + IrTypeExpr::Named(name) if convertible.contains(name) => { + names.insert(name.clone()); + } + IrTypeExpr::Array(inner) | IrTypeExpr::Nullable(inner) | IrTypeExpr::Map(inner) => { + collect_convertible_named_refs(inner, convertible, names); + } + IrTypeExpr::Union(members) => { + for member in members { + collect_convertible_named_refs(member, convertible, names); + } + } + _ => {} + } +} diff --git a/tests/fixtures/valid/media-type-selection.yaml b/tests/fixtures/valid/media-type-selection.yaml new file mode 100644 index 000000000..1dc4e0603 --- /dev/null +++ b/tests/fixtures/valid/media-type-selection.yaml @@ -0,0 +1,115 @@ +openapi: 3.1.0 +info: + title: Media Type Selection + version: 1.0.0 + description: Covers normalized media-type selection for requests and responses. +paths: + /request/parameterized-multipart: + post: + tags: + - media + operationId: send_parameterized_multipart + requestBody: + required: true + content: + multipart/form-data; charset=utf-8: + schema: + $ref: '#/components/schemas/FileEnvelope' + responses: + '204': + description: Accepted + /request/json-vs-multipart: + post: + tags: + - media + operationId: send_json_preferred + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/FileEnvelope' + application/json: + schema: + $ref: '#/components/schemas/Payload' + responses: + '204': + description: Accepted + /request/vendor-json: + patch: + tags: + - media + operationId: send_vendor_json + requestBody: + required: true + content: + application/vnd.example+json; charset=utf-8: + schema: + $ref: '#/components/schemas/Payload' + responses: + '204': + description: Accepted + /response/vendor-json: + get: + tags: + - media + operationId: get_vendor_json + responses: + '200': + description: OK + content: + application/vnd.example+json; charset=utf-8: + schema: + $ref: '#/components/schemas/Payload' + /response/octet-before-xml: + get: + tags: + - media + operationId: get_octet_preferred + responses: + '200': + description: OK + content: + application/xml: + schema: + $ref: '#/components/schemas/Payload' + application/octet-stream: + schema: + type: string + format: binary + /response/text-before-xml: + get: + tags: + - media + operationId: get_text_preferred + responses: + '200': + description: OK + content: + application/xml: + schema: + $ref: '#/components/schemas/Payload' + text/plain: + schema: + type: string +components: + schemas: + FileEnvelope: + type: object + required: + - file + properties: + file: + type: string + format: binary + note: + type: string + Payload: + type: object + required: + - id + properties: + id: + type: string + count: + type: integer diff --git a/tests/fixtures/valid/multipart-edge-cases.yaml b/tests/fixtures/valid/multipart-edge-cases.yaml new file mode 100644 index 000000000..c4b7cd458 --- /dev/null +++ b/tests/fixtures/valid/multipart-edge-cases.yaml @@ -0,0 +1,72 @@ +openapi: 3.1.0 +info: + title: Multipart Edge Cases + version: 1.0.0 + description: Covers optional multipart bodies, optional parts, and text-only multipart fields. +paths: + /multipart/optional: + post: + tags: + - multipart + operationId: send_optional_parts + requestBody: + required: false + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/OptionalUpload' + responses: + '204': + description: Accepted + /multipart/text-only: + post: + tags: + - multipart + operationId: send_text_fields + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/TextFields' + responses: + '204': + description: Accepted +components: + schemas: + OptionalUpload: + type: object + properties: + file: + type: string + format: binary + title: + type: string + retry_count: + type: integer + enabled: + type: boolean + attributes: + $ref: '#/components/schemas/Attributes' + TextFields: + type: object + required: + - note + - retry_count + - enabled + properties: + note: + type: string + retry_count: + type: integer + enabled: + type: boolean + Attributes: + type: object + required: + - label + properties: + label: + type: string + priority: + type: integer diff --git a/tests/fixtures/valid/multipart-nested-object-parts.yaml b/tests/fixtures/valid/multipart-nested-object-parts.yaml new file mode 100644 index 000000000..7e2cab3bc --- /dev/null +++ b/tests/fixtures/valid/multipart-nested-object-parts.yaml @@ -0,0 +1,42 @@ +openapi: 3.1.0 +info: + title: Multipart Nested Object Parts + version: 1.0.0 + description: Covers multipart object parts whose wire names differ from ergonomic names. +paths: + /multipart/nested-object: + post: + tags: + - multipart + operationId: send_nested_object_part + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/NestedUpload' + responses: + '204': + description: Accepted +components: + schemas: + NestedUpload: + type: object + required: + - file + - item_config + properties: + file: + type: string + format: binary + item_config: + $ref: '#/components/schemas/ItemConfig' + ItemConfig: + type: object + required: + - display_name + properties: + display_name: + type: string + retention_days: + type: integer diff --git a/tests/golden/go/go-http/binary-transfer-media-types/apis/transfer.go.golden b/tests/golden/go/go-http/binary-transfer-media-types/apis/transfer.go.golden index 2a8a1eb22..5e6d19d0d 100644 --- a/tests/golden/go/go-http/binary-transfer-media-types/apis/transfer.go.golden +++ b/tests/golden/go/go-http/binary-transfer-media-types/apis/transfer.go.golden @@ -11,6 +11,7 @@ import ( "encoding/json" "fmt" "io" + "mime/multipart" "net/http" "strings" @@ -32,7 +33,7 @@ func NewTransferAPI(client *runtime.Client) *TransferAPI { type DownloadAssetResponse struct { StatusCode int Raw *http.Response - Status200 []int32 + Status200 []byte } // DownloadAsset calls GET /assets/{asset_id}/content. @@ -53,9 +54,9 @@ func (a *TransferAPI) DownloadAsset(ctx context.Context, assetId string) (*Downl resp := &DownloadAssetResponse{StatusCode: httpResp.StatusCode, Raw: httpResp} switch httpResp.StatusCode { case 200: - var payload []int32 - if err := json.NewDecoder(httpResp.Body).Decode(&payload); err != nil { - return nil, fmt.Errorf("decode response: %w", err) + payload, err := io.ReadAll(httpResp.Body) + if err != nil { + return nil, fmt.Errorf("read response: %w", err) } resp.Status200 = payload default: @@ -78,18 +79,40 @@ type UploadAssetResponse struct { func (a *TransferAPI) UploadAsset(ctx context.Context, body *models.UploadAssetRequest) (*UploadAssetResponse, error) { path := "/uploads" var bodyReader io.Reader + var multipartContentType string if body != nil { - buf, err := json.Marshal(body) + buf := &bytes.Buffer{} + writer := multipart.NewWriter(buf) + partWriter, err := writer.CreateFormFile("file", "file") + if err != nil { + return nil, fmt.Errorf("create multipart file: %w", err) + } + if _, err := partWriter.Write(body.File); err != nil { + return nil, fmt.Errorf("write multipart file: %w", err) + } + partValue, err := json.Marshal(body.Metadata) if err != nil { - return nil, fmt.Errorf("marshal body: %w", err) + return nil, fmt.Errorf("marshal multipart field: %w", err) } - bodyReader = bytes.NewReader(buf) + if err := writer.WriteField("metadata", string(partValue)); err != nil { + return nil, fmt.Errorf("write multipart field: %w", err) + } + if err := writer.WriteField("purpose", body.Purpose); err != nil { + return nil, fmt.Errorf("write multipart field: %w", err) + } + if err := writer.Close(); err != nil { + return nil, fmt.Errorf("close multipart writer: %w", err) + } + bodyReader = buf + multipartContentType = writer.FormDataContentType() } req, err := a.client.NewRequest(ctx, "POST", path, nil, bodyReader) if err != nil { return nil, err } - req.Header.Set("Content-Type", "application/json") + if multipartContentType != "" { + req.Header.Set("Content-Type", multipartContentType) + } req.Header.Set("Accept", "application/json") httpResp, err := a.client.Do(req) if err != nil { diff --git a/tests/golden/go/go-http/media-type-selection/README.md.golden b/tests/golden/go/go-http/media-type-selection/README.md.golden new file mode 100644 index 000000000..7a446d9d1 --- /dev/null +++ b/tests/golden/go/go-http/media-type-selection/README.md.golden @@ -0,0 +1,7 @@ +# Media Type Selection + +Covers normalized media-type selection for requests and responses. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `media-type-selection`. diff --git a/tests/golden/go/go-http/media-type-selection/apis/media.go.golden b/tests/golden/go/go-http/media-type-selection/apis/media.go.golden new file mode 100644 index 000000000..11500eff6 --- /dev/null +++ b/tests/golden/go/go-http/media-type-selection/apis/media.go.golden @@ -0,0 +1,272 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package apis + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + + "example.com/sdk/models" + "example.com/sdk/runtime" +) + +// MediaAPI groups operations under the corresponding tag. +type MediaAPI struct { + client *runtime.Client +} + +// NewMediaAPI constructs a MediaAPI bound to client. +func NewMediaAPI(client *runtime.Client) *MediaAPI { + return &MediaAPI{client: client} +} + +// SendJsonPreferredResponse carries the response from the corresponding operation. +type SendJsonPreferredResponse struct { + StatusCode int + Raw *http.Response +} + +// SendJsonPreferred calls POST /request/json-vs-multipart. +func (a *MediaAPI) SendJsonPreferred(ctx context.Context, body *models.Payload) (*SendJsonPreferredResponse, error) { + path := "/request/json-vs-multipart" + var bodyReader io.Reader + if body != nil { + buf, err := json.Marshal(body) + if err != nil { + return nil, fmt.Errorf("marshal body: %w", err) + } + bodyReader = bytes.NewReader(buf) + } + req, err := a.client.NewRequest(ctx, "POST", path, nil, bodyReader) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + httpResp, err := a.client.Do(req) + if err != nil { + return nil, err + } + defer httpResp.Body.Close() + + resp := &SendJsonPreferredResponse{StatusCode: httpResp.StatusCode, Raw: httpResp} + if httpResp.StatusCode >= 400 { + body, _ := io.ReadAll(httpResp.Body) + return nil, &runtime.APIError{StatusCode: httpResp.StatusCode, Status: httpResp.Status, Body: body} + } + return resp, nil +} + +// SendParameterizedMultipartResponse carries the response from the corresponding operation. +type SendParameterizedMultipartResponse struct { + StatusCode int + Raw *http.Response +} + +// SendParameterizedMultipart calls POST /request/parameterized-multipart. +func (a *MediaAPI) SendParameterizedMultipart(ctx context.Context, body *models.FileEnvelope) (*SendParameterizedMultipartResponse, error) { + path := "/request/parameterized-multipart" + var bodyReader io.Reader + var multipartContentType string + if body != nil { + buf := &bytes.Buffer{} + writer := multipart.NewWriter(buf) + partWriter, err := writer.CreateFormFile("file", "file") + if err != nil { + return nil, fmt.Errorf("create multipart file: %w", err) + } + if _, err := partWriter.Write(body.File); err != nil { + return nil, fmt.Errorf("write multipart file: %w", err) + } + if body.Note != nil { + if err := writer.WriteField("note", *body.Note); err != nil { + return nil, fmt.Errorf("write multipart field: %w", err) + } + } + if err := writer.Close(); err != nil { + return nil, fmt.Errorf("close multipart writer: %w", err) + } + bodyReader = buf + multipartContentType = writer.FormDataContentType() + } + req, err := a.client.NewRequest(ctx, "POST", path, nil, bodyReader) + if err != nil { + return nil, err + } + if multipartContentType != "" { + req.Header.Set("Content-Type", multipartContentType) + } + req.Header.Set("Accept", "application/json") + httpResp, err := a.client.Do(req) + if err != nil { + return nil, err + } + defer httpResp.Body.Close() + + resp := &SendParameterizedMultipartResponse{StatusCode: httpResp.StatusCode, Raw: httpResp} + if httpResp.StatusCode >= 400 { + body, _ := io.ReadAll(httpResp.Body) + return nil, &runtime.APIError{StatusCode: httpResp.StatusCode, Status: httpResp.Status, Body: body} + } + return resp, nil +} + +// SendVendorJsonResponse carries the response from the corresponding operation. +type SendVendorJsonResponse struct { + StatusCode int + Raw *http.Response +} + +// SendVendorJson calls PATCH /request/vendor-json. +func (a *MediaAPI) SendVendorJson(ctx context.Context, body *models.Payload) (*SendVendorJsonResponse, error) { + path := "/request/vendor-json" + var bodyReader io.Reader + if body != nil { + buf, err := json.Marshal(body) + if err != nil { + return nil, fmt.Errorf("marshal body: %w", err) + } + bodyReader = bytes.NewReader(buf) + } + req, err := a.client.NewRequest(ctx, "PATCH", path, nil, bodyReader) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/vnd.example+json; charset=utf-8") + req.Header.Set("Accept", "application/json") + httpResp, err := a.client.Do(req) + if err != nil { + return nil, err + } + defer httpResp.Body.Close() + + resp := &SendVendorJsonResponse{StatusCode: httpResp.StatusCode, Raw: httpResp} + if httpResp.StatusCode >= 400 { + body, _ := io.ReadAll(httpResp.Body) + return nil, &runtime.APIError{StatusCode: httpResp.StatusCode, Status: httpResp.Status, Body: body} + } + return resp, nil +} + +// GetOctetPreferredResponse carries the response from the corresponding operation. +type GetOctetPreferredResponse struct { + StatusCode int + Raw *http.Response + Status200 []byte +} + +// GetOctetPreferred calls GET /response/octet-before-xml. +func (a *MediaAPI) GetOctetPreferred(ctx context.Context) (*GetOctetPreferredResponse, error) { + path := "/response/octet-before-xml" + req, err := a.client.NewRequest(ctx, "GET", path, nil, nil) + if err != nil { + return nil, err + } + req.Header.Set("Accept", "application/json") + httpResp, err := a.client.Do(req) + if err != nil { + return nil, err + } + defer httpResp.Body.Close() + + resp := &GetOctetPreferredResponse{StatusCode: httpResp.StatusCode, Raw: httpResp} + switch httpResp.StatusCode { + case 200: + payload, err := io.ReadAll(httpResp.Body) + if err != nil { + return nil, fmt.Errorf("read response: %w", err) + } + resp.Status200 = payload + default: + if httpResp.StatusCode >= 400 { + body, _ := io.ReadAll(httpResp.Body) + return nil, &runtime.APIError{StatusCode: httpResp.StatusCode, Status: httpResp.Status, Body: body} + } + } + return resp, nil +} + +// GetTextPreferredResponse carries the response from the corresponding operation. +type GetTextPreferredResponse struct { + StatusCode int + Raw *http.Response + Status200 *string +} + +// GetTextPreferred calls GET /response/text-before-xml. +func (a *MediaAPI) GetTextPreferred(ctx context.Context) (*GetTextPreferredResponse, error) { + path := "/response/text-before-xml" + req, err := a.client.NewRequest(ctx, "GET", path, nil, nil) + if err != nil { + return nil, err + } + req.Header.Set("Accept", "application/json") + httpResp, err := a.client.Do(req) + if err != nil { + return nil, err + } + defer httpResp.Body.Close() + + resp := &GetTextPreferredResponse{StatusCode: httpResp.StatusCode, Raw: httpResp} + switch httpResp.StatusCode { + case 200: + bodyBytes, err := io.ReadAll(httpResp.Body) + if err != nil { + return nil, fmt.Errorf("read response: %w", err) + } + payload := string(bodyBytes) + resp.Status200 = &payload + default: + if httpResp.StatusCode >= 400 { + body, _ := io.ReadAll(httpResp.Body) + return nil, &runtime.APIError{StatusCode: httpResp.StatusCode, Status: httpResp.Status, Body: body} + } + } + return resp, nil +} + +// GetVendorJsonResponse carries the response from the corresponding operation. +type GetVendorJsonResponse struct { + StatusCode int + Raw *http.Response + Status200 *models.Payload +} + +// GetVendorJson calls GET /response/vendor-json. +func (a *MediaAPI) GetVendorJson(ctx context.Context) (*GetVendorJsonResponse, error) { + path := "/response/vendor-json" + req, err := a.client.NewRequest(ctx, "GET", path, nil, nil) + if err != nil { + return nil, err + } + req.Header.Set("Accept", "application/json") + httpResp, err := a.client.Do(req) + if err != nil { + return nil, err + } + defer httpResp.Body.Close() + + resp := &GetVendorJsonResponse{StatusCode: httpResp.StatusCode, Raw: httpResp} + switch httpResp.StatusCode { + case 200: + var payload models.Payload + if err := json.NewDecoder(httpResp.Body).Decode(&payload); err != nil { + return nil, fmt.Errorf("decode response: %w", err) + } + resp.Status200 = &payload + default: + if httpResp.StatusCode >= 400 { + body, _ := io.ReadAll(httpResp.Body) + return nil, &runtime.APIError{StatusCode: httpResp.StatusCode, Status: httpResp.Status, Body: body} + } + } + return resp, nil +} diff --git a/tests/golden/go/go-http/media-type-selection/go.mod.golden b/tests/golden/go/go-http/media-type-selection/go.mod.golden new file mode 100644 index 000000000..54d0d116e --- /dev/null +++ b/tests/golden/go/go-http/media-type-selection/go.mod.golden @@ -0,0 +1,3 @@ +module example.com/sdk + +go 1.21 diff --git a/tests/golden/go/go-http/media-type-selection/models/file_envelope.go.golden b/tests/golden/go/go-http/media-type-selection/models/file_envelope.go.golden new file mode 100644 index 000000000..f0b911a2d --- /dev/null +++ b/tests/golden/go/go-http/media-type-selection/models/file_envelope.go.golden @@ -0,0 +1,11 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package models + +type FileEnvelope struct { + File []byte `json:"file"` + Note *string `json:"note,omitempty"` +} diff --git a/tests/golden/go/go-http/media-type-selection/models/payload.go.golden b/tests/golden/go/go-http/media-type-selection/models/payload.go.golden new file mode 100644 index 000000000..0650ed720 --- /dev/null +++ b/tests/golden/go/go-http/media-type-selection/models/payload.go.golden @@ -0,0 +1,11 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package models + +type Payload struct { + Count *int `json:"count,omitempty"` + Id string `json:"id"` +} diff --git a/tests/golden/go/go-http/media-type-selection/runtime/auth.go.golden b/tests/golden/go/go-http/media-type-selection/runtime/auth.go.golden new file mode 100644 index 000000000..0c1eb2062 --- /dev/null +++ b/tests/golden/go/go-http/media-type-selection/runtime/auth.go.golden @@ -0,0 +1,113 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package runtime + +import ( + "encoding/base64" + "fmt" + "net/http" +) + +// Authenticator attaches credentials to an outgoing request. +// +// Return a non-nil error to abort the request before it leaves the client. +type Authenticator interface { + AuthenticateRequest(req *http.Request) error +} + +// BearerAuth sends `Authorization: Bearer `. +// +// Use the Token field for a static token, or TokenProvider for a +// function that returns the current token (evaluated per-request). +// If both are set, TokenProvider takes precedence. +type BearerAuth struct { + Token string + TokenProvider func() string +} + +func (b BearerAuth) AuthenticateRequest(req *http.Request) error { + token := b.Token + if b.TokenProvider != nil { + token = b.TokenProvider() + } + if token == "" { + return fmt.Errorf("bearer auth: empty token") + } + req.Header.Set("Authorization", "Bearer "+token) + return nil +} + +// APIKeyLocation enumerates where an API key is placed on the wire. +type APIKeyLocation int + +const ( + APIKeyInHeader APIKeyLocation = iota + APIKeyInQuery + APIKeyInCookie +) + +// APIKeyAuth attaches a named API key to a request. +// +// Use the Key field for a static key, or KeyProvider for a function +// that returns the current key (evaluated per-request). If both are +// set, KeyProvider takes precedence. +type APIKeyAuth struct { + Key string + KeyProvider func() string + Name string + Location APIKeyLocation +} + +func (a APIKeyAuth) AuthenticateRequest(req *http.Request) error { + key := a.Key + if a.KeyProvider != nil { + key = a.KeyProvider() + } + if key == "" || a.Name == "" { + return fmt.Errorf("api key auth: empty key or name") + } + switch a.Location { + case APIKeyInHeader: + req.Header.Set(a.Name, key) + case APIKeyInQuery: + q := req.URL.Query() + q.Set(a.Name, key) + req.URL.RawQuery = q.Encode() + case APIKeyInCookie: + req.AddCookie(&http.Cookie{Name: a.Name, Value: key}) + default: + return fmt.Errorf("api key auth: unknown location %d", a.Location) + } + return nil +} + +// BasicAuth sends `Authorization: Basic `. +// +// Use the Username/Password fields for static credentials, or +// UsernameProvider/PasswordProvider for functions that return the +// current credentials (evaluated per-request). If a provider is set, +// it takes precedence over the corresponding static field. +type BasicAuth struct { + Username string + Password string + UsernameProvider func() string + PasswordProvider func() string +} + +func (b BasicAuth) AuthenticateRequest(req *http.Request) error { + user := b.Username + if b.UsernameProvider != nil { + user = b.UsernameProvider() + } + pass := b.Password + if b.PasswordProvider != nil { + pass = b.PasswordProvider() + } + creds := user + ":" + pass + encoded := base64.StdEncoding.EncodeToString([]byte(creds)) + req.Header.Set("Authorization", "Basic "+encoded) + return nil +} diff --git a/tests/golden/go/go-http/media-type-selection/runtime/client.go.golden b/tests/golden/go/go-http/media-type-selection/runtime/client.go.golden new file mode 100644 index 000000000..081642594 --- /dev/null +++ b/tests/golden/go/go-http/media-type-selection/runtime/client.go.golden @@ -0,0 +1,99 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package runtime + +import ( + "context" + "fmt" + "io" + "net/http" + "net/url" + "strings" +) + +// Client is the transport-layer HTTP client shared by all APIs. +type Client struct { + baseURL string + httpClient *http.Client + authenticator Authenticator + defaultHeaders http.Header +} + +// Option configures a Client. +type Option func(*Client) + +// NewClient builds a Client rooted at baseURL. Options override defaults. +func NewClient(baseURL string, opts ...Option) *Client { + c := &Client{ + baseURL: strings.TrimRight(baseURL, "/"), + httpClient: http.DefaultClient, + defaultHeaders: make(http.Header), + } + for _, opt := range opts { + opt(c) + } + return c +} + +// WithHTTPClient swaps the underlying *http.Client. +func WithHTTPClient(hc *http.Client) Option { + return func(c *Client) { + if hc != nil { + c.httpClient = hc + } + } +} + +// WithAuth installs an Authenticator applied to every request. +func WithAuth(a Authenticator) Option { + return func(c *Client) { c.authenticator = a } +} + +// WithDefaultHeader sets a header applied to every request. Per-request +// headers take precedence. +func WithDefaultHeader(key, value string) Option { + return func(c *Client) { c.defaultHeaders.Set(key, value) } +} + +// BaseURL returns the base URL configured on the client. +func (c *Client) BaseURL() string { return c.baseURL } + +// HTTPClient returns the underlying *http.Client. +func (c *Client) HTTPClient() *http.Client { return c.httpClient } + +// NewRequest builds an *http.Request joined to the client's base URL. Query +// pairs (flat key=value list) are appended when non-empty. +func (c *Client) NewRequest( + ctx context.Context, + method, path string, + query url.Values, + body io.Reader, +) (*http.Request, error) { + full := c.baseURL + path + if len(query) > 0 { + full += "?" + query.Encode() + } + req, err := http.NewRequestWithContext(ctx, method, full, body) + if err != nil { + return nil, fmt.Errorf("build request: %w", err) + } + for k, vals := range c.defaultHeaders { + for _, v := range vals { + req.Header.Add(k, v) + } + } + return req, nil +} + +// Do authenticates and executes a request. +func (c *Client) Do(req *http.Request) (*http.Response, error) { + if c.authenticator != nil { + if err := c.authenticator.AuthenticateRequest(req); err != nil { + return nil, fmt.Errorf("authenticate: %w", err) + } + } + return c.httpClient.Do(req) +} diff --git a/tests/golden/go/go-http/media-type-selection/runtime/errors.go.golden b/tests/golden/go/go-http/media-type-selection/runtime/errors.go.golden new file mode 100644 index 000000000..7a052bd4e --- /dev/null +++ b/tests/golden/go/go-http/media-type-selection/runtime/errors.go.golden @@ -0,0 +1,31 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package runtime + +import ( + "errors" + "fmt" +) + +// APIError represents a non-2xx HTTP response from the API. +// +// Callers can type-assert via `errors.As` to inspect StatusCode and the raw +// response body. +type APIError struct { + StatusCode int + Status string + Body []byte +} + +func (e *APIError) Error() string { + return fmt.Sprintf("api error: %s", e.Status) +} + +// IsAPIError reports whether err is (or wraps) an *APIError. +func IsAPIError(err error) bool { + var apiErr *APIError + return errors.As(err, &apiErr) +} diff --git a/tests/golden/go/go-http/multipart-edge-cases/README.md.golden b/tests/golden/go/go-http/multipart-edge-cases/README.md.golden new file mode 100644 index 000000000..61745a51d --- /dev/null +++ b/tests/golden/go/go-http/multipart-edge-cases/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Edge Cases + +Covers optional multipart bodies, optional parts, and text-only multipart fields. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart-edge-cases`. diff --git a/tests/golden/go/go-http/multipart-edge-cases/apis/multipart.go.golden b/tests/golden/go/go-http/multipart-edge-cases/apis/multipart.go.golden new file mode 100644 index 000000000..a6cb81a39 --- /dev/null +++ b/tests/golden/go/go-http/multipart-edge-cases/apis/multipart.go.golden @@ -0,0 +1,156 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package apis + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + "strconv" + + "example.com/sdk/models" + "example.com/sdk/runtime" +) + +// MultipartAPI groups operations under the corresponding tag. +type MultipartAPI struct { + client *runtime.Client +} + +// NewMultipartAPI constructs a MultipartAPI bound to client. +func NewMultipartAPI(client *runtime.Client) *MultipartAPI { + return &MultipartAPI{client: client} +} + +// SendOptionalPartsResponse carries the response from the corresponding operation. +type SendOptionalPartsResponse struct { + StatusCode int + Raw *http.Response +} + +// SendOptionalParts calls POST /multipart/optional. +func (a *MultipartAPI) SendOptionalParts(ctx context.Context, body *models.OptionalUpload) (*SendOptionalPartsResponse, error) { + path := "/multipart/optional" + var bodyReader io.Reader + var multipartContentType string + if body != nil { + buf := &bytes.Buffer{} + writer := multipart.NewWriter(buf) + if body.Attributes != nil { + partValue, err := json.Marshal(*body.Attributes) + if err != nil { + return nil, fmt.Errorf("marshal multipart field: %w", err) + } + if err := writer.WriteField("attributes", string(partValue)); err != nil { + return nil, fmt.Errorf("write multipart field: %w", err) + } + } + if body.Enabled != nil { + if err := writer.WriteField("enabled", strconv.FormatBool(*body.Enabled)); err != nil { + return nil, fmt.Errorf("write multipart field: %w", err) + } + } + if body.File != nil { + partWriter, err := writer.CreateFormFile("file", "file") + if err != nil { + return nil, fmt.Errorf("create multipart file: %w", err) + } + if _, err := partWriter.Write(*body.File); err != nil { + return nil, fmt.Errorf("write multipart file: %w", err) + } + } + if body.RetryCount != nil { + if err := writer.WriteField("retry_count", strconv.FormatInt(int64(*body.RetryCount), 10)); err != nil { + return nil, fmt.Errorf("write multipart field: %w", err) + } + } + if body.Title != nil { + if err := writer.WriteField("title", *body.Title); err != nil { + return nil, fmt.Errorf("write multipart field: %w", err) + } + } + if err := writer.Close(); err != nil { + return nil, fmt.Errorf("close multipart writer: %w", err) + } + bodyReader = buf + multipartContentType = writer.FormDataContentType() + } + req, err := a.client.NewRequest(ctx, "POST", path, nil, bodyReader) + if err != nil { + return nil, err + } + if multipartContentType != "" { + req.Header.Set("Content-Type", multipartContentType) + } + req.Header.Set("Accept", "application/json") + httpResp, err := a.client.Do(req) + if err != nil { + return nil, err + } + defer httpResp.Body.Close() + + resp := &SendOptionalPartsResponse{StatusCode: httpResp.StatusCode, Raw: httpResp} + if httpResp.StatusCode >= 400 { + body, _ := io.ReadAll(httpResp.Body) + return nil, &runtime.APIError{StatusCode: httpResp.StatusCode, Status: httpResp.Status, Body: body} + } + return resp, nil +} + +// SendTextFieldsResponse carries the response from the corresponding operation. +type SendTextFieldsResponse struct { + StatusCode int + Raw *http.Response +} + +// SendTextFields calls POST /multipart/text-only. +func (a *MultipartAPI) SendTextFields(ctx context.Context, body *models.TextFields) (*SendTextFieldsResponse, error) { + path := "/multipart/text-only" + var bodyReader io.Reader + var multipartContentType string + if body != nil { + buf := &bytes.Buffer{} + writer := multipart.NewWriter(buf) + if err := writer.WriteField("enabled", strconv.FormatBool(body.Enabled)); err != nil { + return nil, fmt.Errorf("write multipart field: %w", err) + } + if err := writer.WriteField("note", body.Note); err != nil { + return nil, fmt.Errorf("write multipart field: %w", err) + } + if err := writer.WriteField("retry_count", strconv.FormatInt(int64(body.RetryCount), 10)); err != nil { + return nil, fmt.Errorf("write multipart field: %w", err) + } + if err := writer.Close(); err != nil { + return nil, fmt.Errorf("close multipart writer: %w", err) + } + bodyReader = buf + multipartContentType = writer.FormDataContentType() + } + req, err := a.client.NewRequest(ctx, "POST", path, nil, bodyReader) + if err != nil { + return nil, err + } + if multipartContentType != "" { + req.Header.Set("Content-Type", multipartContentType) + } + req.Header.Set("Accept", "application/json") + httpResp, err := a.client.Do(req) + if err != nil { + return nil, err + } + defer httpResp.Body.Close() + + resp := &SendTextFieldsResponse{StatusCode: httpResp.StatusCode, Raw: httpResp} + if httpResp.StatusCode >= 400 { + body, _ := io.ReadAll(httpResp.Body) + return nil, &runtime.APIError{StatusCode: httpResp.StatusCode, Status: httpResp.Status, Body: body} + } + return resp, nil +} diff --git a/tests/golden/go/go-http/multipart-edge-cases/go.mod.golden b/tests/golden/go/go-http/multipart-edge-cases/go.mod.golden new file mode 100644 index 000000000..54d0d116e --- /dev/null +++ b/tests/golden/go/go-http/multipart-edge-cases/go.mod.golden @@ -0,0 +1,3 @@ +module example.com/sdk + +go 1.21 diff --git a/tests/golden/go/go-http/multipart-edge-cases/models/attributes.go.golden b/tests/golden/go/go-http/multipart-edge-cases/models/attributes.go.golden new file mode 100644 index 000000000..e27003429 --- /dev/null +++ b/tests/golden/go/go-http/multipart-edge-cases/models/attributes.go.golden @@ -0,0 +1,11 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package models + +type Attributes struct { + Label string `json:"label"` + Priority *int `json:"priority,omitempty"` +} diff --git a/tests/golden/go/go-http/multipart-edge-cases/models/optional_upload.go.golden b/tests/golden/go/go-http/multipart-edge-cases/models/optional_upload.go.golden new file mode 100644 index 000000000..16565539b --- /dev/null +++ b/tests/golden/go/go-http/multipart-edge-cases/models/optional_upload.go.golden @@ -0,0 +1,14 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package models + +type OptionalUpload struct { + Attributes *Attributes `json:"attributes,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + File *[]byte `json:"file,omitempty"` + RetryCount *int `json:"retry_count,omitempty"` + Title *string `json:"title,omitempty"` +} diff --git a/tests/golden/go/go-http/multipart-edge-cases/models/text_fields.go.golden b/tests/golden/go/go-http/multipart-edge-cases/models/text_fields.go.golden new file mode 100644 index 000000000..db4eaa159 --- /dev/null +++ b/tests/golden/go/go-http/multipart-edge-cases/models/text_fields.go.golden @@ -0,0 +1,12 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package models + +type TextFields struct { + Enabled bool `json:"enabled"` + Note string `json:"note"` + RetryCount int `json:"retry_count"` +} diff --git a/tests/golden/go/go-http/multipart-edge-cases/runtime/auth.go.golden b/tests/golden/go/go-http/multipart-edge-cases/runtime/auth.go.golden new file mode 100644 index 000000000..f742088fa --- /dev/null +++ b/tests/golden/go/go-http/multipart-edge-cases/runtime/auth.go.golden @@ -0,0 +1,113 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package runtime + +import ( + "encoding/base64" + "fmt" + "net/http" +) + +// Authenticator attaches credentials to an outgoing request. +// +// Return a non-nil error to abort the request before it leaves the client. +type Authenticator interface { + AuthenticateRequest(req *http.Request) error +} + +// BearerAuth sends `Authorization: Bearer `. +// +// Use the Token field for a static token, or TokenProvider for a +// function that returns the current token (evaluated per-request). +// If both are set, TokenProvider takes precedence. +type BearerAuth struct { + Token string + TokenProvider func() string +} + +func (b BearerAuth) AuthenticateRequest(req *http.Request) error { + token := b.Token + if b.TokenProvider != nil { + token = b.TokenProvider() + } + if token == "" { + return fmt.Errorf("bearer auth: empty token") + } + req.Header.Set("Authorization", "Bearer "+token) + return nil +} + +// APIKeyLocation enumerates where an API key is placed on the wire. +type APIKeyLocation int + +const ( + APIKeyInHeader APIKeyLocation = iota + APIKeyInQuery + APIKeyInCookie +) + +// APIKeyAuth attaches a named API key to a request. +// +// Use the Key field for a static key, or KeyProvider for a function +// that returns the current key (evaluated per-request). If both are +// set, KeyProvider takes precedence. +type APIKeyAuth struct { + Key string + KeyProvider func() string + Name string + Location APIKeyLocation +} + +func (a APIKeyAuth) AuthenticateRequest(req *http.Request) error { + key := a.Key + if a.KeyProvider != nil { + key = a.KeyProvider() + } + if key == "" || a.Name == "" { + return fmt.Errorf("api key auth: empty key or name") + } + switch a.Location { + case APIKeyInHeader: + req.Header.Set(a.Name, key) + case APIKeyInQuery: + q := req.URL.Query() + q.Set(a.Name, key) + req.URL.RawQuery = q.Encode() + case APIKeyInCookie: + req.AddCookie(&http.Cookie{Name: a.Name, Value: key}) + default: + return fmt.Errorf("api key auth: unknown location %d", a.Location) + } + return nil +} + +// BasicAuth sends `Authorization: Basic `. +// +// Use the Username/Password fields for static credentials, or +// UsernameProvider/PasswordProvider for functions that return the +// current credentials (evaluated per-request). If a provider is set, +// it takes precedence over the corresponding static field. +type BasicAuth struct { + Username string + Password string + UsernameProvider func() string + PasswordProvider func() string +} + +func (b BasicAuth) AuthenticateRequest(req *http.Request) error { + user := b.Username + if b.UsernameProvider != nil { + user = b.UsernameProvider() + } + pass := b.Password + if b.PasswordProvider != nil { + pass = b.PasswordProvider() + } + creds := user + ":" + pass + encoded := base64.StdEncoding.EncodeToString([]byte(creds)) + req.Header.Set("Authorization", "Basic "+encoded) + return nil +} diff --git a/tests/golden/go/go-http/multipart-edge-cases/runtime/client.go.golden b/tests/golden/go/go-http/multipart-edge-cases/runtime/client.go.golden new file mode 100644 index 000000000..7c835ba2f --- /dev/null +++ b/tests/golden/go/go-http/multipart-edge-cases/runtime/client.go.golden @@ -0,0 +1,99 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package runtime + +import ( + "context" + "fmt" + "io" + "net/http" + "net/url" + "strings" +) + +// Client is the transport-layer HTTP client shared by all APIs. +type Client struct { + baseURL string + httpClient *http.Client + authenticator Authenticator + defaultHeaders http.Header +} + +// Option configures a Client. +type Option func(*Client) + +// NewClient builds a Client rooted at baseURL. Options override defaults. +func NewClient(baseURL string, opts ...Option) *Client { + c := &Client{ + baseURL: strings.TrimRight(baseURL, "/"), + httpClient: http.DefaultClient, + defaultHeaders: make(http.Header), + } + for _, opt := range opts { + opt(c) + } + return c +} + +// WithHTTPClient swaps the underlying *http.Client. +func WithHTTPClient(hc *http.Client) Option { + return func(c *Client) { + if hc != nil { + c.httpClient = hc + } + } +} + +// WithAuth installs an Authenticator applied to every request. +func WithAuth(a Authenticator) Option { + return func(c *Client) { c.authenticator = a } +} + +// WithDefaultHeader sets a header applied to every request. Per-request +// headers take precedence. +func WithDefaultHeader(key, value string) Option { + return func(c *Client) { c.defaultHeaders.Set(key, value) } +} + +// BaseURL returns the base URL configured on the client. +func (c *Client) BaseURL() string { return c.baseURL } + +// HTTPClient returns the underlying *http.Client. +func (c *Client) HTTPClient() *http.Client { return c.httpClient } + +// NewRequest builds an *http.Request joined to the client's base URL. Query +// pairs (flat key=value list) are appended when non-empty. +func (c *Client) NewRequest( + ctx context.Context, + method, path string, + query url.Values, + body io.Reader, +) (*http.Request, error) { + full := c.baseURL + path + if len(query) > 0 { + full += "?" + query.Encode() + } + req, err := http.NewRequestWithContext(ctx, method, full, body) + if err != nil { + return nil, fmt.Errorf("build request: %w", err) + } + for k, vals := range c.defaultHeaders { + for _, v := range vals { + req.Header.Add(k, v) + } + } + return req, nil +} + +// Do authenticates and executes a request. +func (c *Client) Do(req *http.Request) (*http.Response, error) { + if c.authenticator != nil { + if err := c.authenticator.AuthenticateRequest(req); err != nil { + return nil, fmt.Errorf("authenticate: %w", err) + } + } + return c.httpClient.Do(req) +} diff --git a/tests/golden/go/go-http/multipart-edge-cases/runtime/errors.go.golden b/tests/golden/go/go-http/multipart-edge-cases/runtime/errors.go.golden new file mode 100644 index 000000000..19254b634 --- /dev/null +++ b/tests/golden/go/go-http/multipart-edge-cases/runtime/errors.go.golden @@ -0,0 +1,31 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package runtime + +import ( + "errors" + "fmt" +) + +// APIError represents a non-2xx HTTP response from the API. +// +// Callers can type-assert via `errors.As` to inspect StatusCode and the raw +// response body. +type APIError struct { + StatusCode int + Status string + Body []byte +} + +func (e *APIError) Error() string { + return fmt.Sprintf("api error: %s", e.Status) +} + +// IsAPIError reports whether err is (or wraps) an *APIError. +func IsAPIError(err error) bool { + var apiErr *APIError + return errors.As(err, &apiErr) +} diff --git a/tests/golden/go/go-http/multipart-nested-object-parts/README.md.golden b/tests/golden/go/go-http/multipart-nested-object-parts/README.md.golden new file mode 100644 index 000000000..2899b40d5 --- /dev/null +++ b/tests/golden/go/go-http/multipart-nested-object-parts/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Nested Object Parts + +Covers multipart object parts whose wire names differ from ergonomic names. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart-nested-object-parts`. diff --git a/tests/golden/go/go-http/multipart-nested-object-parts/apis/multipart.go.golden b/tests/golden/go/go-http/multipart-nested-object-parts/apis/multipart.go.golden new file mode 100644 index 000000000..9c61b44bd --- /dev/null +++ b/tests/golden/go/go-http/multipart-nested-object-parts/apis/multipart.go.golden @@ -0,0 +1,85 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package apis + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + + "example.com/sdk/models" + "example.com/sdk/runtime" +) + +// MultipartAPI groups operations under the corresponding tag. +type MultipartAPI struct { + client *runtime.Client +} + +// NewMultipartAPI constructs a MultipartAPI bound to client. +func NewMultipartAPI(client *runtime.Client) *MultipartAPI { + return &MultipartAPI{client: client} +} + +// SendNestedObjectPartResponse carries the response from the corresponding operation. +type SendNestedObjectPartResponse struct { + StatusCode int + Raw *http.Response +} + +// SendNestedObjectPart calls POST /multipart/nested-object. +func (a *MultipartAPI) SendNestedObjectPart(ctx context.Context, body *models.NestedUpload) (*SendNestedObjectPartResponse, error) { + path := "/multipart/nested-object" + var bodyReader io.Reader + var multipartContentType string + if body != nil { + buf := &bytes.Buffer{} + writer := multipart.NewWriter(buf) + partWriter, err := writer.CreateFormFile("file", "file") + if err != nil { + return nil, fmt.Errorf("create multipart file: %w", err) + } + if _, err := partWriter.Write(body.File); err != nil { + return nil, fmt.Errorf("write multipart file: %w", err) + } + partValue, err := json.Marshal(body.ItemConfig) + if err != nil { + return nil, fmt.Errorf("marshal multipart field: %w", err) + } + if err := writer.WriteField("item_config", string(partValue)); err != nil { + return nil, fmt.Errorf("write multipart field: %w", err) + } + if err := writer.Close(); err != nil { + return nil, fmt.Errorf("close multipart writer: %w", err) + } + bodyReader = buf + multipartContentType = writer.FormDataContentType() + } + req, err := a.client.NewRequest(ctx, "POST", path, nil, bodyReader) + if err != nil { + return nil, err + } + if multipartContentType != "" { + req.Header.Set("Content-Type", multipartContentType) + } + req.Header.Set("Accept", "application/json") + httpResp, err := a.client.Do(req) + if err != nil { + return nil, err + } + defer httpResp.Body.Close() + + resp := &SendNestedObjectPartResponse{StatusCode: httpResp.StatusCode, Raw: httpResp} + if httpResp.StatusCode >= 400 { + body, _ := io.ReadAll(httpResp.Body) + return nil, &runtime.APIError{StatusCode: httpResp.StatusCode, Status: httpResp.Status, Body: body} + } + return resp, nil +} diff --git a/tests/golden/go/go-http/multipart-nested-object-parts/go.mod.golden b/tests/golden/go/go-http/multipart-nested-object-parts/go.mod.golden new file mode 100644 index 000000000..54d0d116e --- /dev/null +++ b/tests/golden/go/go-http/multipart-nested-object-parts/go.mod.golden @@ -0,0 +1,3 @@ +module example.com/sdk + +go 1.21 diff --git a/tests/golden/go/go-http/multipart-nested-object-parts/models/item_config.go.golden b/tests/golden/go/go-http/multipart-nested-object-parts/models/item_config.go.golden new file mode 100644 index 000000000..aa9bea5f8 --- /dev/null +++ b/tests/golden/go/go-http/multipart-nested-object-parts/models/item_config.go.golden @@ -0,0 +1,11 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package models + +type ItemConfig struct { + DisplayName string `json:"display_name"` + RetentionDays *int `json:"retention_days,omitempty"` +} diff --git a/tests/golden/go/go-http/multipart-nested-object-parts/models/nested_upload.go.golden b/tests/golden/go/go-http/multipart-nested-object-parts/models/nested_upload.go.golden new file mode 100644 index 000000000..a267a159b --- /dev/null +++ b/tests/golden/go/go-http/multipart-nested-object-parts/models/nested_upload.go.golden @@ -0,0 +1,11 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package models + +type NestedUpload struct { + File []byte `json:"file"` + ItemConfig ItemConfig `json:"item_config"` +} diff --git a/tests/golden/go/go-http/multipart-nested-object-parts/runtime/auth.go.golden b/tests/golden/go/go-http/multipart-nested-object-parts/runtime/auth.go.golden new file mode 100644 index 000000000..c5298a1b8 --- /dev/null +++ b/tests/golden/go/go-http/multipart-nested-object-parts/runtime/auth.go.golden @@ -0,0 +1,113 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package runtime + +import ( + "encoding/base64" + "fmt" + "net/http" +) + +// Authenticator attaches credentials to an outgoing request. +// +// Return a non-nil error to abort the request before it leaves the client. +type Authenticator interface { + AuthenticateRequest(req *http.Request) error +} + +// BearerAuth sends `Authorization: Bearer `. +// +// Use the Token field for a static token, or TokenProvider for a +// function that returns the current token (evaluated per-request). +// If both are set, TokenProvider takes precedence. +type BearerAuth struct { + Token string + TokenProvider func() string +} + +func (b BearerAuth) AuthenticateRequest(req *http.Request) error { + token := b.Token + if b.TokenProvider != nil { + token = b.TokenProvider() + } + if token == "" { + return fmt.Errorf("bearer auth: empty token") + } + req.Header.Set("Authorization", "Bearer "+token) + return nil +} + +// APIKeyLocation enumerates where an API key is placed on the wire. +type APIKeyLocation int + +const ( + APIKeyInHeader APIKeyLocation = iota + APIKeyInQuery + APIKeyInCookie +) + +// APIKeyAuth attaches a named API key to a request. +// +// Use the Key field for a static key, or KeyProvider for a function +// that returns the current key (evaluated per-request). If both are +// set, KeyProvider takes precedence. +type APIKeyAuth struct { + Key string + KeyProvider func() string + Name string + Location APIKeyLocation +} + +func (a APIKeyAuth) AuthenticateRequest(req *http.Request) error { + key := a.Key + if a.KeyProvider != nil { + key = a.KeyProvider() + } + if key == "" || a.Name == "" { + return fmt.Errorf("api key auth: empty key or name") + } + switch a.Location { + case APIKeyInHeader: + req.Header.Set(a.Name, key) + case APIKeyInQuery: + q := req.URL.Query() + q.Set(a.Name, key) + req.URL.RawQuery = q.Encode() + case APIKeyInCookie: + req.AddCookie(&http.Cookie{Name: a.Name, Value: key}) + default: + return fmt.Errorf("api key auth: unknown location %d", a.Location) + } + return nil +} + +// BasicAuth sends `Authorization: Basic `. +// +// Use the Username/Password fields for static credentials, or +// UsernameProvider/PasswordProvider for functions that return the +// current credentials (evaluated per-request). If a provider is set, +// it takes precedence over the corresponding static field. +type BasicAuth struct { + Username string + Password string + UsernameProvider func() string + PasswordProvider func() string +} + +func (b BasicAuth) AuthenticateRequest(req *http.Request) error { + user := b.Username + if b.UsernameProvider != nil { + user = b.UsernameProvider() + } + pass := b.Password + if b.PasswordProvider != nil { + pass = b.PasswordProvider() + } + creds := user + ":" + pass + encoded := base64.StdEncoding.EncodeToString([]byte(creds)) + req.Header.Set("Authorization", "Basic "+encoded) + return nil +} diff --git a/tests/golden/go/go-http/multipart-nested-object-parts/runtime/client.go.golden b/tests/golden/go/go-http/multipart-nested-object-parts/runtime/client.go.golden new file mode 100644 index 000000000..d9f9472f7 --- /dev/null +++ b/tests/golden/go/go-http/multipart-nested-object-parts/runtime/client.go.golden @@ -0,0 +1,99 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package runtime + +import ( + "context" + "fmt" + "io" + "net/http" + "net/url" + "strings" +) + +// Client is the transport-layer HTTP client shared by all APIs. +type Client struct { + baseURL string + httpClient *http.Client + authenticator Authenticator + defaultHeaders http.Header +} + +// Option configures a Client. +type Option func(*Client) + +// NewClient builds a Client rooted at baseURL. Options override defaults. +func NewClient(baseURL string, opts ...Option) *Client { + c := &Client{ + baseURL: strings.TrimRight(baseURL, "/"), + httpClient: http.DefaultClient, + defaultHeaders: make(http.Header), + } + for _, opt := range opts { + opt(c) + } + return c +} + +// WithHTTPClient swaps the underlying *http.Client. +func WithHTTPClient(hc *http.Client) Option { + return func(c *Client) { + if hc != nil { + c.httpClient = hc + } + } +} + +// WithAuth installs an Authenticator applied to every request. +func WithAuth(a Authenticator) Option { + return func(c *Client) { c.authenticator = a } +} + +// WithDefaultHeader sets a header applied to every request. Per-request +// headers take precedence. +func WithDefaultHeader(key, value string) Option { + return func(c *Client) { c.defaultHeaders.Set(key, value) } +} + +// BaseURL returns the base URL configured on the client. +func (c *Client) BaseURL() string { return c.baseURL } + +// HTTPClient returns the underlying *http.Client. +func (c *Client) HTTPClient() *http.Client { return c.httpClient } + +// NewRequest builds an *http.Request joined to the client's base URL. Query +// pairs (flat key=value list) are appended when non-empty. +func (c *Client) NewRequest( + ctx context.Context, + method, path string, + query url.Values, + body io.Reader, +) (*http.Request, error) { + full := c.baseURL + path + if len(query) > 0 { + full += "?" + query.Encode() + } + req, err := http.NewRequestWithContext(ctx, method, full, body) + if err != nil { + return nil, fmt.Errorf("build request: %w", err) + } + for k, vals := range c.defaultHeaders { + for _, v := range vals { + req.Header.Add(k, v) + } + } + return req, nil +} + +// Do authenticates and executes a request. +func (c *Client) Do(req *http.Request) (*http.Response, error) { + if c.authenticator != nil { + if err := c.authenticator.AuthenticateRequest(req); err != nil { + return nil, fmt.Errorf("authenticate: %w", err) + } + } + return c.httpClient.Do(req) +} diff --git a/tests/golden/go/go-http/multipart-nested-object-parts/runtime/errors.go.golden b/tests/golden/go/go-http/multipart-nested-object-parts/runtime/errors.go.golden new file mode 100644 index 000000000..4e14e7a88 --- /dev/null +++ b/tests/golden/go/go-http/multipart-nested-object-parts/runtime/errors.go.golden @@ -0,0 +1,31 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package runtime + +import ( + "errors" + "fmt" +) + +// APIError represents a non-2xx HTTP response from the API. +// +// Callers can type-assert via `errors.As` to inspect StatusCode and the raw +// response body. +type APIError struct { + StatusCode int + Status string + Body []byte +} + +func (e *APIError) Error() string { + return fmt.Sprintf("api error: %s", e.Status) +} + +// IsAPIError reports whether err is (or wraps) an *APIError. +func IsAPIError(err error) bool { + var apiErr *APIError + return errors.As(err, &apiErr) +} diff --git a/tests/golden/go/go-http/multipart-unsupported-schema/README.md.golden b/tests/golden/go/go-http/multipart-unsupported-schema/README.md.golden new file mode 100644 index 000000000..60a3a2357 --- /dev/null +++ b/tests/golden/go/go-http/multipart-unsupported-schema/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Unsupported Schema + +Covers multipart request bodies that are not object-shaped. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart-unsupported-schema`. diff --git a/tests/golden/go/go-http/multipart-unsupported-schema/apis/transfer.go.golden b/tests/golden/go/go-http/multipart-unsupported-schema/apis/transfer.go.golden new file mode 100644 index 000000000..72c415441 --- /dev/null +++ b/tests/golden/go/go-http/multipart-unsupported-schema/apis/transfer.go.golden @@ -0,0 +1,59 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Unsupported Schema — 1.0.0 +// Covers multipart request bodies that are not object-shaped. + +package apis + +import ( + "context" + "fmt" + "io" + "net/http" + + "example.com/sdk/runtime" +) + +// TransferAPI groups operations under the corresponding tag. +type TransferAPI struct { + client *runtime.Client +} + +// NewTransferAPI constructs a TransferAPI bound to client. +func NewTransferAPI(client *runtime.Client) *TransferAPI { + return &TransferAPI{client: client} +} + +// UploadRawMultipartResponse carries the response from the corresponding operation. +type UploadRawMultipartResponse struct { + StatusCode int + Raw *http.Response +} + +// UploadRawMultipart calls POST /uploads/raw. +func (a *TransferAPI) UploadRawMultipart(ctx context.Context, body []byte) (*UploadRawMultipartResponse, error) { + path := "/uploads/raw" + var bodyReader io.Reader + var multipartContentType string + return nil, fmt.Errorf("unsupported multipart request body: schema must be object-shaped") + req, err := a.client.NewRequest(ctx, "POST", path, nil, bodyReader) + if err != nil { + return nil, err + } + if multipartContentType != "" { + req.Header.Set("Content-Type", multipartContentType) + } + req.Header.Set("Accept", "application/json") + httpResp, err := a.client.Do(req) + if err != nil { + return nil, err + } + defer httpResp.Body.Close() + + resp := &UploadRawMultipartResponse{StatusCode: httpResp.StatusCode, Raw: httpResp} + if httpResp.StatusCode >= 400 { + body, _ := io.ReadAll(httpResp.Body) + return nil, &runtime.APIError{StatusCode: httpResp.StatusCode, Status: httpResp.Status, Body: body} + } + return resp, nil +} diff --git a/tests/golden/go/go-http/multipart-unsupported-schema/go.mod.golden b/tests/golden/go/go-http/multipart-unsupported-schema/go.mod.golden new file mode 100644 index 000000000..54d0d116e --- /dev/null +++ b/tests/golden/go/go-http/multipart-unsupported-schema/go.mod.golden @@ -0,0 +1,3 @@ +module example.com/sdk + +go 1.21 diff --git a/tests/golden/go/go-http/multipart-unsupported-schema/runtime/auth.go.golden b/tests/golden/go/go-http/multipart-unsupported-schema/runtime/auth.go.golden new file mode 100644 index 000000000..e9af80e29 --- /dev/null +++ b/tests/golden/go/go-http/multipart-unsupported-schema/runtime/auth.go.golden @@ -0,0 +1,113 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Unsupported Schema — 1.0.0 +// Covers multipart request bodies that are not object-shaped. + +package runtime + +import ( + "encoding/base64" + "fmt" + "net/http" +) + +// Authenticator attaches credentials to an outgoing request. +// +// Return a non-nil error to abort the request before it leaves the client. +type Authenticator interface { + AuthenticateRequest(req *http.Request) error +} + +// BearerAuth sends `Authorization: Bearer `. +// +// Use the Token field for a static token, or TokenProvider for a +// function that returns the current token (evaluated per-request). +// If both are set, TokenProvider takes precedence. +type BearerAuth struct { + Token string + TokenProvider func() string +} + +func (b BearerAuth) AuthenticateRequest(req *http.Request) error { + token := b.Token + if b.TokenProvider != nil { + token = b.TokenProvider() + } + if token == "" { + return fmt.Errorf("bearer auth: empty token") + } + req.Header.Set("Authorization", "Bearer "+token) + return nil +} + +// APIKeyLocation enumerates where an API key is placed on the wire. +type APIKeyLocation int + +const ( + APIKeyInHeader APIKeyLocation = iota + APIKeyInQuery + APIKeyInCookie +) + +// APIKeyAuth attaches a named API key to a request. +// +// Use the Key field for a static key, or KeyProvider for a function +// that returns the current key (evaluated per-request). If both are +// set, KeyProvider takes precedence. +type APIKeyAuth struct { + Key string + KeyProvider func() string + Name string + Location APIKeyLocation +} + +func (a APIKeyAuth) AuthenticateRequest(req *http.Request) error { + key := a.Key + if a.KeyProvider != nil { + key = a.KeyProvider() + } + if key == "" || a.Name == "" { + return fmt.Errorf("api key auth: empty key or name") + } + switch a.Location { + case APIKeyInHeader: + req.Header.Set(a.Name, key) + case APIKeyInQuery: + q := req.URL.Query() + q.Set(a.Name, key) + req.URL.RawQuery = q.Encode() + case APIKeyInCookie: + req.AddCookie(&http.Cookie{Name: a.Name, Value: key}) + default: + return fmt.Errorf("api key auth: unknown location %d", a.Location) + } + return nil +} + +// BasicAuth sends `Authorization: Basic `. +// +// Use the Username/Password fields for static credentials, or +// UsernameProvider/PasswordProvider for functions that return the +// current credentials (evaluated per-request). If a provider is set, +// it takes precedence over the corresponding static field. +type BasicAuth struct { + Username string + Password string + UsernameProvider func() string + PasswordProvider func() string +} + +func (b BasicAuth) AuthenticateRequest(req *http.Request) error { + user := b.Username + if b.UsernameProvider != nil { + user = b.UsernameProvider() + } + pass := b.Password + if b.PasswordProvider != nil { + pass = b.PasswordProvider() + } + creds := user + ":" + pass + encoded := base64.StdEncoding.EncodeToString([]byte(creds)) + req.Header.Set("Authorization", "Basic "+encoded) + return nil +} diff --git a/tests/golden/go/go-http/multipart-unsupported-schema/runtime/client.go.golden b/tests/golden/go/go-http/multipart-unsupported-schema/runtime/client.go.golden new file mode 100644 index 000000000..eaad62193 --- /dev/null +++ b/tests/golden/go/go-http/multipart-unsupported-schema/runtime/client.go.golden @@ -0,0 +1,99 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Unsupported Schema — 1.0.0 +// Covers multipart request bodies that are not object-shaped. + +package runtime + +import ( + "context" + "fmt" + "io" + "net/http" + "net/url" + "strings" +) + +// Client is the transport-layer HTTP client shared by all APIs. +type Client struct { + baseURL string + httpClient *http.Client + authenticator Authenticator + defaultHeaders http.Header +} + +// Option configures a Client. +type Option func(*Client) + +// NewClient builds a Client rooted at baseURL. Options override defaults. +func NewClient(baseURL string, opts ...Option) *Client { + c := &Client{ + baseURL: strings.TrimRight(baseURL, "/"), + httpClient: http.DefaultClient, + defaultHeaders: make(http.Header), + } + for _, opt := range opts { + opt(c) + } + return c +} + +// WithHTTPClient swaps the underlying *http.Client. +func WithHTTPClient(hc *http.Client) Option { + return func(c *Client) { + if hc != nil { + c.httpClient = hc + } + } +} + +// WithAuth installs an Authenticator applied to every request. +func WithAuth(a Authenticator) Option { + return func(c *Client) { c.authenticator = a } +} + +// WithDefaultHeader sets a header applied to every request. Per-request +// headers take precedence. +func WithDefaultHeader(key, value string) Option { + return func(c *Client) { c.defaultHeaders.Set(key, value) } +} + +// BaseURL returns the base URL configured on the client. +func (c *Client) BaseURL() string { return c.baseURL } + +// HTTPClient returns the underlying *http.Client. +func (c *Client) HTTPClient() *http.Client { return c.httpClient } + +// NewRequest builds an *http.Request joined to the client's base URL. Query +// pairs (flat key=value list) are appended when non-empty. +func (c *Client) NewRequest( + ctx context.Context, + method, path string, + query url.Values, + body io.Reader, +) (*http.Request, error) { + full := c.baseURL + path + if len(query) > 0 { + full += "?" + query.Encode() + } + req, err := http.NewRequestWithContext(ctx, method, full, body) + if err != nil { + return nil, fmt.Errorf("build request: %w", err) + } + for k, vals := range c.defaultHeaders { + for _, v := range vals { + req.Header.Add(k, v) + } + } + return req, nil +} + +// Do authenticates and executes a request. +func (c *Client) Do(req *http.Request) (*http.Response, error) { + if c.authenticator != nil { + if err := c.authenticator.AuthenticateRequest(req); err != nil { + return nil, fmt.Errorf("authenticate: %w", err) + } + } + return c.httpClient.Do(req) +} diff --git a/tests/golden/go/go-http/multipart-unsupported-schema/runtime/errors.go.golden b/tests/golden/go/go-http/multipart-unsupported-schema/runtime/errors.go.golden new file mode 100644 index 000000000..cfd000dfb --- /dev/null +++ b/tests/golden/go/go-http/multipart-unsupported-schema/runtime/errors.go.golden @@ -0,0 +1,31 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Unsupported Schema — 1.0.0 +// Covers multipart request bodies that are not object-shaped. + +package runtime + +import ( + "errors" + "fmt" +) + +// APIError represents a non-2xx HTTP response from the API. +// +// Callers can type-assert via `errors.As` to inspect StatusCode and the raw +// response body. +type APIError struct { + StatusCode int + Status string + Body []byte +} + +func (e *APIError) Error() string { + return fmt.Sprintf("api error: %s", e.Status) +} + +// IsAPIError reports whether err is (or wraps) an *APIError. +func IsAPIError(err error) bool { + var apiErr *APIError + return errors.As(err, &apiErr) +} diff --git a/tests/golden/go/go-http/request-body-content-types/apis/default.go.golden b/tests/golden/go/go-http/request-body-content-types/apis/default.go.golden index 84b39d329..25a051487 100644 --- a/tests/golden/go/go-http/request-body-content-types/apis/default.go.golden +++ b/tests/golden/go/go-http/request-body-content-types/apis/default.go.golden @@ -12,6 +12,7 @@ import ( "fmt" "io" "net/http" + "strings" "example.com/sdk/models" "example.com/sdk/runtime" @@ -38,17 +39,13 @@ func (a *DefaultAPI) PostBinary(ctx context.Context, body []byte) (*PostBinaryRe path := "/binary" var bodyReader io.Reader if body != nil { - buf, err := json.Marshal(body) - if err != nil { - return nil, fmt.Errorf("marshal body: %w", err) - } - bodyReader = bytes.NewReader(buf) + bodyReader = bytes.NewReader(body) } req, err := a.client.NewRequest(ctx, "POST", path, nil, bodyReader) if err != nil { return nil, err } - req.Header.Set("Content-Type", "application/json") + req.Header.Set("Content-Type", "application/octet-stream") req.Header.Set("Accept", "application/json") httpResp, err := a.client.Do(req) if err != nil { @@ -74,18 +71,12 @@ type PostFormResponse struct { func (a *DefaultAPI) PostForm(ctx context.Context, body *models.Payload) (*PostFormResponse, error) { path := "/form" var bodyReader io.Reader - if body != nil { - buf, err := json.Marshal(body) - if err != nil { - return nil, fmt.Errorf("marshal body: %w", err) - } - bodyReader = bytes.NewReader(buf) - } + return nil, fmt.Errorf("unsupported request body media type: application/x-www-form-urlencoded") req, err := a.client.NewRequest(ctx, "POST", path, nil, bodyReader) if err != nil { return nil, err } - req.Header.Set("Content-Type", "application/json") + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Accept", "application/json") httpResp, err := a.client.Do(req) if err != nil { @@ -198,7 +189,7 @@ func (a *DefaultAPI) PatchMergeJson(ctx context.Context, body *models.Payload) ( if err != nil { return nil, err } - req.Header.Set("Content-Type", "application/json") + req.Header.Set("Content-Type", "application/merge-patch+json") req.Header.Set("Accept", "application/json") httpResp, err := a.client.Do(req) if err != nil { @@ -225,17 +216,13 @@ func (a *DefaultAPI) PostText(ctx context.Context, body *string) (*PostTextRespo path := "/text" var bodyReader io.Reader if body != nil { - buf, err := json.Marshal(body) - if err != nil { - return nil, fmt.Errorf("marshal body: %w", err) - } - bodyReader = bytes.NewReader(buf) + bodyReader = strings.NewReader(*body) } req, err := a.client.NewRequest(ctx, "POST", path, nil, bodyReader) if err != nil { return nil, err } - req.Header.Set("Content-Type", "application/json") + req.Header.Set("Content-Type", "text/plain") req.Header.Set("Accept", "application/json") httpResp, err := a.client.Do(req) if err != nil { @@ -261,18 +248,12 @@ type PostXmlResponse struct { func (a *DefaultAPI) PostXml(ctx context.Context, body *models.Payload) (*PostXmlResponse, error) { path := "/xml" var bodyReader io.Reader - if body != nil { - buf, err := json.Marshal(body) - if err != nil { - return nil, fmt.Errorf("marshal body: %w", err) - } - bodyReader = bytes.NewReader(buf) - } + return nil, fmt.Errorf("unsupported request body media type: application/xml") req, err := a.client.NewRequest(ctx, "POST", path, nil, bodyReader) if err != nil { return nil, err } - req.Header.Set("Content-Type", "application/json") + req.Header.Set("Content-Type", "application/xml") req.Header.Set("Accept", "application/json") httpResp, err := a.client.Do(req) if err != nil { @@ -292,7 +273,7 @@ func (a *DefaultAPI) PostXml(ctx context.Context, body *models.Payload) (*PostXm type GetXmlResponseResponse struct { StatusCode int Raw *http.Response - Status200 *models.Payload + Status200 *string } // GetXmlResponse calls GET /xml-response. @@ -312,10 +293,11 @@ func (a *DefaultAPI) GetXmlResponse(ctx context.Context) (*GetXmlResponseRespons resp := &GetXmlResponseResponse{StatusCode: httpResp.StatusCode, Raw: httpResp} switch httpResp.StatusCode { case 200: - var payload models.Payload - if err := json.NewDecoder(httpResp.Body).Decode(&payload); err != nil { - return nil, fmt.Errorf("decode response: %w", err) + bodyBytes, err := io.ReadAll(httpResp.Body) + if err != nil { + return nil, fmt.Errorf("read response: %w", err) } + payload := string(bodyBytes) resp.Status200 = &payload default: if httpResp.StatusCode >= 400 { diff --git a/tests/golden/java/java-okhttp/additional-properties/apis/AdditionalPropertiesApi.java.golden b/tests/golden/java/java-okhttp/additional-properties/apis/AdditionalPropertiesApi.java.golden index ef53016f1..15ff7004d 100644 --- a/tests/golden/java/java-okhttp/additional-properties/apis/AdditionalPropertiesApi.java.golden +++ b/tests/golden/java/java-okhttp/additional-properties/apis/AdditionalPropertiesApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -116,18 +119,21 @@ public class AdditionalPropertiesApi { */ public PostLeafResponse postLeaf(LeafValue body) throws IOException { String path = "/leaf"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); LeafValue status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new PostLeafResponse(response.code(), response, status200); } @@ -137,18 +143,21 @@ public class AdditionalPropertiesApi { */ public PostMiddleResponse postMiddle(MiddleLevel body) throws IOException { String path = "/middle"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); MiddleLevel status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new PostMiddleResponse(response.code(), response, status200); } @@ -158,18 +167,21 @@ public class AdditionalPropertiesApi { */ public PostRootResponse postRoot(RootLevel body) throws IOException { String path = "/root"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); RootLevel status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new PostRootResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/additional-properties/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/additional-properties/runtime/ApiClient.java.golden index 71e50290d..20dc35769 100644 --- a/tests/golden/java/java-okhttp/additional-properties/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/additional-properties/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/binary-transfer-media-types/apis/TransferApi.java.golden b/tests/golden/java/java-okhttp/binary-transfer-media-types/apis/TransferApi.java.golden index 734452a2c..2d0eea4b6 100644 --- a/tests/golden/java/java-okhttp/binary-transfer-media-types/apis/TransferApi.java.golden +++ b/tests/golden/java/java-okhttp/binary-transfer-media-types/apis/TransferApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,10 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; +import okhttp3.MultipartBody; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -25,9 +29,9 @@ import okhttp3.Response; public class DownloadAssetResponse { private final int statusCode; private final Response raw; - private final List status200; + private final byte[] status200; - public DownloadAssetResponse(int statusCode, Response raw, List status200) { + public DownloadAssetResponse(int statusCode, Response raw, byte[] status200) { this.statusCode = statusCode; this.raw = raw; this.status200 = status200; @@ -41,7 +45,7 @@ public class DownloadAssetResponse { return this.raw; } - public List getStatus200() { + public byte[] getStatus200() { return this.status200; } } @@ -96,10 +100,11 @@ public class TransferApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; - List status200 = null; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); + byte[] status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken>() {}.getType()); + status200 = responseBytes; } return new DownloadAssetResponse(response.code(), response, status200); } @@ -109,18 +114,24 @@ public class TransferApi { */ public UploadAssetResponse uploadAsset(UploadAssetRequest body) throws IOException { String path = "/uploads"; - String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + Request request; + MultipartBody.Builder multipartBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM); + multipartBuilder.addFormDataPart("file", "file", RequestBody.create(body.getFile(), MediaType.get("application/octet-stream"))); + multipartBuilder.addFormDataPart("metadata", gson.toJson(body.getMetadata())); + multipartBuilder.addFormDataPart("purpose", String.valueOf(body.getPurpose())); + RequestBody multipartBody = multipartBuilder.build(); + request = client.newRequestWithBody("POST", path, null, multipartBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); UploadMetadata status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new UploadAssetResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/binary-transfer-media-types/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/binary-transfer-media-types/runtime/ApiClient.java.golden index e335931f6..a9ca05b83 100644 --- a/tests/golden/java/java-okhttp/binary-transfer-media-types/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/binary-transfer-media-types/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/comprehensive-schemas/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/comprehensive-schemas/apis/DefaultApi.java.golden index 472404446..704670448 100644 --- a/tests/golden/java/java-okhttp/comprehensive-schemas/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/comprehensive-schemas/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/tests/golden/java/java-okhttp/comprehensive-schemas/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/comprehensive-schemas/runtime/ApiClient.java.golden index 06aa32eab..3bc5681a0 100644 --- a/tests/golden/java/java-okhttp/comprehensive-schemas/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/comprehensive-schemas/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/delete-with-response-schema/apis/TestResourceApi.java.golden b/tests/golden/java/java-okhttp/delete-with-response-schema/apis/TestResourceApi.java.golden index 7d8de5720..4aced6f47 100644 --- a/tests/golden/java/java-okhttp/delete-with-response-schema/apis/TestResourceApi.java.golden +++ b/tests/golden/java/java-okhttp/delete-with-response-schema/apis/TestResourceApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -104,18 +107,21 @@ public class TestResourceApi { */ public CreateTestResourceResponse createTestResource(CreateTestResourceRequest body) throws IOException { String path = "/v1/api/test-resource"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); CreateTestResourceResponse status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new CreateTestResourceResponse(response.code(), response, status200); } @@ -132,18 +138,19 @@ public class TestResourceApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); DeleteTestResourceResponse status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } HttpErrorResponse status4xx = null; if (response.code() >= 400 && response.code() < 500) { - status4xx = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status4xx = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } HttpErrorResponse status5xx = null; if (response.code() >= 500 && response.code() < 600) { - status5xx = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status5xx = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new DeleteTestResourceResponse(response.code(), response, status200, status4xx, status5xx); } diff --git a/tests/golden/java/java-okhttp/delete-with-response-schema/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/delete-with-response-schema/runtime/ApiClient.java.golden index b233131de..0ffa50531 100644 --- a/tests/golden/java/java-okhttp/delete-with-response-schema/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/delete-with-response-schema/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/duplicate-param-names/apis/ItemsApi.java.golden b/tests/golden/java/java-okhttp/duplicate-param-names/apis/ItemsApi.java.golden index 6ba2b93b6..21ddf06a8 100644 --- a/tests/golden/java/java-okhttp/duplicate-param-names/apis/ItemsApi.java.golden +++ b/tests/golden/java/java-okhttp/duplicate-param-names/apis/ItemsApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -68,18 +71,21 @@ public class ItemsApi { if (body != null) { query.put("body", body); } + Request request; String jsonBody = gson.toJson(body2); - Request request = client.newRequest("POST", path, query, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, query, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); CreateItemWithBodyConflictResponse200 status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new CreateItemWithBodyConflictResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/duplicate-param-names/apis/UsersApi.java.golden b/tests/golden/java/java-okhttp/duplicate-param-names/apis/UsersApi.java.golden index 7f28cb2dc..2b886a220 100644 --- a/tests/golden/java/java-okhttp/duplicate-param-names/apis/UsersApi.java.golden +++ b/tests/golden/java/java-okhttp/duplicate-param-names/apis/UsersApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -77,10 +78,11 @@ public class UsersApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); GetUserByIdDuplicateResponse200 status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new GetUserByIdDuplicateResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/duplicate-param-names/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/duplicate-param-names/runtime/ApiClient.java.golden index f69f3021a..6365f1f99 100644 --- a/tests/golden/java/java-okhttp/duplicate-param-names/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/duplicate-param-names/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/enum-repr/apis/EnumReprApi.java.golden b/tests/golden/java/java-okhttp/enum-repr/apis/EnumReprApi.java.golden index c30b74e31..36e445766 100644 --- a/tests/golden/java/java-okhttp/enum-repr/apis/EnumReprApi.java.golden +++ b/tests/golden/java/java-okhttp/enum-repr/apis/EnumReprApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -173,18 +176,21 @@ public class EnumReprApi { */ public HandleAdjacentlyTaggedResponse handleAdjacentlyTagged(AdjacentlyTaggedEnum body) throws IOException { String path = "/adjacently-tagged"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); AdjacentlyTaggedEnum status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new HandleAdjacentlyTaggedResponse(response.code(), response, status200); } @@ -194,18 +200,21 @@ public class EnumReprApi { */ public HandleExternallyTaggedResponse handleExternallyTagged(ExternallyTaggedEnum body) throws IOException { String path = "/externally-tagged"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); ExternallyTaggedEnum status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new HandleExternallyTaggedResponse(response.code(), response, status200); } @@ -215,18 +224,21 @@ public class EnumReprApi { */ public HandleInternallyTaggedResponse handleInternallyTagged(InternallyTaggedEnum body) throws IOException { String path = "/internally-tagged"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); InternallyTaggedEnum status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new HandleInternallyTaggedResponse(response.code(), response, status200); } @@ -236,18 +248,21 @@ public class EnumReprApi { */ public HandleMixedResponse handleMixed(MixedEnum body) throws IOException { String path = "/mixed"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); MixedEnum status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new HandleMixedResponse(response.code(), response, status200); } @@ -257,18 +272,21 @@ public class EnumReprApi { */ public HandleUntaggedResponse handleUntagged(UntaggedEnum body) throws IOException { String path = "/untagged"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); UntaggedEnum status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new HandleUntaggedResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/enum-repr/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/enum-repr/runtime/ApiClient.java.golden index 3157c4ab0..4ba876452 100644 --- a/tests/golden/java/java-okhttp/enum-repr/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/enum-repr/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/interface-with-enum-reference/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/interface-with-enum-reference/runtime/ApiClient.java.golden index 01b488983..ab2e4105b 100644 --- a/tests/golden/java/java-okhttp/interface-with-enum-reference/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/interface-with-enum-reference/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/media-type-selection/README.md.golden b/tests/golden/java/java-okhttp/media-type-selection/README.md.golden new file mode 100644 index 000000000..7a446d9d1 --- /dev/null +++ b/tests/golden/java/java-okhttp/media-type-selection/README.md.golden @@ -0,0 +1,7 @@ +# Media Type Selection + +Covers normalized media-type selection for requests and responses. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `media-type-selection`. diff --git a/tests/golden/java/java-okhttp/media-type-selection/apis/MediaApi.java.golden b/tests/golden/java/java-okhttp/media-type-selection/apis/MediaApi.java.golden new file mode 100644 index 000000000..9cfd3b2b5 --- /dev/null +++ b/tests/golden/java/java-okhttp/media-type-selection/apis/MediaApi.java.golden @@ -0,0 +1,301 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package com.example.sdk.apis; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.example.sdk.models.*; +import com.example.sdk.runtime.ApiClient; +import com.example.sdk.runtime.ApiException; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +/** + * SendJsonPreferredResponse carries the response from sendJsonPreferred. + */ +public class SendJsonPreferredResponse { + private final int statusCode; + private final Response raw; + + public SendJsonPreferredResponse(int statusCode, Response raw) { + this.statusCode = statusCode; + this.raw = raw; + } + + public int getStatusCode() { + return this.statusCode; + } + + public Response getRaw() { + return this.raw; + } +} + +/** + * SendParameterizedMultipartResponse carries the response from sendParameterizedMultipart. + */ +public class SendParameterizedMultipartResponse { + private final int statusCode; + private final Response raw; + + public SendParameterizedMultipartResponse(int statusCode, Response raw) { + this.statusCode = statusCode; + this.raw = raw; + } + + public int getStatusCode() { + return this.statusCode; + } + + public Response getRaw() { + return this.raw; + } +} + +/** + * SendVendorJsonResponse carries the response from sendVendorJson. + */ +public class SendVendorJsonResponse { + private final int statusCode; + private final Response raw; + + public SendVendorJsonResponse(int statusCode, Response raw) { + this.statusCode = statusCode; + this.raw = raw; + } + + public int getStatusCode() { + return this.statusCode; + } + + public Response getRaw() { + return this.raw; + } +} + +/** + * GetOctetPreferredResponse carries the response from getOctetPreferred. + */ +public class GetOctetPreferredResponse { + private final int statusCode; + private final Response raw; + private final byte[] status200; + + public GetOctetPreferredResponse(int statusCode, Response raw, byte[] status200) { + this.statusCode = statusCode; + this.raw = raw; + this.status200 = status200; + } + + public int getStatusCode() { + return this.statusCode; + } + + public Response getRaw() { + return this.raw; + } + + public byte[] getStatus200() { + return this.status200; + } +} + +/** + * GetTextPreferredResponse carries the response from getTextPreferred. + */ +public class GetTextPreferredResponse { + private final int statusCode; + private final Response raw; + private final String status200; + + public GetTextPreferredResponse(int statusCode, Response raw, String status200) { + this.statusCode = statusCode; + this.raw = raw; + this.status200 = status200; + } + + public int getStatusCode() { + return this.statusCode; + } + + public Response getRaw() { + return this.raw; + } + + public String getStatus200() { + return this.status200; + } +} + +/** + * GetVendorJsonResponse carries the response from getVendorJson. + */ +public class GetVendorJsonResponse { + private final int statusCode; + private final Response raw; + private final Payload status200; + + public GetVendorJsonResponse(int statusCode, Response raw, Payload status200) { + this.statusCode = statusCode; + this.raw = raw; + this.status200 = status200; + } + + public int getStatusCode() { + return this.statusCode; + } + + public Response getRaw() { + return this.raw; + } + + public Payload getStatus200() { + return this.status200; + } +} + +/** + * MediaApi groups operations under the media tag. + */ +public class MediaApi { + private final ApiClient client; + private final Gson gson = new Gson(); + + public MediaApi(ApiClient client) { + this.client = client; + } + + /** + * sendJsonPreferred POST /request/json-vs-multipart. + */ + public SendJsonPreferredResponse sendJsonPreferred(Payload body) throws IOException { + String path = "/request/json-vs-multipart"; + Request request; + String jsonBody = gson.toJson(body); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); + Response response = client.execute(request); + + if (!response.isSuccessful()) { + String errorBody = response.body() != null ? response.body().string() : ""; + throw new ApiException(response.code(), response.message(), errorBody); + } + return new SendJsonPreferredResponse(response.code(), response); + } + + /** + * sendParameterizedMultipart POST /request/parameterized-multipart. + */ + public SendParameterizedMultipartResponse sendParameterizedMultipart(FileEnvelope body) throws IOException { + String path = "/request/parameterized-multipart"; + Request request; + MultipartBody.Builder multipartBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM); + multipartBuilder.addFormDataPart("file", "file", RequestBody.create(body.getFile(), MediaType.get("application/octet-stream"))); + if (body.getNote() != null) { + multipartBuilder.addFormDataPart("note", String.valueOf(body.getNote())); + } + RequestBody multipartBody = multipartBuilder.build(); + request = client.newRequestWithBody("POST", path, null, multipartBody); + Response response = client.execute(request); + + if (!response.isSuccessful()) { + String errorBody = response.body() != null ? response.body().string() : ""; + throw new ApiException(response.code(), response.message(), errorBody); + } + return new SendParameterizedMultipartResponse(response.code(), response); + } + + /** + * sendVendorJson PATCH /request/vendor-json. + */ + public SendVendorJsonResponse sendVendorJson(Payload body) throws IOException { + String path = "/request/vendor-json"; + Request request; + String jsonBody = gson.toJson(body); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/vnd.example+json; charset=utf-8")); + request = client.newRequestWithBody("PATCH", path, null, requestBody); + Response response = client.execute(request); + + if (!response.isSuccessful()) { + String errorBody = response.body() != null ? response.body().string() : ""; + throw new ApiException(response.code(), response.message(), errorBody); + } + return new SendVendorJsonResponse(response.code(), response); + } + + /** + * getOctetPreferred GET /response/octet-before-xml. + */ + public GetOctetPreferredResponse getOctetPreferred() throws IOException { + String path = "/response/octet-before-xml"; + Request request = client.newRequest("GET", path, null, null); + Response response = client.execute(request); + + if (!response.isSuccessful()) { + String errorBody = response.body() != null ? response.body().string() : ""; + throw new ApiException(response.code(), response.message(), errorBody); + } + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); + byte[] status200 = null; + if (response.code() == 200) { + status200 = responseBytes; + } + return new GetOctetPreferredResponse(response.code(), response, status200); + } + + /** + * getTextPreferred GET /response/text-before-xml. + */ + public GetTextPreferredResponse getTextPreferred() throws IOException { + String path = "/response/text-before-xml"; + Request request = client.newRequest("GET", path, null, null); + Response response = client.execute(request); + + if (!response.isSuccessful()) { + String errorBody = response.body() != null ? response.body().string() : ""; + throw new ApiException(response.code(), response.message(), errorBody); + } + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); + String status200 = null; + if (response.code() == 200) { + status200 = responseText; + } + return new GetTextPreferredResponse(response.code(), response, status200); + } + + /** + * getVendorJson GET /response/vendor-json. + */ + public GetVendorJsonResponse getVendorJson() throws IOException { + String path = "/response/vendor-json"; + Request request = client.newRequest("GET", path, null, null); + Response response = client.execute(request); + + if (!response.isSuccessful()) { + String errorBody = response.body() != null ? response.body().string() : ""; + throw new ApiException(response.code(), response.message(), errorBody); + } + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); + Payload status200 = null; + if (response.code() == 200) { + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); + } + return new GetVendorJsonResponse(response.code(), response, status200); + } +} diff --git a/tests/golden/java/java-okhttp/media-type-selection/build.gradle.golden b/tests/golden/java/java-okhttp/media-type-selection/build.gradle.golden new file mode 100644 index 000000000..ec7d84dad --- /dev/null +++ b/tests/golden/java/java-okhttp/media-type-selection/build.gradle.golden @@ -0,0 +1,21 @@ +plugins { + id("java-library") +} + +group = "com.example.sdk" +version = "1.0.0" + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.squareup.okhttp3:okhttp:4.12.0") + implementation("com.google.code.gson:gson:2.11.0") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} diff --git a/tests/golden/java/java-okhttp/media-type-selection/models/FileEnvelope.java.golden b/tests/golden/java/java-okhttp/media-type-selection/models/FileEnvelope.java.golden new file mode 100644 index 000000000..efb47c4c5 --- /dev/null +++ b/tests/golden/java/java-okhttp/media-type-selection/models/FileEnvelope.java.golden @@ -0,0 +1,24 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package com.example.sdk.models; + +public class FileEnvelope { + private byte[] file; + private String note; + + public FileEnvelope(byte[] file, String note) { + this.file = file; + this.note = note; + } + + public byte[] getFile() { + return this.file; + } + + public String getNote() { + return this.note; + } +} diff --git a/tests/golden/java/java-okhttp/media-type-selection/models/Payload.java.golden b/tests/golden/java/java-okhttp/media-type-selection/models/Payload.java.golden new file mode 100644 index 000000000..199fce35b --- /dev/null +++ b/tests/golden/java/java-okhttp/media-type-selection/models/Payload.java.golden @@ -0,0 +1,24 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package com.example.sdk.models; + +public class Payload { + private Integer count; + private String id; + + public Payload(Integer count, String id) { + this.count = count; + this.id = id; + } + + public Integer getCount() { + return this.count; + } + + public String getId() { + return this.id; + } +} diff --git a/tests/golden/java/java-okhttp/media-type-selection/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/media-type-selection/runtime/ApiClient.java.golden new file mode 100644 index 000000000..4dc369c4b --- /dev/null +++ b/tests/golden/java/java-okhttp/media-type-selection/runtime/ApiClient.java.golden @@ -0,0 +1,79 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package com.example.sdk.runtime; + +import java.io.IOException; +import java.util.Map; + +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class ApiClient { + private static final MediaType JSON = MediaType.get("application/json"); + + private final String baseUrl; + private final OkHttpClient client; + private final Authenticator authenticator; + private final Map defaultHeaders; + + public ApiClient(String baseUrl) { + this(baseUrl, new OkHttpClient(), null, Map.of()); + } + + public ApiClient(String baseUrl, OkHttpClient client, Authenticator authenticator, Map defaultHeaders) { + this.baseUrl = baseUrl; + this.client = client; + this.authenticator = authenticator; + this.defaultHeaders = defaultHeaders; + } + + public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { + String url = baseUrl.replaceAll("/+$", "") + path; + HttpUrl parsed = HttpUrl.parse(url); + if (parsed == null) { + throw new IllegalArgumentException("Invalid URL: " + url); + } + HttpUrl.Builder urlBuilder = parsed.newBuilder(); + if (query != null) { + for (Map.Entry entry : query.entrySet()) { + urlBuilder.addQueryParameter(entry.getKey(), entry.getValue()); + } + } + + Request.Builder builder = new Request.Builder() + .url(urlBuilder.build()) + .method(method, requestBody); + + for (Map.Entry entry : defaultHeaders.entrySet()) { + builder.header(entry.getKey(), entry.getValue()); + } + if (authenticator != null) { + authenticator.authenticate(builder); + } + + return builder.build(); + } + + public Response execute(Request request) throws IOException { + return client.newCall(request).execute(); + } +} diff --git a/tests/golden/java/java-okhttp/media-type-selection/runtime/ApiException.java.golden b/tests/golden/java/java-okhttp/media-type-selection/runtime/ApiException.java.golden new file mode 100644 index 000000000..f5cfdf704 --- /dev/null +++ b/tests/golden/java/java-okhttp/media-type-selection/runtime/ApiException.java.golden @@ -0,0 +1,31 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package com.example.sdk.runtime; + +public class ApiException extends RuntimeException { + private final int statusCode; + private final String status; + private final String body; + + public ApiException(int statusCode, String status, String body) { + super("API error " + statusCode + ": " + status); + this.statusCode = statusCode; + this.status = status; + this.body = body; + } + + public int getStatusCode() { + return statusCode; + } + + public String getStatus() { + return status; + } + + public String getBody() { + return body; + } +} diff --git a/tests/golden/java/java-okhttp/media-type-selection/runtime/ApiKeyAuth.java.golden b/tests/golden/java/java-okhttp/media-type-selection/runtime/ApiKeyAuth.java.golden new file mode 100644 index 000000000..494685887 --- /dev/null +++ b/tests/golden/java/java-okhttp/media-type-selection/runtime/ApiKeyAuth.java.golden @@ -0,0 +1,42 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class ApiKeyAuth implements Authenticator { + private final String key; + private final Supplier keyProvider; + private final String name; + private final ApiKeyLocation location; + + public ApiKeyAuth(String key, String name, ApiKeyLocation location) { + this.key = key; + this.keyProvider = null; + this.name = name; + this.location = location; + } + + public ApiKeyAuth(Supplier keyProvider, String name, ApiKeyLocation location) { + this.key = null; + this.keyProvider = keyProvider; + this.name = name; + this.location = location; + } + + @Override + public void authenticate(Request.Builder builder) { + String k = keyProvider != null ? keyProvider.get() : key; + if (location == ApiKeyLocation.HEADER) { + builder.header(name, k); + } else if (location == ApiKeyLocation.QUERY) { + builder.url(builder.build().url().newBuilder() + .addQueryParameter(name, k) + .build()); + } + } +} diff --git a/tests/golden/java/java-okhttp/media-type-selection/runtime/ApiKeyLocation.java.golden b/tests/golden/java/java-okhttp/media-type-selection/runtime/ApiKeyLocation.java.golden new file mode 100644 index 000000000..3f54675f1 --- /dev/null +++ b/tests/golden/java/java-okhttp/media-type-selection/runtime/ApiKeyLocation.java.golden @@ -0,0 +1,11 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package com.example.sdk.runtime; + +public enum ApiKeyLocation { + HEADER, + QUERY +} diff --git a/tests/golden/java/java-okhttp/media-type-selection/runtime/Authenticator.java.golden b/tests/golden/java/java-okhttp/media-type-selection/runtime/Authenticator.java.golden new file mode 100644 index 000000000..d8a6fb7fb --- /dev/null +++ b/tests/golden/java/java-okhttp/media-type-selection/runtime/Authenticator.java.golden @@ -0,0 +1,12 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package com.example.sdk.runtime; + +import okhttp3.Request; + +public interface Authenticator { + void authenticate(Request.Builder builder); +} diff --git a/tests/golden/java/java-okhttp/media-type-selection/runtime/BearerAuth.java.golden b/tests/golden/java/java-okhttp/media-type-selection/runtime/BearerAuth.java.golden new file mode 100644 index 000000000..b096e60d1 --- /dev/null +++ b/tests/golden/java/java-okhttp/media-type-selection/runtime/BearerAuth.java.golden @@ -0,0 +1,30 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class BearerAuth implements Authenticator { + private final String token; + private final Supplier tokenProvider; + + public BearerAuth(String token) { + this.token = token; + this.tokenProvider = null; + } + + public BearerAuth(Supplier tokenProvider) { + this.token = null; + this.tokenProvider = tokenProvider; + } + + @Override + public void authenticate(Request.Builder builder) { + String t = tokenProvider != null ? tokenProvider.get() : token; + builder.header("Authorization", "Bearer " + t); + } +} diff --git a/tests/golden/java/java-okhttp/minimal/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/minimal/apis/DefaultApi.java.golden index 934e1e1ec..d4b94cc04 100644 --- a/tests/golden/java/java-okhttp/minimal/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/minimal/apis/DefaultApi.java.golden @@ -5,6 +5,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/tests/golden/java/java-okhttp/minimal/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/minimal/runtime/ApiClient.java.golden index aab8a0228..e1512df45 100644 --- a/tests/golden/java/java-okhttp/minimal/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/minimal/runtime/ApiClient.java.golden @@ -34,6 +34,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -46,11 +58,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/multipart-edge-cases/README.md.golden b/tests/golden/java/java-okhttp/multipart-edge-cases/README.md.golden new file mode 100644 index 000000000..61745a51d --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-edge-cases/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Edge Cases + +Covers optional multipart bodies, optional parts, and text-only multipart fields. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart-edge-cases`. diff --git a/tests/golden/java/java-okhttp/multipart-edge-cases/apis/MultipartApi.java.golden b/tests/golden/java/java-okhttp/multipart-edge-cases/apis/MultipartApi.java.golden new file mode 100644 index 000000000..3415771d4 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-edge-cases/apis/MultipartApi.java.golden @@ -0,0 +1,135 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package com.example.sdk.apis; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.example.sdk.models.*; +import com.example.sdk.runtime.ApiClient; +import com.example.sdk.runtime.ApiException; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +/** + * SendOptionalPartsResponse carries the response from sendOptionalParts. + */ +public class SendOptionalPartsResponse { + private final int statusCode; + private final Response raw; + + public SendOptionalPartsResponse(int statusCode, Response raw) { + this.statusCode = statusCode; + this.raw = raw; + } + + public int getStatusCode() { + return this.statusCode; + } + + public Response getRaw() { + return this.raw; + } +} + +/** + * SendTextFieldsResponse carries the response from sendTextFields. + */ +public class SendTextFieldsResponse { + private final int statusCode; + private final Response raw; + + public SendTextFieldsResponse(int statusCode, Response raw) { + this.statusCode = statusCode; + this.raw = raw; + } + + public int getStatusCode() { + return this.statusCode; + } + + public Response getRaw() { + return this.raw; + } +} + +/** + * MultipartApi groups operations under the multipart tag. + */ +public class MultipartApi { + private final ApiClient client; + private final Gson gson = new Gson(); + + public MultipartApi(ApiClient client) { + this.client = client; + } + + /** + * sendOptionalParts POST /multipart/optional. + */ + public SendOptionalPartsResponse sendOptionalParts(OptionalUpload body) throws IOException { + String path = "/multipart/optional"; + Request request; + RequestBody multipartBody = RequestBody.create(new byte[0], null); + if (body != null) { + MultipartBody.Builder multipartBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM); + if (body.getAttributes() != null) { + multipartBuilder.addFormDataPart("attributes", gson.toJson(body.getAttributes())); + } + if (body.getEnabled() != null) { + multipartBuilder.addFormDataPart("enabled", String.valueOf(body.getEnabled())); + } + if (body.getFile() != null) { + multipartBuilder.addFormDataPart("file", "file", RequestBody.create(body.getFile(), MediaType.get("application/octet-stream"))); + } + if (body.getRetryCount() != null) { + multipartBuilder.addFormDataPart("retry_count", String.valueOf(body.getRetryCount())); + } + if (body.getTitle() != null) { + multipartBuilder.addFormDataPart("title", String.valueOf(body.getTitle())); + } + multipartBody = multipartBuilder.build(); + } + request = client.newRequestWithBody("POST", path, null, multipartBody); + Response response = client.execute(request); + + if (!response.isSuccessful()) { + String errorBody = response.body() != null ? response.body().string() : ""; + throw new ApiException(response.code(), response.message(), errorBody); + } + return new SendOptionalPartsResponse(response.code(), response); + } + + /** + * sendTextFields POST /multipart/text-only. + */ + public SendTextFieldsResponse sendTextFields(TextFields body) throws IOException { + String path = "/multipart/text-only"; + Request request; + MultipartBody.Builder multipartBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM); + multipartBuilder.addFormDataPart("enabled", String.valueOf(body.getEnabled())); + multipartBuilder.addFormDataPart("note", String.valueOf(body.getNote())); + multipartBuilder.addFormDataPart("retry_count", String.valueOf(body.getRetryCount())); + RequestBody multipartBody = multipartBuilder.build(); + request = client.newRequestWithBody("POST", path, null, multipartBody); + Response response = client.execute(request); + + if (!response.isSuccessful()) { + String errorBody = response.body() != null ? response.body().string() : ""; + throw new ApiException(response.code(), response.message(), errorBody); + } + return new SendTextFieldsResponse(response.code(), response); + } +} diff --git a/tests/golden/java/java-okhttp/multipart-edge-cases/build.gradle.golden b/tests/golden/java/java-okhttp/multipart-edge-cases/build.gradle.golden new file mode 100644 index 000000000..ec7d84dad --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-edge-cases/build.gradle.golden @@ -0,0 +1,21 @@ +plugins { + id("java-library") +} + +group = "com.example.sdk" +version = "1.0.0" + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.squareup.okhttp3:okhttp:4.12.0") + implementation("com.google.code.gson:gson:2.11.0") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} diff --git a/tests/golden/java/java-okhttp/multipart-edge-cases/models/Attributes.java.golden b/tests/golden/java/java-okhttp/multipart-edge-cases/models/Attributes.java.golden new file mode 100644 index 000000000..157a8cfb3 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-edge-cases/models/Attributes.java.golden @@ -0,0 +1,24 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package com.example.sdk.models; + +public class Attributes { + private String label; + private Integer priority; + + public Attributes(String label, Integer priority) { + this.label = label; + this.priority = priority; + } + + public String getLabel() { + return this.label; + } + + public Integer getPriority() { + return this.priority; + } +} diff --git a/tests/golden/java/java-okhttp/multipart-edge-cases/models/OptionalUpload.java.golden b/tests/golden/java/java-okhttp/multipart-edge-cases/models/OptionalUpload.java.golden new file mode 100644 index 000000000..e25d27953 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-edge-cases/models/OptionalUpload.java.golden @@ -0,0 +1,46 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class OptionalUpload { + private Attributes attributes; + private Boolean enabled; + private byte[] file; + @SerializedName("retry_count") + private Integer retryCount; + private String title; + + public OptionalUpload(Attributes attributes, Boolean enabled, byte[] file, Integer retryCount, + String title) { + this.attributes = attributes; + this.enabled = enabled; + this.file = file; + this.retryCount = retryCount; + this.title = title; + } + + public Attributes getAttributes() { + return this.attributes; + } + + public Boolean getEnabled() { + return this.enabled; + } + + public byte[] getFile() { + return this.file; + } + + public Integer getRetryCount() { + return this.retryCount; + } + + public String getTitle() { + return this.title; + } +} diff --git a/tests/golden/java/java-okhttp/multipart-edge-cases/models/TextFields.java.golden b/tests/golden/java/java-okhttp/multipart-edge-cases/models/TextFields.java.golden new file mode 100644 index 000000000..4aeb84459 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-edge-cases/models/TextFields.java.golden @@ -0,0 +1,33 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class TextFields { + private boolean enabled; + private String note; + @SerializedName("retry_count") + private int retryCount; + + public TextFields(boolean enabled, String note, int retryCount) { + this.enabled = enabled; + this.note = note; + this.retryCount = retryCount; + } + + public boolean getEnabled() { + return this.enabled; + } + + public String getNote() { + return this.note; + } + + public int getRetryCount() { + return this.retryCount; + } +} diff --git a/tests/golden/java/java-okhttp/multipart-edge-cases/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/multipart-edge-cases/runtime/ApiClient.java.golden new file mode 100644 index 000000000..495895e6e --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-edge-cases/runtime/ApiClient.java.golden @@ -0,0 +1,79 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package com.example.sdk.runtime; + +import java.io.IOException; +import java.util.Map; + +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class ApiClient { + private static final MediaType JSON = MediaType.get("application/json"); + + private final String baseUrl; + private final OkHttpClient client; + private final Authenticator authenticator; + private final Map defaultHeaders; + + public ApiClient(String baseUrl) { + this(baseUrl, new OkHttpClient(), null, Map.of()); + } + + public ApiClient(String baseUrl, OkHttpClient client, Authenticator authenticator, Map defaultHeaders) { + this.baseUrl = baseUrl; + this.client = client; + this.authenticator = authenticator; + this.defaultHeaders = defaultHeaders; + } + + public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { + String url = baseUrl.replaceAll("/+$", "") + path; + HttpUrl parsed = HttpUrl.parse(url); + if (parsed == null) { + throw new IllegalArgumentException("Invalid URL: " + url); + } + HttpUrl.Builder urlBuilder = parsed.newBuilder(); + if (query != null) { + for (Map.Entry entry : query.entrySet()) { + urlBuilder.addQueryParameter(entry.getKey(), entry.getValue()); + } + } + + Request.Builder builder = new Request.Builder() + .url(urlBuilder.build()) + .method(method, requestBody); + + for (Map.Entry entry : defaultHeaders.entrySet()) { + builder.header(entry.getKey(), entry.getValue()); + } + if (authenticator != null) { + authenticator.authenticate(builder); + } + + return builder.build(); + } + + public Response execute(Request request) throws IOException { + return client.newCall(request).execute(); + } +} diff --git a/tests/golden/java/java-okhttp/multipart-edge-cases/runtime/ApiException.java.golden b/tests/golden/java/java-okhttp/multipart-edge-cases/runtime/ApiException.java.golden new file mode 100644 index 000000000..2da5ca665 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-edge-cases/runtime/ApiException.java.golden @@ -0,0 +1,31 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package com.example.sdk.runtime; + +public class ApiException extends RuntimeException { + private final int statusCode; + private final String status; + private final String body; + + public ApiException(int statusCode, String status, String body) { + super("API error " + statusCode + ": " + status); + this.statusCode = statusCode; + this.status = status; + this.body = body; + } + + public int getStatusCode() { + return statusCode; + } + + public String getStatus() { + return status; + } + + public String getBody() { + return body; + } +} diff --git a/tests/golden/java/java-okhttp/multipart-edge-cases/runtime/ApiKeyAuth.java.golden b/tests/golden/java/java-okhttp/multipart-edge-cases/runtime/ApiKeyAuth.java.golden new file mode 100644 index 000000000..0c785aa15 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-edge-cases/runtime/ApiKeyAuth.java.golden @@ -0,0 +1,42 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class ApiKeyAuth implements Authenticator { + private final String key; + private final Supplier keyProvider; + private final String name; + private final ApiKeyLocation location; + + public ApiKeyAuth(String key, String name, ApiKeyLocation location) { + this.key = key; + this.keyProvider = null; + this.name = name; + this.location = location; + } + + public ApiKeyAuth(Supplier keyProvider, String name, ApiKeyLocation location) { + this.key = null; + this.keyProvider = keyProvider; + this.name = name; + this.location = location; + } + + @Override + public void authenticate(Request.Builder builder) { + String k = keyProvider != null ? keyProvider.get() : key; + if (location == ApiKeyLocation.HEADER) { + builder.header(name, k); + } else if (location == ApiKeyLocation.QUERY) { + builder.url(builder.build().url().newBuilder() + .addQueryParameter(name, k) + .build()); + } + } +} diff --git a/tests/golden/java/java-okhttp/multipart-edge-cases/runtime/ApiKeyLocation.java.golden b/tests/golden/java/java-okhttp/multipart-edge-cases/runtime/ApiKeyLocation.java.golden new file mode 100644 index 000000000..309d56d99 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-edge-cases/runtime/ApiKeyLocation.java.golden @@ -0,0 +1,11 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package com.example.sdk.runtime; + +public enum ApiKeyLocation { + HEADER, + QUERY +} diff --git a/tests/golden/java/java-okhttp/multipart-edge-cases/runtime/Authenticator.java.golden b/tests/golden/java/java-okhttp/multipart-edge-cases/runtime/Authenticator.java.golden new file mode 100644 index 000000000..4d3d5fc1d --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-edge-cases/runtime/Authenticator.java.golden @@ -0,0 +1,12 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package com.example.sdk.runtime; + +import okhttp3.Request; + +public interface Authenticator { + void authenticate(Request.Builder builder); +} diff --git a/tests/golden/java/java-okhttp/multipart-edge-cases/runtime/BearerAuth.java.golden b/tests/golden/java/java-okhttp/multipart-edge-cases/runtime/BearerAuth.java.golden new file mode 100644 index 000000000..f2a608654 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-edge-cases/runtime/BearerAuth.java.golden @@ -0,0 +1,30 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class BearerAuth implements Authenticator { + private final String token; + private final Supplier tokenProvider; + + public BearerAuth(String token) { + this.token = token; + this.tokenProvider = null; + } + + public BearerAuth(Supplier tokenProvider) { + this.token = null; + this.tokenProvider = tokenProvider; + } + + @Override + public void authenticate(Request.Builder builder) { + String t = tokenProvider != null ? tokenProvider.get() : token; + builder.header("Authorization", "Bearer " + t); + } +} diff --git a/tests/golden/java/java-okhttp/multipart-nested-object-parts/README.md.golden b/tests/golden/java/java-okhttp/multipart-nested-object-parts/README.md.golden new file mode 100644 index 000000000..2899b40d5 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-nested-object-parts/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Nested Object Parts + +Covers multipart object parts whose wire names differ from ergonomic names. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart-nested-object-parts`. diff --git a/tests/golden/java/java-okhttp/multipart-nested-object-parts/apis/MultipartApi.java.golden b/tests/golden/java/java-okhttp/multipart-nested-object-parts/apis/MultipartApi.java.golden new file mode 100644 index 000000000..1936b2ca7 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-nested-object-parts/apis/MultipartApi.java.golden @@ -0,0 +1,77 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package com.example.sdk.apis; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.example.sdk.models.*; +import com.example.sdk.runtime.ApiClient; +import com.example.sdk.runtime.ApiException; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +/** + * SendNestedObjectPartResponse carries the response from sendNestedObjectPart. + */ +public class SendNestedObjectPartResponse { + private final int statusCode; + private final Response raw; + + public SendNestedObjectPartResponse(int statusCode, Response raw) { + this.statusCode = statusCode; + this.raw = raw; + } + + public int getStatusCode() { + return this.statusCode; + } + + public Response getRaw() { + return this.raw; + } +} + +/** + * MultipartApi groups operations under the multipart tag. + */ +public class MultipartApi { + private final ApiClient client; + private final Gson gson = new Gson(); + + public MultipartApi(ApiClient client) { + this.client = client; + } + + /** + * sendNestedObjectPart POST /multipart/nested-object. + */ + public SendNestedObjectPartResponse sendNestedObjectPart(NestedUpload body) throws IOException { + String path = "/multipart/nested-object"; + Request request; + MultipartBody.Builder multipartBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM); + multipartBuilder.addFormDataPart("file", "file", RequestBody.create(body.getFile(), MediaType.get("application/octet-stream"))); + multipartBuilder.addFormDataPart("item_config", gson.toJson(body.getItemConfig())); + RequestBody multipartBody = multipartBuilder.build(); + request = client.newRequestWithBody("POST", path, null, multipartBody); + Response response = client.execute(request); + + if (!response.isSuccessful()) { + String errorBody = response.body() != null ? response.body().string() : ""; + throw new ApiException(response.code(), response.message(), errorBody); + } + return new SendNestedObjectPartResponse(response.code(), response); + } +} diff --git a/tests/golden/java/java-okhttp/multipart-nested-object-parts/build.gradle.golden b/tests/golden/java/java-okhttp/multipart-nested-object-parts/build.gradle.golden new file mode 100644 index 000000000..ec7d84dad --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-nested-object-parts/build.gradle.golden @@ -0,0 +1,21 @@ +plugins { + id("java-library") +} + +group = "com.example.sdk" +version = "1.0.0" + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.squareup.okhttp3:okhttp:4.12.0") + implementation("com.google.code.gson:gson:2.11.0") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} diff --git a/tests/golden/java/java-okhttp/multipart-nested-object-parts/models/ItemConfig.java.golden b/tests/golden/java/java-okhttp/multipart-nested-object-parts/models/ItemConfig.java.golden new file mode 100644 index 000000000..7273ca3ec --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-nested-object-parts/models/ItemConfig.java.golden @@ -0,0 +1,28 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class ItemConfig { + @SerializedName("display_name") + private String displayName; + @SerializedName("retention_days") + private Integer retentionDays; + + public ItemConfig(String displayName, Integer retentionDays) { + this.displayName = displayName; + this.retentionDays = retentionDays; + } + + public String getDisplayName() { + return this.displayName; + } + + public Integer getRetentionDays() { + return this.retentionDays; + } +} diff --git a/tests/golden/java/java-okhttp/multipart-nested-object-parts/models/NestedUpload.java.golden b/tests/golden/java/java-okhttp/multipart-nested-object-parts/models/NestedUpload.java.golden new file mode 100644 index 000000000..ff60480f4 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-nested-object-parts/models/NestedUpload.java.golden @@ -0,0 +1,27 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class NestedUpload { + private byte[] file; + @SerializedName("item_config") + private ItemConfig itemConfig; + + public NestedUpload(byte[] file, ItemConfig itemConfig) { + this.file = file; + this.itemConfig = itemConfig; + } + + public byte[] getFile() { + return this.file; + } + + public ItemConfig getItemConfig() { + return this.itemConfig; + } +} diff --git a/tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/ApiClient.java.golden new file mode 100644 index 000000000..ffe7c6586 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/ApiClient.java.golden @@ -0,0 +1,79 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package com.example.sdk.runtime; + +import java.io.IOException; +import java.util.Map; + +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class ApiClient { + private static final MediaType JSON = MediaType.get("application/json"); + + private final String baseUrl; + private final OkHttpClient client; + private final Authenticator authenticator; + private final Map defaultHeaders; + + public ApiClient(String baseUrl) { + this(baseUrl, new OkHttpClient(), null, Map.of()); + } + + public ApiClient(String baseUrl, OkHttpClient client, Authenticator authenticator, Map defaultHeaders) { + this.baseUrl = baseUrl; + this.client = client; + this.authenticator = authenticator; + this.defaultHeaders = defaultHeaders; + } + + public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { + String url = baseUrl.replaceAll("/+$", "") + path; + HttpUrl parsed = HttpUrl.parse(url); + if (parsed == null) { + throw new IllegalArgumentException("Invalid URL: " + url); + } + HttpUrl.Builder urlBuilder = parsed.newBuilder(); + if (query != null) { + for (Map.Entry entry : query.entrySet()) { + urlBuilder.addQueryParameter(entry.getKey(), entry.getValue()); + } + } + + Request.Builder builder = new Request.Builder() + .url(urlBuilder.build()) + .method(method, requestBody); + + for (Map.Entry entry : defaultHeaders.entrySet()) { + builder.header(entry.getKey(), entry.getValue()); + } + if (authenticator != null) { + authenticator.authenticate(builder); + } + + return builder.build(); + } + + public Response execute(Request request) throws IOException { + return client.newCall(request).execute(); + } +} diff --git a/tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/ApiException.java.golden b/tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/ApiException.java.golden new file mode 100644 index 000000000..0696df1fd --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/ApiException.java.golden @@ -0,0 +1,31 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package com.example.sdk.runtime; + +public class ApiException extends RuntimeException { + private final int statusCode; + private final String status; + private final String body; + + public ApiException(int statusCode, String status, String body) { + super("API error " + statusCode + ": " + status); + this.statusCode = statusCode; + this.status = status; + this.body = body; + } + + public int getStatusCode() { + return statusCode; + } + + public String getStatus() { + return status; + } + + public String getBody() { + return body; + } +} diff --git a/tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/ApiKeyAuth.java.golden b/tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/ApiKeyAuth.java.golden new file mode 100644 index 000000000..7a1358d7b --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/ApiKeyAuth.java.golden @@ -0,0 +1,42 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class ApiKeyAuth implements Authenticator { + private final String key; + private final Supplier keyProvider; + private final String name; + private final ApiKeyLocation location; + + public ApiKeyAuth(String key, String name, ApiKeyLocation location) { + this.key = key; + this.keyProvider = null; + this.name = name; + this.location = location; + } + + public ApiKeyAuth(Supplier keyProvider, String name, ApiKeyLocation location) { + this.key = null; + this.keyProvider = keyProvider; + this.name = name; + this.location = location; + } + + @Override + public void authenticate(Request.Builder builder) { + String k = keyProvider != null ? keyProvider.get() : key; + if (location == ApiKeyLocation.HEADER) { + builder.header(name, k); + } else if (location == ApiKeyLocation.QUERY) { + builder.url(builder.build().url().newBuilder() + .addQueryParameter(name, k) + .build()); + } + } +} diff --git a/tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/ApiKeyLocation.java.golden b/tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/ApiKeyLocation.java.golden new file mode 100644 index 000000000..f25bb2a8d --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/ApiKeyLocation.java.golden @@ -0,0 +1,11 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package com.example.sdk.runtime; + +public enum ApiKeyLocation { + HEADER, + QUERY +} diff --git a/tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/Authenticator.java.golden b/tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/Authenticator.java.golden new file mode 100644 index 000000000..be9a454f7 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/Authenticator.java.golden @@ -0,0 +1,12 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package com.example.sdk.runtime; + +import okhttp3.Request; + +public interface Authenticator { + void authenticate(Request.Builder builder); +} diff --git a/tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/BearerAuth.java.golden b/tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/BearerAuth.java.golden new file mode 100644 index 000000000..f3e250a76 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-nested-object-parts/runtime/BearerAuth.java.golden @@ -0,0 +1,30 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class BearerAuth implements Authenticator { + private final String token; + private final Supplier tokenProvider; + + public BearerAuth(String token) { + this.token = token; + this.tokenProvider = null; + } + + public BearerAuth(Supplier tokenProvider) { + this.token = null; + this.tokenProvider = tokenProvider; + } + + @Override + public void authenticate(Request.Builder builder) { + String t = tokenProvider != null ? tokenProvider.get() : token; + builder.header("Authorization", "Bearer " + t); + } +} diff --git a/tests/golden/java/java-okhttp/multipart-unsupported-schema/README.md.golden b/tests/golden/java/java-okhttp/multipart-unsupported-schema/README.md.golden new file mode 100644 index 000000000..60a3a2357 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-unsupported-schema/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Unsupported Schema + +Covers multipart request bodies that are not object-shaped. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart-unsupported-schema`. diff --git a/tests/golden/java/java-okhttp/multipart-unsupported-schema/apis/TransferApi.java.golden b/tests/golden/java/java-okhttp/multipart-unsupported-schema/apis/TransferApi.java.golden new file mode 100644 index 000000000..4a1ba5e61 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-unsupported-schema/apis/TransferApi.java.golden @@ -0,0 +1,72 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Unsupported Schema — 1.0.0 +// Covers multipart request bodies that are not object-shaped. + +package com.example.sdk.apis; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.example.sdk.models.*; +import com.example.sdk.runtime.ApiClient; +import com.example.sdk.runtime.ApiException; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +/** + * UploadRawMultipartResponse carries the response from uploadRawMultipart. + */ +public class UploadRawMultipartResponse { + private final int statusCode; + private final Response raw; + + public UploadRawMultipartResponse(int statusCode, Response raw) { + this.statusCode = statusCode; + this.raw = raw; + } + + public int getStatusCode() { + return this.statusCode; + } + + public Response getRaw() { + return this.raw; + } +} + +/** + * TransferApi groups operations under the transfer tag. + */ +public class TransferApi { + private final ApiClient client; + private final Gson gson = new Gson(); + + public TransferApi(ApiClient client) { + this.client = client; + } + + /** + * uploadRawMultipart POST /uploads/raw. + */ + public UploadRawMultipartResponse uploadRawMultipart(byte[] body) throws IOException { + String path = "/uploads/raw"; + Request request; + throw new IllegalArgumentException("unsupported multipart request body: schema must be object-shaped"); + Response response = client.execute(request); + + if (!response.isSuccessful()) { + String errorBody = response.body() != null ? response.body().string() : ""; + throw new ApiException(response.code(), response.message(), errorBody); + } + return new UploadRawMultipartResponse(response.code(), response); + } +} diff --git a/tests/golden/java/java-okhttp/multipart-unsupported-schema/build.gradle.golden b/tests/golden/java/java-okhttp/multipart-unsupported-schema/build.gradle.golden new file mode 100644 index 000000000..ec7d84dad --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-unsupported-schema/build.gradle.golden @@ -0,0 +1,21 @@ +plugins { + id("java-library") +} + +group = "com.example.sdk" +version = "1.0.0" + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.squareup.okhttp3:okhttp:4.12.0") + implementation("com.google.code.gson:gson:2.11.0") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} diff --git a/tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/ApiClient.java.golden new file mode 100644 index 000000000..2748afe3c --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/ApiClient.java.golden @@ -0,0 +1,79 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Unsupported Schema — 1.0.0 +// Covers multipart request bodies that are not object-shaped. + +package com.example.sdk.runtime; + +import java.io.IOException; +import java.util.Map; + +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class ApiClient { + private static final MediaType JSON = MediaType.get("application/json"); + + private final String baseUrl; + private final OkHttpClient client; + private final Authenticator authenticator; + private final Map defaultHeaders; + + public ApiClient(String baseUrl) { + this(baseUrl, new OkHttpClient(), null, Map.of()); + } + + public ApiClient(String baseUrl, OkHttpClient client, Authenticator authenticator, Map defaultHeaders) { + this.baseUrl = baseUrl; + this.client = client; + this.authenticator = authenticator; + this.defaultHeaders = defaultHeaders; + } + + public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { + String url = baseUrl.replaceAll("/+$", "") + path; + HttpUrl parsed = HttpUrl.parse(url); + if (parsed == null) { + throw new IllegalArgumentException("Invalid URL: " + url); + } + HttpUrl.Builder urlBuilder = parsed.newBuilder(); + if (query != null) { + for (Map.Entry entry : query.entrySet()) { + urlBuilder.addQueryParameter(entry.getKey(), entry.getValue()); + } + } + + Request.Builder builder = new Request.Builder() + .url(urlBuilder.build()) + .method(method, requestBody); + + for (Map.Entry entry : defaultHeaders.entrySet()) { + builder.header(entry.getKey(), entry.getValue()); + } + if (authenticator != null) { + authenticator.authenticate(builder); + } + + return builder.build(); + } + + public Response execute(Request request) throws IOException { + return client.newCall(request).execute(); + } +} diff --git a/tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/ApiException.java.golden b/tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/ApiException.java.golden new file mode 100644 index 000000000..ccda90476 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/ApiException.java.golden @@ -0,0 +1,31 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Unsupported Schema — 1.0.0 +// Covers multipart request bodies that are not object-shaped. + +package com.example.sdk.runtime; + +public class ApiException extends RuntimeException { + private final int statusCode; + private final String status; + private final String body; + + public ApiException(int statusCode, String status, String body) { + super("API error " + statusCode + ": " + status); + this.statusCode = statusCode; + this.status = status; + this.body = body; + } + + public int getStatusCode() { + return statusCode; + } + + public String getStatus() { + return status; + } + + public String getBody() { + return body; + } +} diff --git a/tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/ApiKeyAuth.java.golden b/tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/ApiKeyAuth.java.golden new file mode 100644 index 000000000..e5f51acc2 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/ApiKeyAuth.java.golden @@ -0,0 +1,42 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Unsupported Schema — 1.0.0 +// Covers multipart request bodies that are not object-shaped. + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class ApiKeyAuth implements Authenticator { + private final String key; + private final Supplier keyProvider; + private final String name; + private final ApiKeyLocation location; + + public ApiKeyAuth(String key, String name, ApiKeyLocation location) { + this.key = key; + this.keyProvider = null; + this.name = name; + this.location = location; + } + + public ApiKeyAuth(Supplier keyProvider, String name, ApiKeyLocation location) { + this.key = null; + this.keyProvider = keyProvider; + this.name = name; + this.location = location; + } + + @Override + public void authenticate(Request.Builder builder) { + String k = keyProvider != null ? keyProvider.get() : key; + if (location == ApiKeyLocation.HEADER) { + builder.header(name, k); + } else if (location == ApiKeyLocation.QUERY) { + builder.url(builder.build().url().newBuilder() + .addQueryParameter(name, k) + .build()); + } + } +} diff --git a/tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/ApiKeyLocation.java.golden b/tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/ApiKeyLocation.java.golden new file mode 100644 index 000000000..7ebe5b69d --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/ApiKeyLocation.java.golden @@ -0,0 +1,11 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Unsupported Schema — 1.0.0 +// Covers multipart request bodies that are not object-shaped. + +package com.example.sdk.runtime; + +public enum ApiKeyLocation { + HEADER, + QUERY +} diff --git a/tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/Authenticator.java.golden b/tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/Authenticator.java.golden new file mode 100644 index 000000000..fa0bf9741 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/Authenticator.java.golden @@ -0,0 +1,12 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Unsupported Schema — 1.0.0 +// Covers multipart request bodies that are not object-shaped. + +package com.example.sdk.runtime; + +import okhttp3.Request; + +public interface Authenticator { + void authenticate(Request.Builder builder); +} diff --git a/tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/BearerAuth.java.golden b/tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/BearerAuth.java.golden new file mode 100644 index 000000000..ca43acb93 --- /dev/null +++ b/tests/golden/java/java-okhttp/multipart-unsupported-schema/runtime/BearerAuth.java.golden @@ -0,0 +1,30 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Unsupported Schema — 1.0.0 +// Covers multipart request bodies that are not object-shaped. + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class BearerAuth implements Authenticator { + private final String token; + private final Supplier tokenProvider; + + public BearerAuth(String token) { + this.token = token; + this.tokenProvider = null; + } + + public BearerAuth(Supplier tokenProvider) { + this.token = null; + this.tokenProvider = tokenProvider; + } + + @Override + public void authenticate(Request.Builder builder) { + String t = tokenProvider != null ? tokenProvider.get() : token; + builder.header("Authorization", "Bearer " + t); + } +} diff --git a/tests/golden/java/java-okhttp/multiple-similar-request-schemas/apis/TestApiApi.java.golden b/tests/golden/java/java-okhttp/multiple-similar-request-schemas/apis/TestApiApi.java.golden index 643db35db..4bd7281a5 100644 --- a/tests/golden/java/java-okhttp/multiple-similar-request-schemas/apis/TestApiApi.java.golden +++ b/tests/golden/java/java-okhttp/multiple-similar-request-schemas/apis/TestApiApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -89,18 +92,21 @@ public class TestApiApi { */ public CreateTypeAResponse createTypeA(CreateRequestTypeA body) throws IOException { String path = "/api/v1/type-a/resources"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); ResourceTypeA status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new CreateTypeAResponse(response.code(), response, status200); } @@ -110,18 +116,21 @@ public class TestApiApi { */ public CreateTypeBResponse createTypeB(CreateRequestTypeB body) throws IOException { String path = "/api/v1/type-b/resources"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); ResourceTypeB status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new CreateTypeBResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/multiple-similar-request-schemas/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/multiple-similar-request-schemas/runtime/ApiClient.java.golden index a72a9488b..d4971d3e9 100644 --- a/tests/golden/java/java-okhttp/multiple-similar-request-schemas/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/multiple-similar-request-schemas/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/naming-conventions/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/naming-conventions/apis/DefaultApi.java.golden index dabaf73eb..e82af64f9 100644 --- a/tests/golden/java/java-okhttp/naming-conventions/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/naming-conventions/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -144,18 +147,21 @@ public class DefaultApi { */ public TestNamingConventionsResponse testNamingConventions(NamingConventionTest body) throws IOException { String path = "/test"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); NamingConventionTest status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new TestNamingConventionsResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/naming-conventions/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/naming-conventions/runtime/ApiClient.java.golden index dc8b62793..82ad30d80 100644 --- a/tests/golden/java/java-okhttp/naming-conventions/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/naming-conventions/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/petstore/apis/PetApi.java.golden b/tests/golden/java/java-okhttp/petstore/apis/PetApi.java.golden index db79b34ab..359754e54 100644 --- a/tests/golden/java/java-okhttp/petstore/apis/PetApi.java.golden +++ b/tests/golden/java/java-okhttp/petstore/apis/PetApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -245,18 +248,21 @@ public class PetApi { */ public UpdatePetResponse updatePet(Pet body) throws IOException { String path = "/pet"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("PUT", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("PUT", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Pet status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new UpdatePetResponse(response.code(), response, status200); } @@ -266,18 +272,21 @@ public class PetApi { */ public AddPetResponse addPet(Pet body) throws IOException { String path = "/pet"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Pet status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new AddPetResponse(response.code(), response, status200); } @@ -296,10 +305,11 @@ public class PetApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); List status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken>() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken>() {}.getType()); } return new FindPetsByStatusResponse(response.code(), response, status200); } @@ -318,10 +328,11 @@ public class PetApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); List status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken>() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken>() {}.getType()); } return new FindPetsByTagsResponse(response.code(), response, status200); } @@ -338,10 +349,11 @@ public class PetApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Pet status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new GetPetByIdResponse(response.code(), response, status200); } @@ -365,10 +377,11 @@ public class PetApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Pet status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new UpdatePetWithFormResponse(response.code(), response, status200); } @@ -404,10 +417,11 @@ public class PetApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); UploadResponse status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new UploadFileResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/petstore/apis/StoreApi.java.golden b/tests/golden/java/java-okhttp/petstore/apis/StoreApi.java.golden index 3bb02c04c..bf678c059 100644 --- a/tests/golden/java/java-okhttp/petstore/apis/StoreApi.java.golden +++ b/tests/golden/java/java-okhttp/petstore/apis/StoreApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -144,10 +147,11 @@ public class StoreApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Map status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken>() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken>() {}.getType()); } return new GetInventoryResponse(response.code(), response, status200); } @@ -157,18 +161,21 @@ public class StoreApi { */ public PlaceOrderResponse placeOrder(Order body) throws IOException { String path = "/store/order"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Order status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new PlaceOrderResponse(response.code(), response, status200); } @@ -185,10 +192,11 @@ public class StoreApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Order status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new GetOrderByIdResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/petstore/apis/UserApi.java.golden b/tests/golden/java/java-okhttp/petstore/apis/UserApi.java.golden index eeaadd78e..5001fa566 100644 --- a/tests/golden/java/java-okhttp/petstore/apis/UserApi.java.golden +++ b/tests/golden/java/java-okhttp/petstore/apis/UserApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -200,18 +203,21 @@ public class UserApi { */ public CreateUserResponse createUser(User body) throws IOException { String path = "/user"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); User status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new CreateUserResponse(response.code(), response, status200); } @@ -221,18 +227,21 @@ public class UserApi { */ public CreateUsersWithListInputResponse createUsersWithListInput(List body) throws IOException { String path = "/user/createWithList"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); User status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new CreateUsersWithListInputResponse(response.code(), response, status200); } @@ -286,10 +295,11 @@ public class UserApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); User status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new GetUserByNameResponse(response.code(), response, status200); } @@ -299,8 +309,10 @@ public class UserApi { */ public UpdateUserResponse updateUser(String username, User body) throws IOException { String path = "/user/{username}".replace("{username}", username); + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("PUT", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("PUT", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { diff --git a/tests/golden/java/java-okhttp/petstore/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/petstore/runtime/ApiClient.java.golden index b33d484ae..3a47fa1da 100644 --- a/tests/golden/java/java-okhttp/petstore/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/petstore/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/query-param-enum/apis/ItemsApi.java.golden b/tests/golden/java/java-okhttp/query-param-enum/apis/ItemsApi.java.golden index f1302bfbe..a4cca2535 100644 --- a/tests/golden/java/java-okhttp/query-param-enum/apis/ItemsApi.java.golden +++ b/tests/golden/java/java-okhttp/query-param-enum/apis/ItemsApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -73,10 +74,11 @@ public class ItemsApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); GetItemsResponse200 status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new GetItemsResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/query-param-enum/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/query-param-enum/runtime/ApiClient.java.golden index d05255875..9fe199382 100644 --- a/tests/golden/java/java-okhttp/query-param-enum/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/query-param-enum/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/recursive-json-all-optional-properties/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/recursive-json-all-optional-properties/apis/DefaultApi.java.golden index 65038badd..395bc938a 100644 --- a/tests/golden/java/java-okhttp/recursive-json-all-optional-properties/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-all-optional-properties/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/tests/golden/java/java-okhttp/recursive-json-all-optional-properties/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/recursive-json-all-optional-properties/runtime/ApiClient.java.golden index 637681efe..54ecc14f1 100644 --- a/tests/golden/java/java-okhttp/recursive-json-all-optional-properties/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-all-optional-properties/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/recursive-json-array-of-inline-objects/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/recursive-json-array-of-inline-objects/apis/DefaultApi.java.golden index f554d5b26..7ebc0c8ea 100644 --- a/tests/golden/java/java-okhttp/recursive-json-array-of-inline-objects/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-array-of-inline-objects/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/tests/golden/java/java-okhttp/recursive-json-array-of-inline-objects/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/recursive-json-array-of-inline-objects/runtime/ApiClient.java.golden index 4ee6a895c..99cbcb82e 100644 --- a/tests/golden/java/java-okhttp/recursive-json-array-of-inline-objects/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-array-of-inline-objects/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/recursive-json-array-of-referenced-types/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/recursive-json-array-of-referenced-types/apis/DefaultApi.java.golden index f05a1706f..5b51b42de 100644 --- a/tests/golden/java/java-okhttp/recursive-json-array-of-referenced-types/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-array-of-referenced-types/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/tests/golden/java/java-okhttp/recursive-json-array-of-referenced-types/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/recursive-json-array-of-referenced-types/runtime/ApiClient.java.golden index fa42bb714..13cbad10a 100644 --- a/tests/golden/java/java-okhttp/recursive-json-array-of-referenced-types/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-array-of-referenced-types/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/recursive-json-array-with-reference-property/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/recursive-json-array-with-reference-property/apis/DefaultApi.java.golden index 4d9ef349b..ba32dcefc 100644 --- a/tests/golden/java/java-okhttp/recursive-json-array-with-reference-property/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-array-with-reference-property/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/tests/golden/java/java-okhttp/recursive-json-array-with-reference-property/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/recursive-json-array-with-reference-property/runtime/ApiClient.java.golden index 09315c6c0..d8b49c97c 100644 --- a/tests/golden/java/java-okhttp/recursive-json-array-with-reference-property/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-array-with-reference-property/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/recursive-json-complex-array-structure/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/recursive-json-complex-array-structure/apis/DefaultApi.java.golden index f0e0fc45e..e4439af4f 100644 --- a/tests/golden/java/java-okhttp/recursive-json-complex-array-structure/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-complex-array-structure/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/tests/golden/java/java-okhttp/recursive-json-complex-array-structure/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/recursive-json-complex-array-structure/runtime/ApiClient.java.golden index 215ee79f0..71265d3c0 100644 --- a/tests/golden/java/java-okhttp/recursive-json-complex-array-structure/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-complex-array-structure/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/recursive-json-deeply-nested-inline/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/recursive-json-deeply-nested-inline/apis/DefaultApi.java.golden index 7ef2f201f..dd2cf3ddd 100644 --- a/tests/golden/java/java-okhttp/recursive-json-deeply-nested-inline/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-deeply-nested-inline/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/tests/golden/java/java-okhttp/recursive-json-deeply-nested-inline/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/recursive-json-deeply-nested-inline/runtime/ApiClient.java.golden index 42e5b1c66..1c7a8c948 100644 --- a/tests/golden/java/java-okhttp/recursive-json-deeply-nested-inline/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-deeply-nested-inline/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/recursive-json-empty-array/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/recursive-json-empty-array/apis/DefaultApi.java.golden index 79ad11c10..7a31a7409 100644 --- a/tests/golden/java/java-okhttp/recursive-json-empty-array/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-empty-array/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/tests/golden/java/java-okhttp/recursive-json-empty-array/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/recursive-json-empty-array/runtime/ApiClient.java.golden index ce000bdbf..2501fe177 100644 --- a/tests/golden/java/java-okhttp/recursive-json-empty-array/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-empty-array/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/recursive-json-inline-object-with-array/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/recursive-json-inline-object-with-array/apis/DefaultApi.java.golden index 3698745a4..e16cb1867 100644 --- a/tests/golden/java/java-okhttp/recursive-json-inline-object-with-array/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-inline-object-with-array/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/tests/golden/java/java-okhttp/recursive-json-inline-object-with-array/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/recursive-json-inline-object-with-array/runtime/ApiClient.java.golden index 14395a91d..4d7b67ad0 100644 --- a/tests/golden/java/java-okhttp/recursive-json-inline-object-with-array/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-inline-object-with-array/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/recursive-json-inline-object/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/recursive-json-inline-object/apis/DefaultApi.java.golden index fff53de4d..711033be7 100644 --- a/tests/golden/java/java-okhttp/recursive-json-inline-object/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-inline-object/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/tests/golden/java/java-okhttp/recursive-json-inline-object/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/recursive-json-inline-object/runtime/ApiClient.java.golden index a6dbf7dfd..fa866a267 100644 --- a/tests/golden/java/java-okhttp/recursive-json-inline-object/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-inline-object/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/recursive-json-mixed-property-types/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/recursive-json-mixed-property-types/apis/DefaultApi.java.golden index 366d4f2f3..d673d1c2a 100644 --- a/tests/golden/java/java-okhttp/recursive-json-mixed-property-types/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-mixed-property-types/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/tests/golden/java/java-okhttp/recursive-json-mixed-property-types/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/recursive-json-mixed-property-types/runtime/ApiClient.java.golden index fdae7fb18..d74137c30 100644 --- a/tests/golden/java/java-okhttp/recursive-json-mixed-property-types/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-mixed-property-types/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/recursive-json-nested-object-reference/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/recursive-json-nested-object-reference/apis/DefaultApi.java.golden index d60601a39..d69455a2f 100644 --- a/tests/golden/java/java-okhttp/recursive-json-nested-object-reference/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-nested-object-reference/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/tests/golden/java/java-okhttp/recursive-json-nested-object-reference/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/recursive-json-nested-object-reference/runtime/ApiClient.java.golden index 55b6a8aef..028647c40 100644 --- a/tests/golden/java/java-okhttp/recursive-json-nested-object-reference/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-nested-object-reference/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/recursive-json-optional-array-of-inline-objects/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/recursive-json-optional-array-of-inline-objects/apis/DefaultApi.java.golden index 82754993a..c301b8002 100644 --- a/tests/golden/java/java-okhttp/recursive-json-optional-array-of-inline-objects/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-optional-array-of-inline-objects/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/tests/golden/java/java-okhttp/recursive-json-optional-array-of-inline-objects/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/recursive-json-optional-array-of-inline-objects/runtime/ApiClient.java.golden index fb3c0e600..e16bc2612 100644 --- a/tests/golden/java/java-okhttp/recursive-json-optional-array-of-inline-objects/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-optional-array-of-inline-objects/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/recursive-json-optional-array-of-referenced-types/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/recursive-json-optional-array-of-referenced-types/apis/DefaultApi.java.golden index 3a5c6569c..0f0d3f634 100644 --- a/tests/golden/java/java-okhttp/recursive-json-optional-array-of-referenced-types/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-optional-array-of-referenced-types/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/tests/golden/java/java-okhttp/recursive-json-optional-array-of-referenced-types/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/recursive-json-optional-array-of-referenced-types/runtime/ApiClient.java.golden index dc61b8141..417f928bf 100644 --- a/tests/golden/java/java-okhttp/recursive-json-optional-array-of-referenced-types/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-optional-array-of-referenced-types/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/recursive-json-optional-inline-object/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/recursive-json-optional-inline-object/apis/DefaultApi.java.golden index 8df5966ab..b00efd8df 100644 --- a/tests/golden/java/java-okhttp/recursive-json-optional-inline-object/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-optional-inline-object/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/tests/golden/java/java-okhttp/recursive-json-optional-inline-object/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/recursive-json-optional-inline-object/runtime/ApiClient.java.golden index 25a0fe40f..c672b4fe3 100644 --- a/tests/golden/java/java-okhttp/recursive-json-optional-inline-object/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-optional-inline-object/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/recursive-json-optional-nested-object-reference/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/recursive-json-optional-nested-object-reference/apis/DefaultApi.java.golden index 2bcb56b9b..ff96c4ddd 100644 --- a/tests/golden/java/java-okhttp/recursive-json-optional-nested-object-reference/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-optional-nested-object-reference/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/tests/golden/java/java-okhttp/recursive-json-optional-nested-object-reference/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/recursive-json-optional-nested-object-reference/runtime/ApiClient.java.golden index 7df8e362a..428535f28 100644 --- a/tests/golden/java/java-okhttp/recursive-json-optional-nested-object-reference/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-optional-nested-object-reference/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/recursive-json-primitive-array/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/recursive-json-primitive-array/apis/DefaultApi.java.golden index 23a3634fc..4ae986f49 100644 --- a/tests/golden/java/java-okhttp/recursive-json-primitive-array/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-primitive-array/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/tests/golden/java/java-okhttp/recursive-json-primitive-array/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/recursive-json-primitive-array/runtime/ApiClient.java.golden index 4c41d5126..37a0ae3e6 100644 --- a/tests/golden/java/java-okhttp/recursive-json-primitive-array/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/recursive-json-primitive-array/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/request-body-content-types/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/request-body-content-types/apis/DefaultApi.java.golden index 64b9ea583..45f653d8b 100644 --- a/tests/golden/java/java-okhttp/request-body-content-types/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/request-body-content-types/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -172,9 +175,9 @@ public class PostXmlResponse { public class GetXmlResponseResponse { private final int statusCode; private final Response raw; - private final Payload status200; + private final String status200; - public GetXmlResponseResponse(int statusCode, Response raw, Payload status200) { + public GetXmlResponseResponse(int statusCode, Response raw, String status200) { this.statusCode = statusCode; this.raw = raw; this.status200 = status200; @@ -188,7 +191,7 @@ public class GetXmlResponseResponse { return this.raw; } - public Payload getStatus200() { + public String getStatus200() { return this.status200; } } @@ -209,8 +212,9 @@ public class DefaultApi { */ public PostBinaryResponse postBinary(byte[] body) throws IOException { String path = "/binary"; - String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + Request request; + RequestBody requestBody = RequestBody.create(body, MediaType.get("application/octet-stream")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { @@ -225,8 +229,9 @@ public class DefaultApi { */ public PostFormResponse postForm(Payload body) throws IOException { String path = "/form"; - String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + Request request; + throw new IllegalArgumentException("unsupported request body media type: application/x-www-form-urlencoded"); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { @@ -241,8 +246,10 @@ public class DefaultApi { */ public PostJsonResponse postJson(Payload body) throws IOException { String path = "/json"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { @@ -257,8 +264,10 @@ public class DefaultApi { */ public PostJsonOrXmlResponse postJsonOrXml(Payload body) throws IOException { String path = "/json-or-xml"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { @@ -273,8 +282,10 @@ public class DefaultApi { */ public PatchMergeJsonResponse patchMergeJson(Payload body) throws IOException { String path = "/merge-patch"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("PATCH", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/merge-patch+json")); + request = client.newRequestWithBody("PATCH", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { @@ -289,8 +300,9 @@ public class DefaultApi { */ public PostTextResponse postText(String body) throws IOException { String path = "/text"; - String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + Request request; + RequestBody requestBody = RequestBody.create(body, MediaType.get("text/plain")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { @@ -305,8 +317,9 @@ public class DefaultApi { */ public PostXmlResponse postXml(Payload body) throws IOException { String path = "/xml"; - String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + Request request; + throw new IllegalArgumentException("unsupported request body media type: application/xml"); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { @@ -328,10 +341,11 @@ public class DefaultApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; - Payload status200 = null; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); + String status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = responseText; } return new GetXmlResponseResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/request-body-content-types/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/request-body-content-types/runtime/ApiClient.java.golden index 75b3390dc..be7aefe50 100644 --- a/tests/golden/java/java-okhttp/request-body-content-types/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/request-body-content-types/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/reserved-word-fields/apis/ItemsApi.java.golden b/tests/golden/java/java-okhttp/reserved-word-fields/apis/ItemsApi.java.golden index ba698e000..d48d6c7cb 100644 --- a/tests/golden/java/java-okhttp/reserved-word-fields/apis/ItemsApi.java.golden +++ b/tests/golden/java/java-okhttp/reserved-word-fields/apis/ItemsApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -69,10 +70,11 @@ public class ItemsApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Item status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new GetItemsResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/reserved-word-fields/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/reserved-word-fields/runtime/ApiClient.java.golden index f443a48cf..71d00001a 100644 --- a/tests/golden/java/java-okhttp/reserved-word-fields/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/reserved-word-fields/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/response-body-default-and-exact/apis/WidgetApi.java.golden b/tests/golden/java/java-okhttp/response-body-default-and-exact/apis/WidgetApi.java.golden index 9336ed2d6..82407a350 100644 --- a/tests/golden/java/java-okhttp/response-body-default-and-exact/apis/WidgetApi.java.golden +++ b/tests/golden/java/java-okhttp/response-body-default-and-exact/apis/WidgetApi.java.golden @@ -5,6 +5,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -74,14 +75,15 @@ public class WidgetApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Widget status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } ErrorResponse default_ = null; if (true) { - default_ = gson.fromJson(responseBody, new TypeToken() {}.getType()); + default_ = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new GetWidgetResponse(response.code(), response, status200, default_); } diff --git a/tests/golden/java/java-okhttp/response-body-default-and-exact/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/response-body-default-and-exact/runtime/ApiClient.java.golden index 51d382968..43fdc496b 100644 --- a/tests/golden/java/java-okhttp/response-body-default-and-exact/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/response-body-default-and-exact/runtime/ApiClient.java.golden @@ -34,6 +34,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -46,11 +58,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/response-body-fallback/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/response-body-fallback/apis/DefaultApi.java.golden index 93c1a8239..713512b90 100644 --- a/tests/golden/java/java-okhttp/response-body-fallback/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/response-body-fallback/apis/DefaultApi.java.golden @@ -5,6 +5,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -105,10 +106,11 @@ public class DefaultApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); GetFallbackWithBodyResponse200 status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new GetFallbackWithBodyResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/response-body-fallback/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/response-body-fallback/runtime/ApiClient.java.golden index beb44be5e..61c458d26 100644 --- a/tests/golden/java/java-okhttp/response-body-fallback/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/response-body-fallback/runtime/ApiClient.java.golden @@ -34,6 +34,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -46,11 +58,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/response-body-multi-status-responses/apis/WidgetApi.java.golden b/tests/golden/java/java-okhttp/response-body-multi-status-responses/apis/WidgetApi.java.golden index 3847985e1..88d7febd8 100644 --- a/tests/golden/java/java-okhttp/response-body-multi-status-responses/apis/WidgetApi.java.golden +++ b/tests/golden/java/java-okhttp/response-body-multi-status-responses/apis/WidgetApi.java.golden @@ -5,6 +5,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -81,18 +82,19 @@ public class WidgetApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); List status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken>() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken>() {}.getType()); } WidgetListPartial status206 = null; if (response.code() == 206) { - status206 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status206 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } ErrorResponse status404 = null; if (response.code() == 404) { - status404 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status404 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new ListWidgetsResponse(response.code(), response, status200, status206, status404); } diff --git a/tests/golden/java/java-okhttp/response-body-multi-status-responses/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/response-body-multi-status-responses/runtime/ApiClient.java.golden index 3e6cb98b0..91e440afd 100644 --- a/tests/golden/java/java-okhttp/response-body-multi-status-responses/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/response-body-multi-status-responses/runtime/ApiClient.java.golden @@ -34,6 +34,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -46,11 +58,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/response-body-no-response-body/apis/FooApi.java.golden b/tests/golden/java/java-okhttp/response-body-no-response-body/apis/FooApi.java.golden index e979b157f..dd0d3933d 100644 --- a/tests/golden/java/java-okhttp/response-body-no-response-body/apis/FooApi.java.golden +++ b/tests/golden/java/java-okhttp/response-body-no-response-body/apis/FooApi.java.golden @@ -5,6 +5,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -15,7 +16,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -68,22 +71,25 @@ public class FooApi { */ public UpdateFooBarResponse updateFooBar(String fooId, FooRequest body) throws IOException { String path = "/foo/bar".replace("{foo_id}", fooId); + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("PATCH", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("PATCH", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); ErrorResponse status400 = null; if (response.code() == 400) { - status400 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status400 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } ErrorResponse status500 = null; if (response.code() == 500) { - status500 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status500 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new UpdateFooBarResponse(response.code(), response, status400, status500); } diff --git a/tests/golden/java/java-okhttp/response-body-no-response-body/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/response-body-no-response-body/runtime/ApiClient.java.golden index 6f3275759..6f7d08f22 100644 --- a/tests/golden/java/java-okhttp/response-body-no-response-body/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/response-body-no-response-body/runtime/ApiClient.java.golden @@ -34,6 +34,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -46,11 +58,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/server-object/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/server-object/apis/DefaultApi.java.golden index d2d622f65..f1ab44a40 100644 --- a/tests/golden/java/java-okhttp/server-object/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/server-object/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -104,10 +105,11 @@ public class DefaultApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); List status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken>() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken>() {}.getType()); } return new GetAllUsersResponse(response.code(), response, status200); } @@ -124,14 +126,15 @@ public class DefaultApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); GetUserByIdResponse200 status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } GetUserByIdResponse404 status404 = null; if (response.code() == 404) { - status404 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status404 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new GetUserByIdResponse(response.code(), response, status200, status404); } diff --git a/tests/golden/java/java-okhttp/server-object/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/server-object/runtime/ApiClient.java.golden index b2d512831..4ee41be0b 100644 --- a/tests/golden/java/java-okhttp/server-object/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/server-object/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/server-path-prefix/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/server-path-prefix/apis/DefaultApi.java.golden index b4a300d6b..5f0331857 100644 --- a/tests/golden/java/java-okhttp/server-path-prefix/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/server-path-prefix/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/tests/golden/java/java-okhttp/server-path-prefix/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/server-path-prefix/runtime/ApiClient.java.golden index 71294656a..4c50e7edc 100644 --- a/tests/golden/java/java-okhttp/server-path-prefix/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/server-path-prefix/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/type-aliases-complex-union/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/type-aliases-complex-union/apis/DefaultApi.java.golden index ce2ee590e..401e4532d 100644 --- a/tests/golden/java/java-okhttp/type-aliases-complex-union/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-complex-union/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -62,18 +65,21 @@ public class DefaultApi { */ public TestComplexUnionResponse testComplexUnion(Foo body) throws IOException { String path = "/test"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Foo status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new TestComplexUnionResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/type-aliases-complex-union/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/type-aliases-complex-union/runtime/ApiClient.java.golden index 9de577de5..34ed6d2c2 100644 --- a/tests/golden/java/java-okhttp/type-aliases-complex-union/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-complex-union/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-inline-discriminator-only/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-inline-discriminator-only/apis/DefaultApi.java.golden index 686796d14..021e0cfcc 100644 --- a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-inline-discriminator-only/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-inline-discriminator-only/apis/DefaultApi.java.golden @@ -15,6 +15,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -25,7 +26,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -71,18 +74,21 @@ public class DefaultApi { */ public CreateEventResponse createEvent(Event body) throws IOException { String path = "/event"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Event status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new CreateEventResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-inline-discriminator-only/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-inline-discriminator-only/runtime/ApiClient.java.golden index 5a40aa24a..84ad33807 100644 --- a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-inline-discriminator-only/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-inline-discriminator-only/runtime/ApiClient.java.golden @@ -44,6 +44,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -56,11 +68,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-internally-tagged/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-internally-tagged/apis/DefaultApi.java.golden index df4b6e391..f73b500b7 100644 --- a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-internally-tagged/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-internally-tagged/apis/DefaultApi.java.golden @@ -8,6 +8,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -18,7 +19,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -64,18 +67,21 @@ public class DefaultApi { */ public CreateResourceResponse createResource(Resource body) throws IOException { String path = "/resource"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Resource status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new CreateResourceResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-internally-tagged/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-internally-tagged/runtime/ApiClient.java.golden index 851da7c31..32ad7e5f6 100644 --- a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-internally-tagged/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-internally-tagged/runtime/ApiClient.java.golden @@ -37,6 +37,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -49,11 +61,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-long-names/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-long-names/apis/DefaultApi.java.golden index c31ddb5f4..5628a2e7c 100644 --- a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-long-names/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-long-names/apis/DefaultApi.java.golden @@ -8,6 +8,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -18,7 +19,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -65,18 +68,21 @@ public class DefaultApi { */ public CreateResourceResponse createResource(VeryLongDiscriminatedUnionTypeNameForTesting20260630 body) throws IOException { String path = "/resource"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); VeryLongDiscriminatedUnionTypeNameForTesting20260630 status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new CreateResourceResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-long-names/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-long-names/runtime/ApiClient.java.golden index 2aed2bfad..c298990f4 100644 --- a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-long-names/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-long-names/runtime/ApiClient.java.golden @@ -37,6 +37,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -49,11 +61,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/apis/DefaultApi.java.golden index 429a0708d..d21febc83 100644 --- a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/apis/DefaultApi.java.golden @@ -11,6 +11,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -21,7 +22,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -67,18 +70,21 @@ public class DefaultApi { */ public CreateResourceResponse createResource(Setup body) throws IOException { String path = "/resource"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Setup status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new CreateResourceResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/runtime/ApiClient.java.golden index 5b0325aa9..8780269b9 100644 --- a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/runtime/ApiClient.java.golden @@ -40,6 +40,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -52,11 +64,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-multiple/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-multiple/apis/DefaultApi.java.golden index 75597286a..93d319e54 100644 --- a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-multiple/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-multiple/apis/DefaultApi.java.golden @@ -9,6 +9,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -19,7 +20,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -92,18 +95,21 @@ public class DefaultApi { */ public CreateContainerResponse createContainer(ContainerKind body) throws IOException { String path = "/container"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); ContainerKind status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new CreateContainerResponse(response.code(), response, status200); } @@ -113,18 +119,21 @@ public class DefaultApi { */ public CreateVolumeResponse createVolume(VolumeKind body) throws IOException { String path = "/volume"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); VolumeKind status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new CreateVolumeResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-multiple/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-multiple/runtime/ApiClient.java.golden index cd9a0e58e..69e828515 100644 --- a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-multiple/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-multiple/runtime/ApiClient.java.golden @@ -38,6 +38,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -50,11 +62,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-with-refs/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-with-refs/apis/DefaultApi.java.golden index 3cb62f1a6..67a8d7bb6 100644 --- a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-with-refs/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-with-refs/apis/DefaultApi.java.golden @@ -7,6 +7,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -17,7 +18,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -63,18 +66,21 @@ public class DefaultApi { */ public CreateContainerResponse createContainer(ContainerImage body) throws IOException { String path = "/container"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); ContainerImage status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new CreateContainerResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-with-refs/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-with-refs/runtime/ApiClient.java.golden index 3fa3c17cf..99777634d 100644 --- a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-with-refs/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-with-refs/runtime/ApiClient.java.golden @@ -36,6 +36,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -48,11 +60,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/type-aliases-intersection-allof/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/type-aliases-intersection-allof/apis/DefaultApi.java.golden index 3f12a7602..e51f39d2a 100644 --- a/tests/golden/java/java-okhttp/type-aliases-intersection-allof/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-intersection-allof/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -62,18 +65,21 @@ public class DefaultApi { */ public TestIntersectionResponse testIntersection(Foo body) throws IOException { String path = "/test"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Foo status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new TestIntersectionResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/type-aliases-intersection-allof/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/type-aliases-intersection-allof/runtime/ApiClient.java.golden index 1833ffc8d..b1736f297 100644 --- a/tests/golden/java/java-okhttp/type-aliases-intersection-allof/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-intersection-allof/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/type-aliases-intersection-with-nullable-reference/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/type-aliases-intersection-with-nullable-reference/apis/DefaultApi.java.golden index b1beb3e11..402109114 100644 --- a/tests/golden/java/java-okhttp/type-aliases-intersection-with-nullable-reference/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-intersection-with-nullable-reference/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -89,18 +92,21 @@ public class DefaultApi { */ public CreateTestResponse createTest(Baz body) throws IOException { String path = "/test"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Baz status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new CreateTestResponse(response.code(), response, status200); } @@ -117,10 +123,11 @@ public class DefaultApi { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Baz status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new GetTestResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/type-aliases-intersection-with-nullable-reference/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/type-aliases-intersection-with-nullable-reference/runtime/ApiClient.java.golden index 81ba3b951..d5cb8e7d0 100644 --- a/tests/golden/java/java-okhttp/type-aliases-intersection-with-nullable-reference/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-intersection-with-nullable-reference/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/type-aliases-nested-union/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/type-aliases-nested-union/apis/DefaultApi.java.golden index 8276484d8..2c2f64d65 100644 --- a/tests/golden/java/java-okhttp/type-aliases-nested-union/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-nested-union/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -62,18 +65,21 @@ public class DefaultApi { */ public TestNestedUnionResponse testNestedUnion(Foo body) throws IOException { String path = "/test"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Foo status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new TestNestedUnionResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/type-aliases-nested-union/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/type-aliases-nested-union/runtime/ApiClient.java.golden index 567aa8f92..7f39189e3 100644 --- a/tests/golden/java/java-okhttp/type-aliases-nested-union/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-nested-union/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/type-aliases-simple-type-alias/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/type-aliases-simple-type-alias/apis/DefaultApi.java.golden index fae5ee0c0..9d72dabb4 100644 --- a/tests/golden/java/java-okhttp/type-aliases-simple-type-alias/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-simple-type-alias/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -62,18 +65,21 @@ public class DefaultApi { */ public TestSimpleAliasResponse testSimpleAlias(Foo body) throws IOException { String path = "/test"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Foo status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new TestSimpleAliasResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/type-aliases-simple-type-alias/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/type-aliases-simple-type-alias/runtime/ApiClient.java.golden index 7f9bb4737..4385885bc 100644 --- a/tests/golden/java/java-okhttp/type-aliases-simple-type-alias/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-simple-type-alias/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/type-aliases-union-mixed/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-mixed/apis/DefaultApi.java.golden index 3e7c94605..b539ae4f1 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-mixed/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-mixed/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -62,18 +65,21 @@ public class DefaultApi { */ public TestMixedUnionResponse testMixedUnion(Foo body) throws IOException { String path = "/test"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Foo status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new TestMixedUnionResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/type-aliases-union-mixed/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-mixed/runtime/ApiClient.java.golden index ca9def495..fb910cb6c 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-mixed/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-mixed/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/type-aliases-union-with-any/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-with-any/apis/DefaultApi.java.golden index 409f2a82f..2411080d0 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-with-any/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-with-any/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -62,18 +65,21 @@ public class DefaultApi { */ public TestUnionWithAnyResponse testUnionWithAny(Config body) throws IOException { String path = "/test"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Config status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new TestUnionWithAnyResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/type-aliases-union-with-any/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-with-any/runtime/ApiClient.java.golden index 3ca95b1ae..f025cbcaf 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-with-any/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-with-any/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/type-aliases-union-with-inline-objects/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-with-inline-objects/apis/DefaultApi.java.golden index cc5116073..74b00aea6 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-with-inline-objects/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-with-inline-objects/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -62,18 +65,21 @@ public class DefaultApi { */ public TestUnionWithInlineObjectsResponse testUnionWithInlineObjects(Foo body) throws IOException { String path = "/test"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Foo status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new TestUnionWithInlineObjectsResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/type-aliases-union-with-inline-objects/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-with-inline-objects/runtime/ApiClient.java.golden index 6c8ab919c..281f5fbef 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-with-inline-objects/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-with-inline-objects/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/type-aliases-union-with-interfaces/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-with-interfaces/apis/DefaultApi.java.golden index bfe57577c..0627890fe 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-with-interfaces/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-with-interfaces/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -62,18 +65,21 @@ public class DefaultApi { */ public TestUnionResponse testUnion(Foo body) throws IOException { String path = "/test"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Foo status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new TestUnionResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/type-aliases-union-with-interfaces/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-with-interfaces/runtime/ApiClient.java.golden index c24d0f74f..d9aa6fbb2 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-with-interfaces/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-with-interfaces/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/java/java-okhttp/type-aliases-union-with-primitives/apis/DefaultApi.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-with-primitives/apis/DefaultApi.java.golden index 3f7706d0d..fd0db23c6 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-with-primitives/apis/DefaultApi.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-with-primitives/apis/DefaultApi.java.golden @@ -6,6 +6,7 @@ package com.example.sdk.apis; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,7 +17,9 @@ import com.example.sdk.runtime.ApiClient; import com.example.sdk.runtime.ApiException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import okhttp3.MediaType; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; /** @@ -62,18 +65,21 @@ public class DefaultApi { */ public TestUnionPrimitivesResponse testUnionPrimitives(Foo body) throws IOException { String path = "/test"; + Request request; String jsonBody = gson.toJson(body); - Request request = client.newRequest("POST", path, null, jsonBody); + RequestBody requestBody = RequestBody.create(jsonBody, MediaType.get("application/json")); + request = client.newRequestWithBody("POST", path, null, requestBody); Response response = client.execute(request); if (!response.isSuccessful()) { String errorBody = response.body() != null ? response.body().string() : ""; throw new ApiException(response.code(), response.message(), errorBody); } - String responseBody = response.body() != null ? response.body().string() : "null"; + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); Foo status200 = null; if (response.code() == 200) { - status200 = gson.fromJson(responseBody, new TypeToken() {}.getType()); + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); } return new TestUnionPrimitivesResponse(response.code(), response, status200); } diff --git a/tests/golden/java/java-okhttp/type-aliases-union-with-primitives/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-with-primitives/runtime/ApiClient.java.golden index 02df9817f..dd5d34564 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-with-primitives/runtime/ApiClient.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-with-primitives/runtime/ApiClient.java.golden @@ -35,6 +35,18 @@ public class ApiClient { } public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { String url = baseUrl.replaceAll("/+$", "") + path; HttpUrl parsed = HttpUrl.parse(url); if (parsed == null) { @@ -47,11 +59,6 @@ public class ApiClient { } } - RequestBody requestBody = null; - if (body != null) { - requestBody = RequestBody.create(body, JSON); - } - Request.Builder builder = new Request.Builder() .url(urlBuilder.build()) .method(method, requestBody); diff --git a/tests/golden/kotlin/kotlin-okhttp/additional-properties/apis/AdditionalPropertiesApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/additional-properties/apis/AdditionalPropertiesApi.kt.golden index aee6c201b..67b8e3582 100644 --- a/tests/golden/kotlin/kotlin-okhttp/additional-properties/apis/AdditionalPropertiesApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/additional-properties/apis/AdditionalPropertiesApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -43,16 +45,18 @@ class AdditionalPropertiesApi(private val client: ApiClient) { fun postLeaf(body: LeafValue): PostLeafResponse { val path = "/leaf" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return PostLeafResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -62,16 +66,18 @@ class AdditionalPropertiesApi(private val client: ApiClient) { fun postMiddle(body: MiddleLevel): PostMiddleResponse { val path = "/middle" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return PostMiddleResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -81,16 +87,18 @@ class AdditionalPropertiesApi(private val client: ApiClient) { fun postRoot(body: RootLevel): PostRootResponse { val path = "/root" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return PostRootResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/additional-properties/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/additional-properties/runtime/ApiClient.kt.golden index a69d04cf7..e09a2e8b4 100644 --- a/tests/golden/kotlin/kotlin-okhttp/additional-properties/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/additional-properties/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/binary-transfer-media-types/apis/TransferApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/binary-transfer-media-types/apis/TransferApi.kt.golden index 3a1736263..796954050 100644 --- a/tests/golden/kotlin/kotlin-okhttp/binary-transfer-media-types/apis/TransferApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/binary-transfer-media-types/apis/TransferApi.kt.golden @@ -10,13 +10,16 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** * DownloadAssetResponse carries the response from downloadAsset. */ data class DownloadAssetResponse(val statusCode: Int, val raw: Response, -val status200: List? = null) { +val status200: ByteArray? = null) { } /** @@ -44,9 +47,10 @@ class TransferApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson>(responseBody, object : TypeToken>() {}.type) else null + val status200 = if (response.code == 200) responseBytes else null return DownloadAssetResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -55,17 +59,22 @@ class TransferApi(private val client: ApiClient) { */ fun uploadAsset(body: UploadAssetRequest): UploadAssetResponse { val path = "/uploads" - val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val multipartBuilder = MultipartBody.Builder().setType(MultipartBody.FORM) + multipartBuilder.addFormDataPart("file", "file", body.file.toRequestBody("application/octet-stream".toMediaType())) + multipartBuilder.addFormDataPart("metadata", gson.toJson(body.metadata)) + multipartBuilder.addFormDataPart("purpose", body.purpose.toString()) + val multipartBody = multipartBuilder.build() + val request = client.newRequestWithBody("POST", path, null, multipartBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return UploadAssetResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/binary-transfer-media-types/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/binary-transfer-media-types/runtime/ApiClient.kt.golden index 725d83b47..08be1fb08 100644 --- a/tests/golden/kotlin/kotlin-okhttp/binary-transfer-media-types/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/binary-transfer-media-types/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/runtime/ApiClient.kt.golden index 81be2faf9..a4fc1e29f 100644 --- a/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/delete-with-response-schema/apis/TestResourceApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/delete-with-response-schema/apis/TestResourceApi.kt.golden index 1057a63d0..28127f1e8 100644 --- a/tests/golden/kotlin/kotlin-okhttp/delete-with-response-schema/apis/TestResourceApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/delete-with-response-schema/apis/TestResourceApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -40,16 +42,18 @@ class TestResourceApi(private val client: ApiClient) { fun createTestResource(body: CreateTestResourceRequest): CreateTestResourceResponse { val path = "/v1/api/test-resource" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return CreateTestResourceResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -65,13 +69,14 @@ class TestResourceApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null - val status4xx = if (response.code in 400..499) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status4xx = if (response.code in 400..499) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null - val status5xx = if (response.code in 500..599) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status5xx = if (response.code in 500..599) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return DeleteTestResourceResponse(statusCode = response.code, raw = response, status200 = status200, status4xx = status4xx, status5xx = status5xx) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/delete-with-response-schema/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/delete-with-response-schema/runtime/ApiClient.kt.golden index f9a22d73d..70a2bcaad 100644 --- a/tests/golden/kotlin/kotlin-okhttp/delete-with-response-schema/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/delete-with-response-schema/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/duplicate-param-names/apis/ItemsApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/duplicate-param-names/apis/ItemsApi.kt.golden index b1020ab17..127071531 100644 --- a/tests/golden/kotlin/kotlin-okhttp/duplicate-param-names/apis/ItemsApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/duplicate-param-names/apis/ItemsApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -36,16 +38,18 @@ class ItemsApi(private val client: ApiClient) { query["body"] = body } val jsonBody = gson.toJson(body2) - val request = client.newRequest("POST", path, query, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, query, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return CreateItemWithBodyConflictResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/duplicate-param-names/apis/UsersApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/duplicate-param-names/apis/UsersApi.kt.golden index 893690336..2669f78bc 100644 --- a/tests/golden/kotlin/kotlin-okhttp/duplicate-param-names/apis/UsersApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/duplicate-param-names/apis/UsersApi.kt.golden @@ -45,9 +45,10 @@ class UsersApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return GetUserByIdDuplicateResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/duplicate-param-names/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/duplicate-param-names/runtime/ApiClient.kt.golden index 440293dd5..e226b2382 100644 --- a/tests/golden/kotlin/kotlin-okhttp/duplicate-param-names/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/duplicate-param-names/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/enum-repr/apis/EnumReprApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/enum-repr/apis/EnumReprApi.kt.golden index 89b5e88ae..52a2293ba 100644 --- a/tests/golden/kotlin/kotlin-okhttp/enum-repr/apis/EnumReprApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/enum-repr/apis/EnumReprApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -59,16 +61,18 @@ class EnumReprApi(private val client: ApiClient) { fun handleAdjacentlyTagged(body: AdjacentlyTaggedEnum): HandleAdjacentlyTaggedResponse { val path = "/adjacently-tagged" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return HandleAdjacentlyTaggedResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -78,16 +82,18 @@ class EnumReprApi(private val client: ApiClient) { fun handleExternallyTagged(body: ExternallyTaggedEnum): HandleExternallyTaggedResponse { val path = "/externally-tagged" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return HandleExternallyTaggedResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -97,16 +103,18 @@ class EnumReprApi(private val client: ApiClient) { fun handleInternallyTagged(body: InternallyTaggedEnum): HandleInternallyTaggedResponse { val path = "/internally-tagged" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return HandleInternallyTaggedResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -116,16 +124,18 @@ class EnumReprApi(private val client: ApiClient) { fun handleMixed(body: MixedEnum): HandleMixedResponse { val path = "/mixed" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return HandleMixedResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -135,16 +145,18 @@ class EnumReprApi(private val client: ApiClient) { fun handleUntagged(body: UntaggedEnum): HandleUntaggedResponse { val path = "/untagged" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return HandleUntaggedResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/enum-repr/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/enum-repr/runtime/ApiClient.kt.golden index b55b665d6..398327555 100644 --- a/tests/golden/kotlin/kotlin-okhttp/enum-repr/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/enum-repr/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/interface-with-enum-reference/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/interface-with-enum-reference/runtime/ApiClient.kt.golden index 4d9cb7074..b276f3cda 100644 --- a/tests/golden/kotlin/kotlin-okhttp/interface-with-enum-reference/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/interface-with-enum-reference/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/media-type-selection/README.md.golden b/tests/golden/kotlin/kotlin-okhttp/media-type-selection/README.md.golden new file mode 100644 index 000000000..7a446d9d1 --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/media-type-selection/README.md.golden @@ -0,0 +1,7 @@ +# Media Type Selection + +Covers normalized media-type selection for requests and responses. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `media-type-selection`. diff --git a/tests/golden/kotlin/kotlin-okhttp/media-type-selection/apis/MediaApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/media-type-selection/apis/MediaApi.kt.golden new file mode 100644 index 000000000..82be6646b --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/media-type-selection/apis/MediaApi.kt.golden @@ -0,0 +1,174 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package com.example.sdk.apis + +import com.example.sdk.models.* +import com.example.sdk.runtime.ApiClient +import com.example.sdk.runtime.ApiException +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response + +/** + * SendJsonPreferredResponse carries the response from sendJsonPreferred. + */ +data class SendJsonPreferredResponse(val statusCode: Int, val raw: Response) { +} + +/** + * SendParameterizedMultipartResponse carries the response from sendParameterizedMultipart. + */ +data class SendParameterizedMultipartResponse(val statusCode: Int, val raw: Response) { +} + +/** + * SendVendorJsonResponse carries the response from sendVendorJson. + */ +data class SendVendorJsonResponse(val statusCode: Int, val raw: Response) { +} + +/** + * GetOctetPreferredResponse carries the response from getOctetPreferred. + */ +data class GetOctetPreferredResponse(val statusCode: Int, val raw: Response, +val status200: ByteArray? = null) { +} + +/** + * GetTextPreferredResponse carries the response from getTextPreferred. + */ +data class GetTextPreferredResponse(val statusCode: Int, val raw: Response, +val status200: String? = null) { +} + +/** + * GetVendorJsonResponse carries the response from getVendorJson. + */ +data class GetVendorJsonResponse(val statusCode: Int, val raw: Response, +val status200: Payload? = null) { +} + +/** + * MediaApi groups operations under the media tag. + */ +class MediaApi(private val client: ApiClient) { + private val gson: Gson = Gson() + + /** + * sendJsonPreferred POST /request/json-vs-multipart. + */ + fun sendJsonPreferred(body: Payload): SendJsonPreferredResponse { + val path = "/request/json-vs-multipart" + val jsonBody = gson.toJson(body) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) + val response = client.execute(request) + + if (!response.isSuccessful) { + val errorBody = response.body?.string() ?: "" + throw ApiException(response.code, response.message, errorBody) + } + return SendJsonPreferredResponse(statusCode = response.code, raw = response) + } + + /** + * sendParameterizedMultipart POST /request/parameterized-multipart. + */ + fun sendParameterizedMultipart(body: FileEnvelope): SendParameterizedMultipartResponse { + val path = "/request/parameterized-multipart" + val multipartBuilder = MultipartBody.Builder().setType(MultipartBody.FORM) + multipartBuilder.addFormDataPart("file", "file", body.file.toRequestBody("application/octet-stream".toMediaType())) + if (body.note != null) { + multipartBuilder.addFormDataPart("note", body.note.toString()) + } + val multipartBody = multipartBuilder.build() + val request = client.newRequestWithBody("POST", path, null, multipartBody) + val response = client.execute(request) + + if (!response.isSuccessful) { + val errorBody = response.body?.string() ?: "" + throw ApiException(response.code, response.message, errorBody) + } + return SendParameterizedMultipartResponse(statusCode = response.code, raw = response) + } + + /** + * sendVendorJson PATCH /request/vendor-json. + */ + fun sendVendorJson(body: Payload): SendVendorJsonResponse { + val path = "/request/vendor-json" + val jsonBody = gson.toJson(body) + val requestBody = jsonBody.toRequestBody("application/vnd.example+json; charset=utf-8".toMediaType()) + val request = client.newRequestWithBody("PATCH", path, null, requestBody) + val response = client.execute(request) + + if (!response.isSuccessful) { + val errorBody = response.body?.string() ?: "" + throw ApiException(response.code, response.message, errorBody) + } + return SendVendorJsonResponse(statusCode = response.code, raw = response) + } + + /** + * getOctetPreferred GET /response/octet-before-xml. + */ + fun getOctetPreferred(): GetOctetPreferredResponse { + val path = "/response/octet-before-xml" + val request = client.newRequest("GET", path, null, null) + val response = client.execute(request) + + if (!response.isSuccessful) { + val errorBody = response.body?.string() ?: "" + throw ApiException(response.code, response.message, errorBody) + } + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) + + val status200 = if (response.code == 200) responseBytes else null + return GetOctetPreferredResponse(statusCode = response.code, raw = response, status200 = status200) + } + + /** + * getTextPreferred GET /response/text-before-xml. + */ + fun getTextPreferred(): GetTextPreferredResponse { + val path = "/response/text-before-xml" + val request = client.newRequest("GET", path, null, null) + val response = client.execute(request) + + if (!response.isSuccessful) { + val errorBody = response.body?.string() ?: "" + throw ApiException(response.code, response.message, errorBody) + } + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) + + val status200 = if (response.code == 200) responseText else null + return GetTextPreferredResponse(statusCode = response.code, raw = response, status200 = status200) + } + + /** + * getVendorJson GET /response/vendor-json. + */ + fun getVendorJson(): GetVendorJsonResponse { + val path = "/response/vendor-json" + val request = client.newRequest("GET", path, null, null) + val response = client.execute(request) + + if (!response.isSuccessful) { + val errorBody = response.body?.string() ?: "" + throw ApiException(response.code, response.message, errorBody) + } + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) + + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null + return GetVendorJsonResponse(statusCode = response.code, raw = response, status200 = status200) + } +} diff --git a/tests/golden/kotlin/kotlin-okhttp/media-type-selection/build.gradle.kts.golden b/tests/golden/kotlin/kotlin-okhttp/media-type-selection/build.gradle.kts.golden new file mode 100644 index 000000000..5c6106455 --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/media-type-selection/build.gradle.kts.golden @@ -0,0 +1,19 @@ +plugins { + kotlin("jvm") version "1.9.22" +} + +group = "com.example.sdk" +version = "1.0.0" + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.squareup.okhttp3:okhttp:4.12.0") + implementation("com.google.code.gson:gson:2.11.0") +} + +kotlin { + jvmToolchain(17) +} diff --git a/tests/golden/kotlin/kotlin-okhttp/media-type-selection/models/FileEnvelope.kt.golden b/tests/golden/kotlin/kotlin-okhttp/media-type-selection/models/FileEnvelope.kt.golden new file mode 100644 index 000000000..05b98ef28 --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/media-type-selection/models/FileEnvelope.kt.golden @@ -0,0 +1,9 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package com.example.sdk.models + +data class FileEnvelope(val file: ByteArray, val note: String? = null) { +} diff --git a/tests/golden/kotlin/kotlin-okhttp/media-type-selection/models/Payload.kt.golden b/tests/golden/kotlin/kotlin-okhttp/media-type-selection/models/Payload.kt.golden new file mode 100644 index 000000000..666e20e57 --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/media-type-selection/models/Payload.kt.golden @@ -0,0 +1,9 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package com.example.sdk.models + +data class Payload(val count: Int? = null, val id: String) { +} diff --git a/tests/golden/kotlin/kotlin-okhttp/media-type-selection/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/media-type-selection/runtime/ApiClient.kt.golden new file mode 100644 index 000000000..1cea7a03a --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/media-type-selection/runtime/ApiClient.kt.golden @@ -0,0 +1,62 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package com.example.sdk.runtime + +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response + +class ApiClient( + private val baseUrl: String, + private val client: OkHttpClient = OkHttpClient(), + private val authenticator: Authenticator? = null, + private val defaultHeaders: Map = emptyMap(), +) { + fun newRequest( + method: String, + path: String, + query: Map? = null, + body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() + query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } + + val builder = Request.Builder() + .url(urlBuilder.build()) + .method(method, body) + + defaultHeaders.forEach { (k, v) -> builder.header(k, v) } + authenticator?.authenticate(builder) + + return builder.build() + } + + fun execute(request: Request): Response { + return client.newCall(request).execute() + } +} diff --git a/tests/golden/kotlin/kotlin-okhttp/media-type-selection/runtime/ApiException.kt.golden b/tests/golden/kotlin/kotlin-okhttp/media-type-selection/runtime/ApiException.kt.golden new file mode 100644 index 000000000..4560a9df5 --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/media-type-selection/runtime/ApiException.kt.golden @@ -0,0 +1,12 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package com.example.sdk.runtime + +class ApiException( + val statusCode: Int, + val status: String, + val body: String, +) : RuntimeException("API error $statusCode: $status") diff --git a/tests/golden/kotlin/kotlin-okhttp/media-type-selection/runtime/Auth.kt.golden b/tests/golden/kotlin/kotlin-okhttp/media-type-selection/runtime/Auth.kt.golden new file mode 100644 index 000000000..df6aabacf --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/media-type-selection/runtime/Auth.kt.golden @@ -0,0 +1,71 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +package com.example.sdk.runtime + +import okhttp3.Request + +interface Authenticator { + fun authenticate(builder: Request.Builder) +} + +class BearerAuth : Authenticator { + private val token: String? + private val tokenProvider: (() -> String)? + + constructor(token: String) { + this.token = token + this.tokenProvider = null + } + + constructor(tokenProvider: () -> String) { + this.token = null + this.tokenProvider = tokenProvider + } + + override fun authenticate(builder: Request.Builder) { + val t = tokenProvider?.invoke() ?: token!! + builder.header("Authorization", "Bearer $t") + } +} + +class ApiKeyAuth : Authenticator { + private val key: String? + private val keyProvider: (() -> String)? + private val name: String + private val location: ApiKeyLocation + + constructor(key: String, name: String, location: ApiKeyLocation) { + this.key = key + this.keyProvider = null + this.name = name + this.location = location + } + + constructor(keyProvider: () -> String, name: String, location: ApiKeyLocation) { + this.key = null + this.keyProvider = keyProvider + this.name = name + this.location = location + } + + override fun authenticate(builder: Request.Builder) { + val k = keyProvider?.invoke() ?: key!! + when (location) { + ApiKeyLocation.HEADER -> builder.header(name, k) + ApiKeyLocation.QUERY -> { + val url = builder.build().url.newBuilder() + .addQueryParameter(name, k) + .build() + builder.url(url) + } + } + } +} + +enum class ApiKeyLocation { + HEADER, + QUERY, +} diff --git a/tests/golden/kotlin/kotlin-okhttp/minimal/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/minimal/runtime/ApiClient.kt.golden index 525027be5..444c78e70 100644 --- a/tests/golden/kotlin/kotlin-okhttp/minimal/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/minimal/runtime/ApiClient.kt.golden @@ -23,15 +23,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/README.md.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/README.md.golden new file mode 100644 index 000000000..61745a51d --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Edge Cases + +Covers optional multipart bodies, optional parts, and text-only multipart fields. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart-edge-cases`. diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/apis/MultipartApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/apis/MultipartApi.kt.golden new file mode 100644 index 000000000..e51414e14 --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/apis/MultipartApi.kt.golden @@ -0,0 +1,90 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package com.example.sdk.apis + +import com.example.sdk.models.* +import com.example.sdk.runtime.ApiClient +import com.example.sdk.runtime.ApiException +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response + +/** + * SendOptionalPartsResponse carries the response from sendOptionalParts. + */ +data class SendOptionalPartsResponse(val statusCode: Int, val raw: Response) { +} + +/** + * SendTextFieldsResponse carries the response from sendTextFields. + */ +data class SendTextFieldsResponse(val statusCode: Int, val raw: Response) { +} + +/** + * MultipartApi groups operations under the multipart tag. + */ +class MultipartApi(private val client: ApiClient) { + private val gson: Gson = Gson() + + /** + * sendOptionalParts POST /multipart/optional. + */ + fun sendOptionalParts(body: OptionalUpload?): SendOptionalPartsResponse { + val path = "/multipart/optional" + var multipartBody = ByteArray(0).toRequestBody(null) + if (body != null) { + val multipartBuilder = MultipartBody.Builder().setType(MultipartBody.FORM) + if (body.attributes != null) { + multipartBuilder.addFormDataPart("attributes", gson.toJson(body.attributes)) + } + if (body.enabled != null) { + multipartBuilder.addFormDataPart("enabled", body.enabled.toString()) + } + if (body.file != null) { + multipartBuilder.addFormDataPart("file", "file", body.file.toRequestBody("application/octet-stream".toMediaType())) + } + if (body.retryCount != null) { + multipartBuilder.addFormDataPart("retry_count", body.retryCount.toString()) + } + if (body.title != null) { + multipartBuilder.addFormDataPart("title", body.title.toString()) + } + multipartBody = multipartBuilder.build() + } + val request = client.newRequestWithBody("POST", path, null, multipartBody) + val response = client.execute(request) + + if (!response.isSuccessful) { + val errorBody = response.body?.string() ?: "" + throw ApiException(response.code, response.message, errorBody) + } + return SendOptionalPartsResponse(statusCode = response.code, raw = response) + } + + /** + * sendTextFields POST /multipart/text-only. + */ + fun sendTextFields(body: TextFields): SendTextFieldsResponse { + val path = "/multipart/text-only" + val multipartBuilder = MultipartBody.Builder().setType(MultipartBody.FORM) + multipartBuilder.addFormDataPart("enabled", body.enabled.toString()) + multipartBuilder.addFormDataPart("note", body.note.toString()) + multipartBuilder.addFormDataPart("retry_count", body.retryCount.toString()) + val multipartBody = multipartBuilder.build() + val request = client.newRequestWithBody("POST", path, null, multipartBody) + val response = client.execute(request) + + if (!response.isSuccessful) { + val errorBody = response.body?.string() ?: "" + throw ApiException(response.code, response.message, errorBody) + } + return SendTextFieldsResponse(statusCode = response.code, raw = response) + } +} diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/build.gradle.kts.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/build.gradle.kts.golden new file mode 100644 index 000000000..5c6106455 --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/build.gradle.kts.golden @@ -0,0 +1,19 @@ +plugins { + kotlin("jvm") version "1.9.22" +} + +group = "com.example.sdk" +version = "1.0.0" + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.squareup.okhttp3:okhttp:4.12.0") + implementation("com.google.code.gson:gson:2.11.0") +} + +kotlin { + jvmToolchain(17) +} diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/models/Attributes.kt.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/models/Attributes.kt.golden new file mode 100644 index 000000000..9ace01a0c --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/models/Attributes.kt.golden @@ -0,0 +1,9 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package com.example.sdk.models + +data class Attributes(val label: String, val priority: Int? = null) { +} diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/models/OptionalUpload.kt.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/models/OptionalUpload.kt.golden new file mode 100644 index 000000000..7fd80f416 --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/models/OptionalUpload.kt.golden @@ -0,0 +1,14 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package com.example.sdk.models + +import com.google.gson.annotations.SerializedName + +data class OptionalUpload(val attributes: Attributes? = null, val enabled: Boolean? = null, +val file: ByteArray? = null, +@SerializedName("retry_count") val retryCount: Int? = null, +val title: String? = null) { +} diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/models/TextFields.kt.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/models/TextFields.kt.golden new file mode 100644 index 000000000..e2df29cea --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/models/TextFields.kt.golden @@ -0,0 +1,12 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package com.example.sdk.models + +import com.google.gson.annotations.SerializedName + +data class TextFields(val enabled: Boolean, val note: String, +@SerializedName("retry_count") val retryCount: Int) { +} diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/runtime/ApiClient.kt.golden new file mode 100644 index 000000000..fc7478b8a --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/runtime/ApiClient.kt.golden @@ -0,0 +1,62 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package com.example.sdk.runtime + +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response + +class ApiClient( + private val baseUrl: String, + private val client: OkHttpClient = OkHttpClient(), + private val authenticator: Authenticator? = null, + private val defaultHeaders: Map = emptyMap(), +) { + fun newRequest( + method: String, + path: String, + query: Map? = null, + body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() + query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } + + val builder = Request.Builder() + .url(urlBuilder.build()) + .method(method, body) + + defaultHeaders.forEach { (k, v) -> builder.header(k, v) } + authenticator?.authenticate(builder) + + return builder.build() + } + + fun execute(request: Request): Response { + return client.newCall(request).execute() + } +} diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/runtime/ApiException.kt.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/runtime/ApiException.kt.golden new file mode 100644 index 000000000..f0aeb6992 --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/runtime/ApiException.kt.golden @@ -0,0 +1,12 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package com.example.sdk.runtime + +class ApiException( + val statusCode: Int, + val status: String, + val body: String, +) : RuntimeException("API error $statusCode: $status") diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/runtime/Auth.kt.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/runtime/Auth.kt.golden new file mode 100644 index 000000000..025dcca3d --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-edge-cases/runtime/Auth.kt.golden @@ -0,0 +1,71 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +package com.example.sdk.runtime + +import okhttp3.Request + +interface Authenticator { + fun authenticate(builder: Request.Builder) +} + +class BearerAuth : Authenticator { + private val token: String? + private val tokenProvider: (() -> String)? + + constructor(token: String) { + this.token = token + this.tokenProvider = null + } + + constructor(tokenProvider: () -> String) { + this.token = null + this.tokenProvider = tokenProvider + } + + override fun authenticate(builder: Request.Builder) { + val t = tokenProvider?.invoke() ?: token!! + builder.header("Authorization", "Bearer $t") + } +} + +class ApiKeyAuth : Authenticator { + private val key: String? + private val keyProvider: (() -> String)? + private val name: String + private val location: ApiKeyLocation + + constructor(key: String, name: String, location: ApiKeyLocation) { + this.key = key + this.keyProvider = null + this.name = name + this.location = location + } + + constructor(keyProvider: () -> String, name: String, location: ApiKeyLocation) { + this.key = null + this.keyProvider = keyProvider + this.name = name + this.location = location + } + + override fun authenticate(builder: Request.Builder) { + val k = keyProvider?.invoke() ?: key!! + when (location) { + ApiKeyLocation.HEADER -> builder.header(name, k) + ApiKeyLocation.QUERY -> { + val url = builder.build().url.newBuilder() + .addQueryParameter(name, k) + .build() + builder.url(url) + } + } + } +} + +enum class ApiKeyLocation { + HEADER, + QUERY, +} diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/README.md.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/README.md.golden new file mode 100644 index 000000000..2899b40d5 --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Nested Object Parts + +Covers multipart object parts whose wire names differ from ergonomic names. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart-nested-object-parts`. diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/apis/MultipartApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/apis/MultipartApi.kt.golden new file mode 100644 index 000000000..3bdc940fa --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/apis/MultipartApi.kt.golden @@ -0,0 +1,48 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package com.example.sdk.apis + +import com.example.sdk.models.* +import com.example.sdk.runtime.ApiClient +import com.example.sdk.runtime.ApiException +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response + +/** + * SendNestedObjectPartResponse carries the response from sendNestedObjectPart. + */ +data class SendNestedObjectPartResponse(val statusCode: Int, val raw: Response) { +} + +/** + * MultipartApi groups operations under the multipart tag. + */ +class MultipartApi(private val client: ApiClient) { + private val gson: Gson = Gson() + + /** + * sendNestedObjectPart POST /multipart/nested-object. + */ + fun sendNestedObjectPart(body: NestedUpload): SendNestedObjectPartResponse { + val path = "/multipart/nested-object" + val multipartBuilder = MultipartBody.Builder().setType(MultipartBody.FORM) + multipartBuilder.addFormDataPart("file", "file", body.file.toRequestBody("application/octet-stream".toMediaType())) + multipartBuilder.addFormDataPart("item_config", gson.toJson(body.itemConfig)) + val multipartBody = multipartBuilder.build() + val request = client.newRequestWithBody("POST", path, null, multipartBody) + val response = client.execute(request) + + if (!response.isSuccessful) { + val errorBody = response.body?.string() ?: "" + throw ApiException(response.code, response.message, errorBody) + } + return SendNestedObjectPartResponse(statusCode = response.code, raw = response) + } +} diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/build.gradle.kts.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/build.gradle.kts.golden new file mode 100644 index 000000000..5c6106455 --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/build.gradle.kts.golden @@ -0,0 +1,19 @@ +plugins { + kotlin("jvm") version "1.9.22" +} + +group = "com.example.sdk" +version = "1.0.0" + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.squareup.okhttp3:okhttp:4.12.0") + implementation("com.google.code.gson:gson:2.11.0") +} + +kotlin { + jvmToolchain(17) +} diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/models/ItemConfig.kt.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/models/ItemConfig.kt.golden new file mode 100644 index 000000000..4255546fc --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/models/ItemConfig.kt.golden @@ -0,0 +1,12 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package com.example.sdk.models + +import com.google.gson.annotations.SerializedName + +data class ItemConfig(@SerializedName("display_name") val displayName: String, +@SerializedName("retention_days") val retentionDays: Int? = null) { +} diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/models/NestedUpload.kt.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/models/NestedUpload.kt.golden new file mode 100644 index 000000000..252b7c3cf --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/models/NestedUpload.kt.golden @@ -0,0 +1,12 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package com.example.sdk.models + +import com.google.gson.annotations.SerializedName + +data class NestedUpload(val file: ByteArray, +@SerializedName("item_config") val itemConfig: ItemConfig) { +} diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/runtime/ApiClient.kt.golden new file mode 100644 index 000000000..bb5c6cd79 --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/runtime/ApiClient.kt.golden @@ -0,0 +1,62 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package com.example.sdk.runtime + +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response + +class ApiClient( + private val baseUrl: String, + private val client: OkHttpClient = OkHttpClient(), + private val authenticator: Authenticator? = null, + private val defaultHeaders: Map = emptyMap(), +) { + fun newRequest( + method: String, + path: String, + query: Map? = null, + body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() + query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } + + val builder = Request.Builder() + .url(urlBuilder.build()) + .method(method, body) + + defaultHeaders.forEach { (k, v) -> builder.header(k, v) } + authenticator?.authenticate(builder) + + return builder.build() + } + + fun execute(request: Request): Response { + return client.newCall(request).execute() + } +} diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/runtime/ApiException.kt.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/runtime/ApiException.kt.golden new file mode 100644 index 000000000..fd861d339 --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/runtime/ApiException.kt.golden @@ -0,0 +1,12 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package com.example.sdk.runtime + +class ApiException( + val statusCode: Int, + val status: String, + val body: String, +) : RuntimeException("API error $statusCode: $status") diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/runtime/Auth.kt.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/runtime/Auth.kt.golden new file mode 100644 index 000000000..9f8ad3bd7 --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-nested-object-parts/runtime/Auth.kt.golden @@ -0,0 +1,71 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +package com.example.sdk.runtime + +import okhttp3.Request + +interface Authenticator { + fun authenticate(builder: Request.Builder) +} + +class BearerAuth : Authenticator { + private val token: String? + private val tokenProvider: (() -> String)? + + constructor(token: String) { + this.token = token + this.tokenProvider = null + } + + constructor(tokenProvider: () -> String) { + this.token = null + this.tokenProvider = tokenProvider + } + + override fun authenticate(builder: Request.Builder) { + val t = tokenProvider?.invoke() ?: token!! + builder.header("Authorization", "Bearer $t") + } +} + +class ApiKeyAuth : Authenticator { + private val key: String? + private val keyProvider: (() -> String)? + private val name: String + private val location: ApiKeyLocation + + constructor(key: String, name: String, location: ApiKeyLocation) { + this.key = key + this.keyProvider = null + this.name = name + this.location = location + } + + constructor(keyProvider: () -> String, name: String, location: ApiKeyLocation) { + this.key = null + this.keyProvider = keyProvider + this.name = name + this.location = location + } + + override fun authenticate(builder: Request.Builder) { + val k = keyProvider?.invoke() ?: key!! + when (location) { + ApiKeyLocation.HEADER -> builder.header(name, k) + ApiKeyLocation.QUERY -> { + val url = builder.build().url.newBuilder() + .addQueryParameter(name, k) + .build() + builder.url(url) + } + } + } +} + +enum class ApiKeyLocation { + HEADER, + QUERY, +} diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/README.md.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/README.md.golden new file mode 100644 index 000000000..60a3a2357 --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Unsupported Schema + +Covers multipart request bodies that are not object-shaped. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart-unsupported-schema`. diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/apis/TransferApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/apis/TransferApi.kt.golden new file mode 100644 index 000000000..5d357a5be --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/apis/TransferApi.kt.golden @@ -0,0 +1,43 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Unsupported Schema — 1.0.0 +// Covers multipart request bodies that are not object-shaped. + +package com.example.sdk.apis + +import com.example.sdk.models.* +import com.example.sdk.runtime.ApiClient +import com.example.sdk.runtime.ApiException +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response + +/** + * UploadRawMultipartResponse carries the response from uploadRawMultipart. + */ +data class UploadRawMultipartResponse(val statusCode: Int, val raw: Response) { +} + +/** + * TransferApi groups operations under the transfer tag. + */ +class TransferApi(private val client: ApiClient) { + private val gson: Gson = Gson() + + /** + * uploadRawMultipart POST /uploads/raw. + */ + fun uploadRawMultipart(body: ByteArray): UploadRawMultipartResponse { + val path = "/uploads/raw" + val request: okhttp3.Request = throw IllegalArgumentException("unsupported multipart request body: schema must be object-shaped") + val response = client.execute(request) + + if (!response.isSuccessful) { + val errorBody = response.body?.string() ?: "" + throw ApiException(response.code, response.message, errorBody) + } + return UploadRawMultipartResponse(statusCode = response.code, raw = response) + } +} diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/build.gradle.kts.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/build.gradle.kts.golden new file mode 100644 index 000000000..5c6106455 --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/build.gradle.kts.golden @@ -0,0 +1,19 @@ +plugins { + kotlin("jvm") version "1.9.22" +} + +group = "com.example.sdk" +version = "1.0.0" + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.squareup.okhttp3:okhttp:4.12.0") + implementation("com.google.code.gson:gson:2.11.0") +} + +kotlin { + jvmToolchain(17) +} diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/runtime/ApiClient.kt.golden new file mode 100644 index 000000000..ddf3a91b5 --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/runtime/ApiClient.kt.golden @@ -0,0 +1,62 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Unsupported Schema — 1.0.0 +// Covers multipart request bodies that are not object-shaped. + +package com.example.sdk.runtime + +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response + +class ApiClient( + private val baseUrl: String, + private val client: OkHttpClient = OkHttpClient(), + private val authenticator: Authenticator? = null, + private val defaultHeaders: Map = emptyMap(), +) { + fun newRequest( + method: String, + path: String, + query: Map? = null, + body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() + query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } + + val builder = Request.Builder() + .url(urlBuilder.build()) + .method(method, body) + + defaultHeaders.forEach { (k, v) -> builder.header(k, v) } + authenticator?.authenticate(builder) + + return builder.build() + } + + fun execute(request: Request): Response { + return client.newCall(request).execute() + } +} diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/runtime/ApiException.kt.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/runtime/ApiException.kt.golden new file mode 100644 index 000000000..b31ff3e48 --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/runtime/ApiException.kt.golden @@ -0,0 +1,12 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Unsupported Schema — 1.0.0 +// Covers multipart request bodies that are not object-shaped. + +package com.example.sdk.runtime + +class ApiException( + val statusCode: Int, + val status: String, + val body: String, +) : RuntimeException("API error $statusCode: $status") diff --git a/tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/runtime/Auth.kt.golden b/tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/runtime/Auth.kt.golden new file mode 100644 index 000000000..c3bb7c374 --- /dev/null +++ b/tests/golden/kotlin/kotlin-okhttp/multipart-unsupported-schema/runtime/Auth.kt.golden @@ -0,0 +1,71 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Unsupported Schema — 1.0.0 +// Covers multipart request bodies that are not object-shaped. + +package com.example.sdk.runtime + +import okhttp3.Request + +interface Authenticator { + fun authenticate(builder: Request.Builder) +} + +class BearerAuth : Authenticator { + private val token: String? + private val tokenProvider: (() -> String)? + + constructor(token: String) { + this.token = token + this.tokenProvider = null + } + + constructor(tokenProvider: () -> String) { + this.token = null + this.tokenProvider = tokenProvider + } + + override fun authenticate(builder: Request.Builder) { + val t = tokenProvider?.invoke() ?: token!! + builder.header("Authorization", "Bearer $t") + } +} + +class ApiKeyAuth : Authenticator { + private val key: String? + private val keyProvider: (() -> String)? + private val name: String + private val location: ApiKeyLocation + + constructor(key: String, name: String, location: ApiKeyLocation) { + this.key = key + this.keyProvider = null + this.name = name + this.location = location + } + + constructor(keyProvider: () -> String, name: String, location: ApiKeyLocation) { + this.key = null + this.keyProvider = keyProvider + this.name = name + this.location = location + } + + override fun authenticate(builder: Request.Builder) { + val k = keyProvider?.invoke() ?: key!! + when (location) { + ApiKeyLocation.HEADER -> builder.header(name, k) + ApiKeyLocation.QUERY -> { + val url = builder.build().url.newBuilder() + .addQueryParameter(name, k) + .build() + builder.url(url) + } + } + } +} + +enum class ApiKeyLocation { + HEADER, + QUERY, +} diff --git a/tests/golden/kotlin/kotlin-okhttp/multiple-similar-request-schemas/apis/TestApiApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/multiple-similar-request-schemas/apis/TestApiApi.kt.golden index fbb679deb..d49cab144 100644 --- a/tests/golden/kotlin/kotlin-okhttp/multiple-similar-request-schemas/apis/TestApiApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/multiple-similar-request-schemas/apis/TestApiApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -38,16 +40,18 @@ class TestApiApi(private val client: ApiClient) { fun createTypeA(body: CreateRequestTypeA): CreateTypeAResponse { val path = "/api/v1/type-a/resources" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return CreateTypeAResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -57,16 +61,18 @@ class TestApiApi(private val client: ApiClient) { fun createTypeB(body: CreateRequestTypeB): CreateTypeBResponse { val path = "/api/v1/type-b/resources" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return CreateTypeBResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/multiple-similar-request-schemas/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/multiple-similar-request-schemas/runtime/ApiClient.kt.golden index a29154ec7..03acc3046 100644 --- a/tests/golden/kotlin/kotlin-okhttp/multiple-similar-request-schemas/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/multiple-similar-request-schemas/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/naming-conventions/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/naming-conventions/apis/DefaultApi.kt.golden index 4cf2ffa9d..e716de985 100644 --- a/tests/golden/kotlin/kotlin-okhttp/naming-conventions/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/naming-conventions/apis/DefaultApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -91,16 +93,18 @@ class DefaultApi(private val client: ApiClient) { fun testNamingConventions(body: NamingConventionTest): TestNamingConventionsResponse { val path = "/test" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return TestNamingConventionsResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/naming-conventions/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/naming-conventions/runtime/ApiClient.kt.golden index 57f0a4d6f..5f03ce3db 100644 --- a/tests/golden/kotlin/kotlin-okhttp/naming-conventions/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/naming-conventions/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/petstore/apis/PetApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/petstore/apis/PetApi.kt.golden index 2db176e2a..3a8021dfc 100644 --- a/tests/golden/kotlin/kotlin-okhttp/petstore/apis/PetApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/petstore/apis/PetApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -76,16 +78,18 @@ class PetApi(private val client: ApiClient) { fun updatePet(body: Pet): UpdatePetResponse { val path = "/pet" val jsonBody = gson.toJson(body) - val request = client.newRequest("PUT", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("PUT", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return UpdatePetResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -95,16 +99,18 @@ class PetApi(private val client: ApiClient) { fun addPet(body: Pet): AddPetResponse { val path = "/pet" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return AddPetResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -122,9 +128,10 @@ class PetApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson>(responseBody, object : TypeToken>() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson>(responseText.ifEmpty { "null" }, object : TypeToken>() {}.type) else null return FindPetsByStatusResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -142,9 +149,10 @@ class PetApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson>(responseBody, object : TypeToken>() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson>(responseText.ifEmpty { "null" }, object : TypeToken>() {}.type) else null return FindPetsByTagsResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -160,9 +168,10 @@ class PetApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return GetPetByIdResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -185,9 +194,10 @@ class PetApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return UpdatePetWithFormResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -222,9 +232,10 @@ class PetApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return UploadFileResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/petstore/apis/StoreApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/petstore/apis/StoreApi.kt.golden index 3281309c2..c8a040206 100644 --- a/tests/golden/kotlin/kotlin-okhttp/petstore/apis/StoreApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/petstore/apis/StoreApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -55,9 +57,10 @@ class StoreApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson>(responseBody, object : TypeToken>() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson>(responseText.ifEmpty { "null" }, object : TypeToken>() {}.type) else null return GetInventoryResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -67,16 +70,18 @@ class StoreApi(private val client: ApiClient) { fun placeOrder(body: Order): PlaceOrderResponse { val path = "/store/order" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return PlaceOrderResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -92,9 +97,10 @@ class StoreApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return GetOrderByIdResponse(statusCode = response.code, raw = response, status200 = status200) } diff --git a/tests/golden/kotlin/kotlin-okhttp/petstore/apis/UserApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/petstore/apis/UserApi.kt.golden index e45e21f64..d001114b8 100644 --- a/tests/golden/kotlin/kotlin-okhttp/petstore/apis/UserApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/petstore/apis/UserApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -67,16 +69,18 @@ class UserApi(private val client: ApiClient) { fun createUser(body: User): CreateUserResponse { val path = "/user" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return CreateUserResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -86,16 +90,18 @@ class UserApi(private val client: ApiClient) { fun createUsersWithListInput(body: List): CreateUsersWithListInputResponse { val path = "/user/createWithList" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return CreateUsersWithListInputResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -148,9 +154,10 @@ class UserApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return GetUserByNameResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -160,7 +167,8 @@ class UserApi(private val client: ApiClient) { fun updateUser(username: String, body: User): UpdateUserResponse { val path = "/user/{username}".replace("{username}", username) val jsonBody = gson.toJson(body) - val request = client.newRequest("PUT", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("PUT", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { diff --git a/tests/golden/kotlin/kotlin-okhttp/petstore/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/petstore/runtime/ApiClient.kt.golden index f069c2d2f..94e8f15ed 100644 --- a/tests/golden/kotlin/kotlin-okhttp/petstore/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/petstore/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/query-param-enum/apis/ItemsApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/query-param-enum/apis/ItemsApi.kt.golden index ed100889a..3f3d5cd38 100644 --- a/tests/golden/kotlin/kotlin-okhttp/query-param-enum/apis/ItemsApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/query-param-enum/apis/ItemsApi.kt.golden @@ -41,9 +41,10 @@ class ItemsApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return GetItemsResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/query-param-enum/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/query-param-enum/runtime/ApiClient.kt.golden index 66e541c78..2088bfea4 100644 --- a/tests/golden/kotlin/kotlin-okhttp/query-param-enum/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/query-param-enum/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/recursive-json-all-optional-properties/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/recursive-json-all-optional-properties/runtime/ApiClient.kt.golden index 605e2a512..bb078e2f5 100644 --- a/tests/golden/kotlin/kotlin-okhttp/recursive-json-all-optional-properties/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/recursive-json-all-optional-properties/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/recursive-json-array-of-inline-objects/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/recursive-json-array-of-inline-objects/runtime/ApiClient.kt.golden index 95682e5de..6d65bceee 100644 --- a/tests/golden/kotlin/kotlin-okhttp/recursive-json-array-of-inline-objects/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/recursive-json-array-of-inline-objects/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/recursive-json-array-of-referenced-types/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/recursive-json-array-of-referenced-types/runtime/ApiClient.kt.golden index b83be8f5a..d4b0a14f2 100644 --- a/tests/golden/kotlin/kotlin-okhttp/recursive-json-array-of-referenced-types/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/recursive-json-array-of-referenced-types/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/recursive-json-array-with-reference-property/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/recursive-json-array-with-reference-property/runtime/ApiClient.kt.golden index fad424f01..f065a04a7 100644 --- a/tests/golden/kotlin/kotlin-okhttp/recursive-json-array-with-reference-property/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/recursive-json-array-with-reference-property/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/recursive-json-complex-array-structure/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/recursive-json-complex-array-structure/runtime/ApiClient.kt.golden index c5595297c..381222540 100644 --- a/tests/golden/kotlin/kotlin-okhttp/recursive-json-complex-array-structure/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/recursive-json-complex-array-structure/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/recursive-json-deeply-nested-inline/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/recursive-json-deeply-nested-inline/runtime/ApiClient.kt.golden index 1250c0fbe..54e75a5bb 100644 --- a/tests/golden/kotlin/kotlin-okhttp/recursive-json-deeply-nested-inline/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/recursive-json-deeply-nested-inline/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/recursive-json-empty-array/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/recursive-json-empty-array/runtime/ApiClient.kt.golden index 729be55d8..c913f9f48 100644 --- a/tests/golden/kotlin/kotlin-okhttp/recursive-json-empty-array/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/recursive-json-empty-array/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/recursive-json-inline-object-with-array/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/recursive-json-inline-object-with-array/runtime/ApiClient.kt.golden index c629bf824..48bd3a2f4 100644 --- a/tests/golden/kotlin/kotlin-okhttp/recursive-json-inline-object-with-array/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/recursive-json-inline-object-with-array/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/recursive-json-inline-object/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/recursive-json-inline-object/runtime/ApiClient.kt.golden index 2db7fecfa..c2a911319 100644 --- a/tests/golden/kotlin/kotlin-okhttp/recursive-json-inline-object/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/recursive-json-inline-object/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/recursive-json-mixed-property-types/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/recursive-json-mixed-property-types/runtime/ApiClient.kt.golden index 54c46e9f1..86c1e2875 100644 --- a/tests/golden/kotlin/kotlin-okhttp/recursive-json-mixed-property-types/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/recursive-json-mixed-property-types/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/recursive-json-nested-object-reference/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/recursive-json-nested-object-reference/runtime/ApiClient.kt.golden index b9886ed11..b107fac87 100644 --- a/tests/golden/kotlin/kotlin-okhttp/recursive-json-nested-object-reference/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/recursive-json-nested-object-reference/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/recursive-json-optional-array-of-inline-objects/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/recursive-json-optional-array-of-inline-objects/runtime/ApiClient.kt.golden index b76f09d94..2cbb67b98 100644 --- a/tests/golden/kotlin/kotlin-okhttp/recursive-json-optional-array-of-inline-objects/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/recursive-json-optional-array-of-inline-objects/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/recursive-json-optional-array-of-referenced-types/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/recursive-json-optional-array-of-referenced-types/runtime/ApiClient.kt.golden index aa58da246..54cb35e2e 100644 --- a/tests/golden/kotlin/kotlin-okhttp/recursive-json-optional-array-of-referenced-types/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/recursive-json-optional-array-of-referenced-types/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/recursive-json-optional-inline-object/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/recursive-json-optional-inline-object/runtime/ApiClient.kt.golden index 0a12eedfc..87d19d83c 100644 --- a/tests/golden/kotlin/kotlin-okhttp/recursive-json-optional-inline-object/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/recursive-json-optional-inline-object/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/recursive-json-optional-nested-object-reference/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/recursive-json-optional-nested-object-reference/runtime/ApiClient.kt.golden index 040fdfa6a..e5f1e74fb 100644 --- a/tests/golden/kotlin/kotlin-okhttp/recursive-json-optional-nested-object-reference/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/recursive-json-optional-nested-object-reference/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/recursive-json-primitive-array/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/recursive-json-primitive-array/runtime/ApiClient.kt.golden index 7fb7e5677..3010ead0b 100644 --- a/tests/golden/kotlin/kotlin-okhttp/recursive-json-primitive-array/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/recursive-json-primitive-array/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/request-body-content-types/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/request-body-content-types/apis/DefaultApi.kt.golden index b25fa9792..f9a5e1eec 100644 --- a/tests/golden/kotlin/kotlin-okhttp/request-body-content-types/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/request-body-content-types/apis/DefaultApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -58,7 +60,7 @@ data class PostXmlResponse(val statusCode: Int, val raw: Response) { * GetXmlResponseResponse carries the response from getXmlResponse. */ data class GetXmlResponseResponse(val statusCode: Int, val raw: Response, -val status200: Payload? = null) { +val status200: String? = null) { } /** @@ -72,8 +74,8 @@ class DefaultApi(private val client: ApiClient) { */ fun postBinary(body: ByteArray): PostBinaryResponse { val path = "/binary" - val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = body.toRequestBody("application/octet-stream".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { @@ -88,8 +90,8 @@ class DefaultApi(private val client: ApiClient) { */ fun postForm(body: Payload): PostFormResponse { val path = "/form" - val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + throw IllegalArgumentException("unsupported request body media type: application/x-www-form-urlencoded") + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { @@ -105,7 +107,8 @@ class DefaultApi(private val client: ApiClient) { fun postJson(body: Payload): PostJsonResponse { val path = "/json" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { @@ -121,7 +124,8 @@ class DefaultApi(private val client: ApiClient) { fun postJsonOrXml(body: Payload): PostJsonOrXmlResponse { val path = "/json-or-xml" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { @@ -137,7 +141,8 @@ class DefaultApi(private val client: ApiClient) { fun patchMergeJson(body: Payload): PatchMergeJsonResponse { val path = "/merge-patch" val jsonBody = gson.toJson(body) - val request = client.newRequest("PATCH", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/merge-patch+json".toMediaType()) + val request = client.newRequestWithBody("PATCH", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { @@ -152,8 +157,8 @@ class DefaultApi(private val client: ApiClient) { */ fun postText(body: String): PostTextResponse { val path = "/text" - val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = body.toRequestBody("text/plain".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { @@ -168,8 +173,8 @@ class DefaultApi(private val client: ApiClient) { */ fun postXml(body: Payload): PostXmlResponse { val path = "/xml" - val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + throw IllegalArgumentException("unsupported request body media type: application/xml") + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { @@ -191,9 +196,10 @@ class DefaultApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) responseText else null return GetXmlResponseResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/request-body-content-types/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/request-body-content-types/runtime/ApiClient.kt.golden index d8581a2b3..daf7abc4a 100644 --- a/tests/golden/kotlin/kotlin-okhttp/request-body-content-types/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/request-body-content-types/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/reserved-word-fields/apis/ItemsApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/reserved-word-fields/apis/ItemsApi.kt.golden index c36956d5b..02f5307c2 100644 --- a/tests/golden/kotlin/kotlin-okhttp/reserved-word-fields/apis/ItemsApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/reserved-word-fields/apis/ItemsApi.kt.golden @@ -36,9 +36,10 @@ class ItemsApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return GetItemsResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/reserved-word-fields/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/reserved-word-fields/runtime/ApiClient.kt.golden index 6f4e3bd64..5f10556c2 100644 --- a/tests/golden/kotlin/kotlin-okhttp/reserved-word-fields/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/reserved-word-fields/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/response-body-default-and-exact/apis/WidgetApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/response-body-default-and-exact/apis/WidgetApi.kt.golden index ce724d616..cd538beed 100644 --- a/tests/golden/kotlin/kotlin-okhttp/response-body-default-and-exact/apis/WidgetApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/response-body-default-and-exact/apis/WidgetApi.kt.golden @@ -36,11 +36,12 @@ class WidgetApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null - val default = if (true) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val default = if (true) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return GetWidgetResponse(statusCode = response.code, raw = response, status200 = status200, default = default) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/response-body-default-and-exact/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/response-body-default-and-exact/runtime/ApiClient.kt.golden index a13541d0a..550136d19 100644 --- a/tests/golden/kotlin/kotlin-okhttp/response-body-default-and-exact/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/response-body-default-and-exact/runtime/ApiClient.kt.golden @@ -23,15 +23,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/response-body-fallback/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/response-body-fallback/apis/DefaultApi.kt.golden index 82fa89950..dd3fab702 100644 --- a/tests/golden/kotlin/kotlin-okhttp/response-body-fallback/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/response-body-fallback/apis/DefaultApi.kt.golden @@ -57,9 +57,10 @@ class DefaultApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return GetFallbackWithBodyResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/response-body-fallback/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/response-body-fallback/runtime/ApiClient.kt.golden index a22bcb6e1..8ca892bdd 100644 --- a/tests/golden/kotlin/kotlin-okhttp/response-body-fallback/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/response-body-fallback/runtime/ApiClient.kt.golden @@ -23,15 +23,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/response-body-multi-status-responses/apis/WidgetApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/response-body-multi-status-responses/apis/WidgetApi.kt.golden index 0d617ea69..34f89800c 100644 --- a/tests/golden/kotlin/kotlin-okhttp/response-body-multi-status-responses/apis/WidgetApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/response-body-multi-status-responses/apis/WidgetApi.kt.golden @@ -38,13 +38,14 @@ class WidgetApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson>(responseBody, object : TypeToken>() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson>(responseText.ifEmpty { "null" }, object : TypeToken>() {}.type) else null - val status206 = if (response.code == 206) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status206 = if (response.code == 206) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null - val status404 = if (response.code == 404) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status404 = if (response.code == 404) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return ListWidgetsResponse(statusCode = response.code, raw = response, status200 = status200, status206 = status206, status404 = status404) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/response-body-multi-status-responses/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/response-body-multi-status-responses/runtime/ApiClient.kt.golden index 6480ae237..13b057a6d 100644 --- a/tests/golden/kotlin/kotlin-okhttp/response-body-multi-status-responses/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/response-body-multi-status-responses/runtime/ApiClient.kt.golden @@ -23,15 +23,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/response-body-no-response-body/apis/FooApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/response-body-no-response-body/apis/FooApi.kt.golden index b0090527e..c8cde6b27 100644 --- a/tests/golden/kotlin/kotlin-okhttp/response-body-no-response-body/apis/FooApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/response-body-no-response-body/apis/FooApi.kt.golden @@ -9,6 +9,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -31,18 +33,20 @@ class FooApi(private val client: ApiClient) { fun updateFooBar(fooId: String, body: FooRequest): UpdateFooBarResponse { val path = "/foo/bar".replace("{foo_id}", fooId) val jsonBody = gson.toJson(body) - val request = client.newRequest("PATCH", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("PATCH", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status400 = if (response.code == 400) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status400 = if (response.code == 400) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null - val status500 = if (response.code == 500) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status500 = if (response.code == 500) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return UpdateFooBarResponse(statusCode = response.code, raw = response, status400 = status400, status500 = status500) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/response-body-no-response-body/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/response-body-no-response-body/runtime/ApiClient.kt.golden index 7565ef164..d15f5388d 100644 --- a/tests/golden/kotlin/kotlin-okhttp/response-body-no-response-body/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/response-body-no-response-body/runtime/ApiClient.kt.golden @@ -23,15 +23,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/server-object/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/server-object/apis/DefaultApi.kt.golden index b0d14905c..17a607f8e 100644 --- a/tests/golden/kotlin/kotlin-okhttp/server-object/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/server-object/apis/DefaultApi.kt.golden @@ -45,9 +45,10 @@ class DefaultApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson>(responseBody, object : TypeToken>() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson>(responseText.ifEmpty { "null" }, object : TypeToken>() {}.type) else null return GetAllUsersResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -63,11 +64,12 @@ class DefaultApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null - val status404 = if (response.code == 404) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status404 = if (response.code == 404) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return GetUserByIdResponse(statusCode = response.code, raw = response, status200 = status200, status404 = status404) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/server-object/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/server-object/runtime/ApiClient.kt.golden index f33e21347..c22ff5106 100644 --- a/tests/golden/kotlin/kotlin-okhttp/server-object/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/server-object/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/server-path-prefix/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/server-path-prefix/runtime/ApiClient.kt.golden index 3ae7a8e64..369466b58 100644 --- a/tests/golden/kotlin/kotlin-okhttp/server-path-prefix/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/server-path-prefix/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-complex-union/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-complex-union/apis/DefaultApi.kt.golden index 9ef20029c..ce2be3bd2 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-complex-union/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-complex-union/apis/DefaultApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -31,16 +33,18 @@ class DefaultApi(private val client: ApiClient) { fun testComplexUnion(body: Foo): TestComplexUnionResponse { val path = "/test" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return TestComplexUnionResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-complex-union/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-complex-union/runtime/ApiClient.kt.golden index 798b3a04a..efc213a1b 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-complex-union/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-complex-union/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-inline-discriminator-only/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-inline-discriminator-only/apis/DefaultApi.kt.golden index 3e0994869..89d7b596b 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-inline-discriminator-only/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-inline-discriminator-only/apis/DefaultApi.kt.golden @@ -19,6 +19,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -39,16 +41,18 @@ class DefaultApi(private val client: ApiClient) { fun createEvent(body: Event): CreateEventResponse { val path = "/event" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return CreateEventResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-inline-discriminator-only/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-inline-discriminator-only/runtime/ApiClient.kt.golden index bd3d2b9f6..af53c4282 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-inline-discriminator-only/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-inline-discriminator-only/runtime/ApiClient.kt.golden @@ -33,15 +33,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-internally-tagged/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-internally-tagged/apis/DefaultApi.kt.golden index bf7146b5c..593af1376 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-internally-tagged/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-internally-tagged/apis/DefaultApi.kt.golden @@ -12,6 +12,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -33,16 +35,18 @@ class DefaultApi(private val client: ApiClient) { fun createResource(body: Resource): CreateResourceResponse { val path = "/resource" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return CreateResourceResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-internally-tagged/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-internally-tagged/runtime/ApiClient.kt.golden index 4b970c2d9..07782cfd3 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-internally-tagged/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-internally-tagged/runtime/ApiClient.kt.golden @@ -26,15 +26,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-long-names/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-long-names/apis/DefaultApi.kt.golden index 248619ab2..3a3fef829 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-long-names/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-long-names/apis/DefaultApi.kt.golden @@ -12,6 +12,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -33,16 +35,18 @@ class DefaultApi(private val client: ApiClient) { fun createResource(body: VeryLongDiscriminatedUnionTypeNameForTesting20260630): CreateResourceResponse { val path = "/resource" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return CreateResourceResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-long-names/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-long-names/runtime/ApiClient.kt.golden index 015fc12cb..79ffcb038 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-long-names/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-long-names/runtime/ApiClient.kt.golden @@ -26,15 +26,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/apis/DefaultApi.kt.golden index e601ba716..66d191714 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/apis/DefaultApi.kt.golden @@ -15,6 +15,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -36,16 +38,18 @@ class DefaultApi(private val client: ApiClient) { fun createResource(body: Setup): CreateResourceResponse { val path = "/resource" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return CreateResourceResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/runtime/ApiClient.kt.golden index d13f02c98..690176cc0 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/runtime/ApiClient.kt.golden @@ -29,15 +29,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-multiple/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-multiple/apis/DefaultApi.kt.golden index 3263817bd..dd6e88a71 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-multiple/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-multiple/apis/DefaultApi.kt.golden @@ -13,6 +13,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -41,16 +43,18 @@ class DefaultApi(private val client: ApiClient) { fun createContainer(body: ContainerKind): CreateContainerResponse { val path = "/container" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return CreateContainerResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -60,16 +64,18 @@ class DefaultApi(private val client: ApiClient) { fun createVolume(body: VolumeKind): CreateVolumeResponse { val path = "/volume" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return CreateVolumeResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-multiple/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-multiple/runtime/ApiClient.kt.golden index a4c076a7e..bed88b246 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-multiple/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-multiple/runtime/ApiClient.kt.golden @@ -27,15 +27,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-with-refs/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-with-refs/apis/DefaultApi.kt.golden index 4f0771f25..ce6c8b43f 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-with-refs/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-with-refs/apis/DefaultApi.kt.golden @@ -11,6 +11,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -32,16 +34,18 @@ class DefaultApi(private val client: ApiClient) { fun createContainer(body: ContainerImage): CreateContainerResponse { val path = "/container" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return CreateContainerResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-with-refs/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-with-refs/runtime/ApiClient.kt.golden index ce4880d91..b6f51653f 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-with-refs/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-with-refs/runtime/ApiClient.kt.golden @@ -25,15 +25,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-intersection-allof/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-intersection-allof/apis/DefaultApi.kt.golden index 095c77598..8b6b4b62d 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-intersection-allof/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-intersection-allof/apis/DefaultApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -31,16 +33,18 @@ class DefaultApi(private val client: ApiClient) { fun testIntersection(body: Foo): TestIntersectionResponse { val path = "/test" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return TestIntersectionResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-intersection-allof/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-intersection-allof/runtime/ApiClient.kt.golden index 6aaed1509..b2948acf5 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-intersection-allof/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-intersection-allof/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-intersection-with-nullable-reference/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-intersection-with-nullable-reference/apis/DefaultApi.kt.golden index 57863b6c9..306c77f9d 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-intersection-with-nullable-reference/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-intersection-with-nullable-reference/apis/DefaultApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -36,16 +38,18 @@ class DefaultApi(private val client: ApiClient) { fun createTest(body: Baz): CreateTestResponse { val path = "/test" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return CreateTestResponse(statusCode = response.code, raw = response, status200 = status200) } @@ -61,9 +65,10 @@ class DefaultApi(private val client: ApiClient) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return GetTestResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-intersection-with-nullable-reference/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-intersection-with-nullable-reference/runtime/ApiClient.kt.golden index 01d574594..598488899 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-intersection-with-nullable-reference/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-intersection-with-nullable-reference/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-nested-union/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-nested-union/apis/DefaultApi.kt.golden index f1e0a6b83..5ae04bde7 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-nested-union/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-nested-union/apis/DefaultApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -31,16 +33,18 @@ class DefaultApi(private val client: ApiClient) { fun testNestedUnion(body: Foo): TestNestedUnionResponse { val path = "/test" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return TestNestedUnionResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-nested-union/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-nested-union/runtime/ApiClient.kt.golden index 06d946a3f..537a7ceaf 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-nested-union/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-nested-union/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-simple-type-alias/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-simple-type-alias/apis/DefaultApi.kt.golden index 02187c037..7ae1c16f4 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-simple-type-alias/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-simple-type-alias/apis/DefaultApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -31,16 +33,18 @@ class DefaultApi(private val client: ApiClient) { fun testSimpleAlias(body: Foo): TestSimpleAliasResponse { val path = "/test" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return TestSimpleAliasResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-simple-type-alias/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-simple-type-alias/runtime/ApiClient.kt.golden index 96882973f..d1a4ff63f 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-simple-type-alias/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-simple-type-alias/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-mixed/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-mixed/apis/DefaultApi.kt.golden index c29819706..8373e02a3 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-mixed/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-mixed/apis/DefaultApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -30,16 +32,18 @@ class DefaultApi(private val client: ApiClient) { fun testMixedUnion(body: Foo): TestMixedUnionResponse { val path = "/test" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return TestMixedUnionResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-mixed/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-mixed/runtime/ApiClient.kt.golden index 6f0170a03..6e41716ff 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-mixed/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-mixed/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-any/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-any/apis/DefaultApi.kt.golden index 7bdd301a9..204c89c8b 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-any/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-any/apis/DefaultApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -31,16 +33,18 @@ class DefaultApi(private val client: ApiClient) { fun testUnionWithAny(body: Config): TestUnionWithAnyResponse { val path = "/test" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return TestUnionWithAnyResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-any/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-any/runtime/ApiClient.kt.golden index a2654af72..44950b5eb 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-any/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-any/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-inline-objects/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-inline-objects/apis/DefaultApi.kt.golden index 1bc18fe24..87ef442af 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-inline-objects/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-inline-objects/apis/DefaultApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -31,16 +33,18 @@ class DefaultApi(private val client: ApiClient) { fun testUnionWithInlineObjects(body: Foo): TestUnionWithInlineObjectsResponse { val path = "/test" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return TestUnionWithInlineObjectsResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-inline-objects/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-inline-objects/runtime/ApiClient.kt.golden index bc03bf301..8afe87dd2 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-inline-objects/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-inline-objects/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-interfaces/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-interfaces/apis/DefaultApi.kt.golden index bde840aee..168a0664b 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-interfaces/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-interfaces/apis/DefaultApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -30,16 +32,18 @@ class DefaultApi(private val client: ApiClient) { fun testUnion(body: Foo): TestUnionResponse { val path = "/test" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return TestUnionResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-interfaces/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-interfaces/runtime/ApiClient.kt.golden index 7d66d9b55..f991045a9 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-interfaces/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-interfaces/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-primitives/apis/DefaultApi.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-primitives/apis/DefaultApi.kt.golden index 21dfe86ff..f27825825 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-primitives/apis/DefaultApi.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-primitives/apis/DefaultApi.kt.golden @@ -10,6 +10,8 @@ import com.example.sdk.runtime.ApiClient import com.example.sdk.runtime.ApiException import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response /** @@ -31,16 +33,18 @@ class DefaultApi(private val client: ApiClient) { fun testUnionPrimitives(body: Foo): TestUnionPrimitivesResponse { val path = "/test" val jsonBody = gson.toJson(body) - val request = client.newRequest("POST", path, null, jsonBody) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) + val request = client.newRequestWithBody("POST", path, null, requestBody) val response = client.execute(request) if (!response.isSuccessful) { val errorBody = response.body?.string() ?: "" throw ApiException(response.code, response.message, errorBody) } - val responseBody = response.body?.string() + val responseBytes = response.body?.bytes() ?: ByteArray(0) + val responseText = responseBytes.toString(Charsets.UTF_8) - val status200 = if (response.code == 200) gson.fromJson(responseBody, object : TypeToken() {}.type) else null + val status200 = if (response.code == 200) gson.fromJson(responseText.ifEmpty { "null" }, object : TypeToken() {}.type) else null return TestUnionPrimitivesResponse(statusCode = response.code, raw = response, status200 = status200) } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-primitives/runtime/ApiClient.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-primitives/runtime/ApiClient.kt.golden index 7248af78d..063343910 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-primitives/runtime/ApiClient.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-primitives/runtime/ApiClient.kt.golden @@ -24,15 +24,31 @@ class ApiClient( path: String, query: Map? = null, body: String? = null, + ): Request { + return buildRequest(method, path, query, body?.toRequestBody("application/json".toMediaType())) + } + + fun newRequestWithBody( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, + ): Request { + return buildRequest(method, path, query, body) + } + + private fun buildRequest( + method: String, + path: String, + query: Map? = null, + body: okhttp3.RequestBody?, ): Request { val urlBuilder: HttpUrl.Builder = (baseUrl.trimEnd('/') + path).toHttpUrl().newBuilder() query?.forEach { (k, v) -> urlBuilder.addQueryParameter(k, v) } - val requestBody = body?.toRequestBody("application/json".toMediaType()) - val builder = Request.Builder() .url(urlBuilder.build()) - .method(method, requestBody) + .method(method, body) defaultHeaders.forEach { (k, v) -> builder.header(k, v) } authenticator?.authenticate(builder) diff --git a/tests/golden/python/python-httpx/additional-properties/additional_properties_api/apis/additional_properties_api.py.golden b/tests/golden/python/python-httpx/additional-properties/additional_properties_api/apis/additional_properties_api.py.golden index ee617c3fc..77a7dd20d 100644 --- a/tests/golden/python/python-httpx/additional-properties/additional_properties_api/apis/additional_properties_api.py.golden +++ b/tests/golden/python/python-httpx/additional-properties/additional_properties_api/apis/additional_properties_api.py.golden @@ -18,7 +18,9 @@ class AdditionalPropertiesApi: def post_leaf(self, *, body: LeafValue) -> LeafValue: """Echo leaf payload (attributes map)..""" path = "/leaf" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return LeafValue.from_dict(response.json()) @@ -26,7 +28,9 @@ class AdditionalPropertiesApi: def post_middle(self, *, body: MiddleLevel) -> MiddleLevel: """Echo middle-level payload (nested + extra maps)..""" path = "/middle" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return MiddleLevel.from_dict(response.json()) @@ -34,7 +38,9 @@ class AdditionalPropertiesApi: def post_root(self, *, body: RootLevel) -> RootLevel: """Echo root payload (all three levels with additional-properties-style maps)..""" path = "/root" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return RootLevel.from_dict(response.json()) diff --git a/tests/golden/python/python-httpx/additional-properties/additional_properties_api/runtime/client.py.golden b/tests/golden/python/python-httpx/additional-properties/additional_properties_api/runtime/client.py.golden index 7188f9ebe..ce4f15661 100644 --- a/tests/golden/python/python-httpx/additional-properties/additional_properties_api/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/additional-properties/additional_properties_api/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/binary-transfer-media-types/binary_transfer_media_types/apis/transfer_api.py.golden b/tests/golden/python/python-httpx/binary-transfer-media-types/binary_transfer_media_types/apis/transfer_api.py.golden index a7fee515b..3d240f985 100644 --- a/tests/golden/python/python-httpx/binary-transfer-media-types/binary_transfer_media_types/apis/transfer_api.py.golden +++ b/tests/golden/python/python-httpx/binary-transfer-media-types/binary_transfer_media_types/apis/transfer_api.py.golden @@ -5,6 +5,8 @@ from __future__ import annotations +import json + from ..models.upload_asset_request import UploadAssetRequest from ..models.upload_metadata import UploadMetadata from ..runtime.client import Client @@ -14,16 +16,20 @@ class TransferApi: def __init__(self, client: Client) -> None: self._client = client - def download_asset(self, asset_id: str) -> list[int]: + def download_asset(self, asset_id: str) -> bytes: path = f"/assets/{asset_id}/content" response = self._client.request("GET", path) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) - return response.json() # type: ignore[return-value] + return response.content def upload_asset(self, *, body: UploadAssetRequest) -> UploadMetadata: path = "/uploads" - response = self._client.request("POST", path, json=body.to_dict()) + files: dict[str, object] = {} + files["file"] = ("file", body.file, "application/octet-stream") + files["metadata"] = (None, json.dumps(body.metadata.to_dict()), "application/json") + files["purpose"] = (None, str(body.purpose)) + response = self._client.request("POST", path, files=files if files else None) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return UploadMetadata.from_dict(response.json()) diff --git a/tests/golden/python/python-httpx/binary-transfer-media-types/binary_transfer_media_types/runtime/client.py.golden b/tests/golden/python/python-httpx/binary-transfer-media-types/binary_transfer_media_types/runtime/client.py.golden index 38499c8cd..2f08a2ef0 100644 --- a/tests/golden/python/python-httpx/binary-transfer-media-types/binary_transfer_media_types/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/binary-transfer-media-types/binary_transfer_media_types/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/comprehensive-schemas/comprehensive_schema_types_example/runtime/client.py.golden b/tests/golden/python/python-httpx/comprehensive-schemas/comprehensive_schema_types_example/runtime/client.py.golden index 64b3c782a..65fd66d3a 100644 --- a/tests/golden/python/python-httpx/comprehensive-schemas/comprehensive_schema_types_example/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/comprehensive-schemas/comprehensive_schema_types_example/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/delete-with-response-schema/test_api_with_delete_response_schema/apis/test_resource_api.py.golden b/tests/golden/python/python-httpx/delete-with-response-schema/test_api_with_delete_response_schema/apis/test_resource_api.py.golden index eeb12c8f8..d3d330828 100644 --- a/tests/golden/python/python-httpx/delete-with-response-schema/test_api_with_delete_response_schema/apis/test_resource_api.py.golden +++ b/tests/golden/python/python-httpx/delete-with-response-schema/test_api_with_delete_response_schema/apis/test_resource_api.py.golden @@ -17,7 +17,9 @@ class TestResourceApi: def create_test_resource(self, *, body: CreateTestResourceRequest) -> CreateTestResourceResponse: path = "/v1/api/test-resource" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return CreateTestResourceResponse.from_dict(response.json()) diff --git a/tests/golden/python/python-httpx/delete-with-response-schema/test_api_with_delete_response_schema/runtime/client.py.golden b/tests/golden/python/python-httpx/delete-with-response-schema/test_api_with_delete_response_schema/runtime/client.py.golden index 70a799977..c13d10617 100644 --- a/tests/golden/python/python-httpx/delete-with-response-schema/test_api_with_delete_response_schema/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/delete-with-response-schema/test_api_with_delete_response_schema/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/duplicate-param-names/duplicate_parameter_names_test_api/apis/items_api.py.golden b/tests/golden/python/python-httpx/duplicate-param-names/duplicate_parameter_names_test_api/apis/items_api.py.golden index 82b4cdb2a..95bc114aa 100644 --- a/tests/golden/python/python-httpx/duplicate-param-names/duplicate_parameter_names_test_api/apis/items_api.py.golden +++ b/tests/golden/python/python-httpx/duplicate-param-names/duplicate_parameter_names_test_api/apis/items_api.py.golden @@ -21,7 +21,9 @@ class ItemsApi: params: dict[str, str] = {} if body is not None: params["body"] = str(body) - response = self._client.request("POST", path, params=params, json=body2.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, params=params, json=body2.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return CreateItemWithBodyConflictResponse200.from_dict(response.json()) diff --git a/tests/golden/python/python-httpx/duplicate-param-names/duplicate_parameter_names_test_api/runtime/client.py.golden b/tests/golden/python/python-httpx/duplicate-param-names/duplicate_parameter_names_test_api/runtime/client.py.golden index d23b28919..b5bae56fa 100644 --- a/tests/golden/python/python-httpx/duplicate-param-names/duplicate_parameter_names_test_api/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/duplicate-param-names/duplicate_parameter_names_test_api/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/enum-repr/enum_representation_api/apis/enum_repr_api.py.golden b/tests/golden/python/python-httpx/enum-repr/enum_representation_api/apis/enum_repr_api.py.golden index 35a8c2aa6..c951ade51 100644 --- a/tests/golden/python/python-httpx/enum-repr/enum_representation_api/apis/enum_repr_api.py.golden +++ b/tests/golden/python/python-httpx/enum-repr/enum_representation_api/apis/enum_repr_api.py.golden @@ -20,7 +20,9 @@ class EnumReprApi: def handle_adjacently_tagged(self, *, body: AdjacentlyTaggedEnum) -> AdjacentlyTaggedEnum: """Handle adjacently tagged enum.""" path = "/adjacently-tagged" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] @@ -28,7 +30,9 @@ class EnumReprApi: def handle_externally_tagged(self, *, body: ExternallyTaggedEnum) -> ExternallyTaggedEnum: """Handle externally tagged enum.""" path = "/externally-tagged" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] @@ -36,7 +40,9 @@ class EnumReprApi: def handle_internally_tagged(self, *, body: InternallyTaggedEnum) -> InternallyTaggedEnum: """Handle internally tagged enum.""" path = "/internally-tagged" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] @@ -44,7 +50,9 @@ class EnumReprApi: def handle_mixed(self, *, body: MixedEnum) -> MixedEnum: """Handle mixed enum (unit variants SimpleA, SimpleB and tuple variants VariantA(VariantA), VariantB(VariantB)).""" path = "/mixed" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] @@ -52,7 +60,9 @@ class EnumReprApi: def handle_untagged(self, *, body: UntaggedEnum) -> UntaggedEnum: """Handle untagged enum.""" path = "/untagged" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-httpx/enum-repr/enum_representation_api/runtime/client.py.golden b/tests/golden/python/python-httpx/enum-repr/enum_representation_api/runtime/client.py.golden index a57fc049c..9e865f908 100644 --- a/tests/golden/python/python-httpx/enum-repr/enum_representation_api/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/enum-repr/enum_representation_api/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/interface-with-enum-reference/interface_with_enum_reference_test/runtime/client.py.golden b/tests/golden/python/python-httpx/interface-with-enum-reference/interface_with_enum_reference_test/runtime/client.py.golden index 17f4a8d43..32c4cc34d 100644 --- a/tests/golden/python/python-httpx/interface-with-enum-reference/interface_with_enum_reference_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/interface-with-enum-reference/interface_with_enum_reference_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/media-type-selection/README.md.golden b/tests/golden/python/python-httpx/media-type-selection/README.md.golden new file mode 100644 index 000000000..c22dde875 --- /dev/null +++ b/tests/golden/python/python-httpx/media-type-selection/README.md.golden @@ -0,0 +1,7 @@ +# Media Type Selection + +Covers normalized media-type selection for requests and responses. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `media_type_selection`. diff --git a/tests/golden/python/python-httpx/media-type-selection/media_type_selection/__init__.py.golden b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/__init__.py.golden new file mode 100644 index 000000000..35a708cb1 --- /dev/null +++ b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/__init__.py.golden @@ -0,0 +1,10 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +from .runtime import ApiKeyAuth as ApiKeyAuth +from .runtime import Authenticator as Authenticator +from .runtime import BearerAuth as BearerAuth +from .runtime import Client as Client +from .runtime import ApiError as ApiError diff --git a/tests/golden/python/python-httpx/media-type-selection/media_type_selection/apis/__init__.py.golden b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/apis/__init__.py.golden new file mode 100644 index 000000000..bc4112fad --- /dev/null +++ b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/apis/__init__.py.golden @@ -0,0 +1,6 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +from .media_api import MediaApi as MediaApi diff --git a/tests/golden/python/python-httpx/media-type-selection/media_type_selection/apis/media_api.py.golden b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/apis/media_api.py.golden new file mode 100644 index 000000000..3f86321a2 --- /dev/null +++ b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/apis/media_api.py.golden @@ -0,0 +1,66 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +from __future__ import annotations + +from ..models.file_envelope import FileEnvelope +from ..models.payload import Payload +from ..runtime.client import Client +from ..runtime.errors import ApiError + +class MediaApi: + def __init__(self, client: Client) -> None: + self._client = client + + def send_json_preferred(self, *, body: Payload) -> None: + path = "/request/json-vs-multipart" + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason_phrase, response.content) + return None + + def send_parameterized_multipart(self, *, body: FileEnvelope) -> None: + path = "/request/parameterized-multipart" + files: dict[str, object] = {} + files["file"] = ("file", body.file, "application/octet-stream") + if body.note is not None: + files["note"] = (None, str(body.note)) + + response = self._client.request("POST", path, files=files if files else None) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason_phrase, response.content) + return None + + def send_vendor_json(self, *, body: Payload) -> None: + path = "/request/vendor-json" + headers: dict[str, str] = {} + headers["Content-Type"] = "application/vnd.example+json; charset=utf-8" + response = self._client.request("PATCH", path, json=body.to_dict(), headers=headers) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason_phrase, response.content) + return None + + def get_octet_preferred(self) -> bytes: + path = "/response/octet-before-xml" + response = self._client.request("GET", path) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason_phrase, response.content) + return response.content + + def get_text_preferred(self) -> str: + path = "/response/text-before-xml" + response = self._client.request("GET", path) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason_phrase, response.content) + return response.text + + def get_vendor_json(self) -> Payload: + path = "/response/vendor-json" + response = self._client.request("GET", path) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason_phrase, response.content) + return Payload.from_dict(response.json()) diff --git a/tests/golden/python/python-httpx/media-type-selection/media_type_selection/models/__init__.py.golden b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/models/__init__.py.golden new file mode 100644 index 000000000..b37a095eb --- /dev/null +++ b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/models/__init__.py.golden @@ -0,0 +1,7 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +from .file_envelope import FileEnvelope as FileEnvelope +from .payload import Payload as Payload diff --git a/tests/golden/python/python-httpx/media-type-selection/media_type_selection/models/file_envelope.py.golden b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/models/file_envelope.py.golden new file mode 100644 index 000000000..76e861905 --- /dev/null +++ b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/models/file_envelope.py.golden @@ -0,0 +1,27 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +from __future__ import annotations + +from dataclasses import dataclass + +@dataclass +class FileEnvelope: + file: bytes + note: str | None = None + + def to_dict(self) -> dict[str, object]: + result: dict[str, object] = {} + result["file"] = self.file + if self.note is not None: + result["note"] = self.note + return result + + @classmethod + def from_dict(cls, data: dict[str, object]) -> FileEnvelope: + return cls( + file=data["file"], # type: ignore[assignment] + note=data.get("note"), # type: ignore[assignment] + ) diff --git a/tests/golden/python/python-httpx/media-type-selection/media_type_selection/models/payload.py.golden b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/models/payload.py.golden new file mode 100644 index 000000000..801723e3d --- /dev/null +++ b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/models/payload.py.golden @@ -0,0 +1,27 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +from __future__ import annotations + +from dataclasses import dataclass + +@dataclass +class Payload: + id: str + count: int | None = None + + def to_dict(self) -> dict[str, object]: + result: dict[str, object] = {} + result["id"] = self.id + if self.count is not None: + result["count"] = self.count + return result + + @classmethod + def from_dict(cls, data: dict[str, object]) -> Payload: + return cls( + id=data["id"], # type: ignore[assignment] + count=data.get("count"), # type: ignore[assignment] + ) diff --git a/tests/golden/python/python-httpx/media-type-selection/media_type_selection/py.typed.golden b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/py.typed.golden new file mode 100644 index 000000000..e69de29bb diff --git a/tests/golden/python/python-httpx/media-type-selection/media_type_selection/runtime/__init__.py.golden b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/runtime/__init__.py.golden new file mode 100644 index 000000000..e4f6e6349 --- /dev/null +++ b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/runtime/__init__.py.golden @@ -0,0 +1,10 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +from .auth import ApiKeyAuth as ApiKeyAuth +from .auth import Authenticator as Authenticator +from .auth import BearerAuth as BearerAuth +from .client import Client as Client +from .errors import ApiError as ApiError diff --git a/tests/golden/python/python-httpx/media-type-selection/media_type_selection/runtime/auth.py.golden b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/runtime/auth.py.golden new file mode 100644 index 000000000..6572db864 --- /dev/null +++ b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/runtime/auth.py.golden @@ -0,0 +1,51 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +"""Authentication helpers.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from collections.abc import Callable + + +class Authenticator(ABC): + """Base class for request authenticators.""" + + @abstractmethod + def auth_headers(self) -> dict[str, str]: + """Return headers to attach to every request.""" + ... + + +class BearerAuth(Authenticator): + """Bearer token authentication. + + Accepts a static token string or a callable that returns the + current token (evaluated on every request). + """ + + def __init__(self, token: str | Callable[[], str]) -> None: + self._token = token + + def auth_headers(self) -> dict[str, str]: + token = self._token() if callable(self._token) else self._token + return {"Authorization": f"Bearer {token}"} + + +class ApiKeyAuth(Authenticator): + """API key authentication via a custom header. + + Accepts a static key string or a callable that returns the + current key (evaluated on every request). + """ + + def __init__(self, header_name: str, api_key: str | Callable[[], str]) -> None: + self._header_name = header_name + self._api_key = api_key + + def auth_headers(self) -> dict[str, str]: + key = self._api_key() if callable(self._api_key) else self._api_key + return {self._header_name: key} diff --git a/tests/golden/python/python-httpx/media-type-selection/media_type_selection/runtime/client.py.golden b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/runtime/client.py.golden new file mode 100644 index 000000000..a919ff402 --- /dev/null +++ b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/runtime/client.py.golden @@ -0,0 +1,71 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +"""HTTP client wrapping httpx.""" + +from __future__ import annotations + +from typing import Any + +import httpx + +from .auth import Authenticator + + +class Client: + """Synchronous HTTP client for the generated SDK.""" + + def __init__( + self, + base_url: str, + *, + http_client: httpx.Client | None = None, + authenticator: Authenticator | None = None, + ) -> None: + self._base_url = base_url.rstrip("/") + self._http_client = http_client or httpx.Client() + self._authenticator = authenticator + + def request( + self, + method: str, + path: str, + *, + params: dict[str, str] | None = None, + json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, + headers: dict[str, str] | None = None, + ) -> httpx.Response: + """Send an HTTP request and return the raw response.""" + url = f"{self._base_url}{path}" + req_headers: dict[str, str] = {"Accept": "application/json"} + if json is not None: + req_headers["Content-Type"] = "application/json" + if headers: + req_headers.update(headers) + if self._authenticator is not None: + req_headers.update(self._authenticator.auth_headers()) + return self._http_client.request( + method, + url, + params=params, + json=json, + content=content, + data=data, + files=files, + headers=req_headers, + ) + + def close(self) -> None: + """Close the underlying HTTP client.""" + self._http_client.close() + + def __enter__(self) -> Client: + return self + + def __exit__(self, *args: object) -> None: + self.close() diff --git a/tests/golden/python/python-httpx/media-type-selection/media_type_selection/runtime/errors.py.golden b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/runtime/errors.py.golden new file mode 100644 index 000000000..6253434ae --- /dev/null +++ b/tests/golden/python/python-httpx/media-type-selection/media_type_selection/runtime/errors.py.golden @@ -0,0 +1,18 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +"""API error type.""" + +from __future__ import annotations + + +class ApiError(Exception): + """Raised when the server returns a 4xx or 5xx status code.""" + + def __init__(self, status_code: int, status: str, body: bytes) -> None: + self.status_code = status_code + self.status = status + self.body = body + super().__init__(f"{status_code} {status}") diff --git a/tests/golden/python/python-httpx/media-type-selection/pyproject.toml.golden b/tests/golden/python/python-httpx/media-type-selection/pyproject.toml.golden new file mode 100644 index 000000000..4f4ad2f30 --- /dev/null +++ b/tests/golden/python/python-httpx/media-type-selection/pyproject.toml.golden @@ -0,0 +1,10 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "media_type_selection" +version = "1.0.0" +description = "Covers normalized media-type selection for requests and responses." +requires-python = ">=3.12" +dependencies = ["httpx>=0.27"] diff --git a/tests/golden/python/python-httpx/minimal/minimal_api/runtime/client.py.golden b/tests/golden/python/python-httpx/minimal/minimal_api/runtime/client.py.golden index 2c6f0766a..9554f4f66 100644 --- a/tests/golden/python/python-httpx/minimal/minimal_api/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/minimal/minimal_api/runtime/client.py.golden @@ -34,6 +34,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -50,6 +53,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/multipart-edge-cases/README.md.golden b/tests/golden/python/python-httpx/multipart-edge-cases/README.md.golden new file mode 100644 index 000000000..17e37372c --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-edge-cases/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Edge Cases + +Covers optional multipart bodies, optional parts, and text-only multipart fields. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart_edge_cases`. diff --git a/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/__init__.py.golden b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/__init__.py.golden new file mode 100644 index 000000000..7fb6a7a96 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/__init__.py.golden @@ -0,0 +1,10 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +from .runtime import ApiKeyAuth as ApiKeyAuth +from .runtime import Authenticator as Authenticator +from .runtime import BearerAuth as BearerAuth +from .runtime import Client as Client +from .runtime import ApiError as ApiError diff --git a/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/apis/__init__.py.golden b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/apis/__init__.py.golden new file mode 100644 index 000000000..1d4c5fadd --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/apis/__init__.py.golden @@ -0,0 +1,6 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +from .multipart_api import MultipartApi as MultipartApi diff --git a/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/apis/multipart_api.py.golden b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/apis/multipart_api.py.golden new file mode 100644 index 000000000..f7c2bca77 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/apis/multipart_api.py.golden @@ -0,0 +1,53 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +from __future__ import annotations + +import json + +from ..models.optional_upload import OptionalUpload +from ..models.text_fields import TextFields +from ..runtime.client import Client +from ..runtime.errors import ApiError + +class MultipartApi: + def __init__(self, client: Client) -> None: + self._client = client + + def send_optional_parts(self, *, body: OptionalUpload | None = None) -> None: + path = "/multipart/optional" + files: dict[str, object] = {} + if body is not None: + if body.attributes is not None: + files["attributes"] = (None, json.dumps(body.attributes.to_dict()), "application/json") + + if body.enabled is not None: + files["enabled"] = (None, str(body.enabled)) + + if body.file is not None: + files["file"] = ("file", body.file, "application/octet-stream") + + if body.retry_count is not None: + files["retry_count"] = (None, str(body.retry_count)) + + if body.title is not None: + files["title"] = (None, str(body.title)) + + + response = self._client.request("POST", path, files=files if files else None) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason_phrase, response.content) + return None + + def send_text_fields(self, *, body: TextFields) -> None: + path = "/multipart/text-only" + files: dict[str, object] = {} + files["enabled"] = (None, str(body.enabled)) + files["note"] = (None, str(body.note)) + files["retry_count"] = (None, str(body.retry_count)) + response = self._client.request("POST", path, files=files if files else None) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason_phrase, response.content) + return None diff --git a/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/models/__init__.py.golden b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/models/__init__.py.golden new file mode 100644 index 000000000..71bfeea0a --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/models/__init__.py.golden @@ -0,0 +1,8 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +from .attributes import Attributes as Attributes +from .optional_upload import OptionalUpload as OptionalUpload +from .text_fields import TextFields as TextFields diff --git a/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/models/attributes.py.golden b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/models/attributes.py.golden new file mode 100644 index 000000000..1d90f0b99 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/models/attributes.py.golden @@ -0,0 +1,27 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +from __future__ import annotations + +from dataclasses import dataclass + +@dataclass +class Attributes: + label: str + priority: int | None = None + + def to_dict(self) -> dict[str, object]: + result: dict[str, object] = {} + result["label"] = self.label + if self.priority is not None: + result["priority"] = self.priority + return result + + @classmethod + def from_dict(cls, data: dict[str, object]) -> Attributes: + return cls( + label=data["label"], # type: ignore[assignment] + priority=data.get("priority"), # type: ignore[assignment] + ) diff --git a/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/models/optional_upload.py.golden b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/models/optional_upload.py.golden new file mode 100644 index 000000000..7a01c050d --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/models/optional_upload.py.golden @@ -0,0 +1,42 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +from __future__ import annotations + +from dataclasses import dataclass + +from .attributes import Attributes + +@dataclass +class OptionalUpload: + attributes: Attributes | None = None + enabled: bool | None = None + file: bytes | None = None + retry_count: int | None = None + title: str | None = None + + def to_dict(self) -> dict[str, object]: + result: dict[str, object] = {} + if self.attributes is not None: + result["attributes"] = self.attributes.to_dict() + if self.enabled is not None: + result["enabled"] = self.enabled + if self.file is not None: + result["file"] = self.file + if self.retry_count is not None: + result["retry_count"] = self.retry_count + if self.title is not None: + result["title"] = self.title + return result + + @classmethod + def from_dict(cls, data: dict[str, object]) -> OptionalUpload: + return cls( + attributes=Attributes.from_dict(data.get("attributes")) if data.get("attributes") is not None else None, # type: ignore[arg-type] + enabled=data.get("enabled"), # type: ignore[assignment] + file=data.get("file"), # type: ignore[assignment] + retry_count=data.get("retry_count"), # type: ignore[assignment] + title=data.get("title"), # type: ignore[assignment] + ) diff --git a/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/models/text_fields.py.golden b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/models/text_fields.py.golden new file mode 100644 index 000000000..ca91a6664 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/models/text_fields.py.golden @@ -0,0 +1,29 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +from __future__ import annotations + +from dataclasses import dataclass + +@dataclass +class TextFields: + enabled: bool + note: str + retry_count: int + + def to_dict(self) -> dict[str, object]: + result: dict[str, object] = {} + result["enabled"] = self.enabled + result["note"] = self.note + result["retry_count"] = self.retry_count + return result + + @classmethod + def from_dict(cls, data: dict[str, object]) -> TextFields: + return cls( + enabled=data["enabled"], # type: ignore[assignment] + note=data["note"], # type: ignore[assignment] + retry_count=data["retry_count"], # type: ignore[assignment] + ) diff --git a/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/py.typed.golden b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/py.typed.golden new file mode 100644 index 000000000..e69de29bb diff --git a/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/runtime/__init__.py.golden b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/runtime/__init__.py.golden new file mode 100644 index 000000000..7226802fc --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/runtime/__init__.py.golden @@ -0,0 +1,10 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +from .auth import ApiKeyAuth as ApiKeyAuth +from .auth import Authenticator as Authenticator +from .auth import BearerAuth as BearerAuth +from .client import Client as Client +from .errors import ApiError as ApiError diff --git a/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/runtime/auth.py.golden b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/runtime/auth.py.golden new file mode 100644 index 000000000..532cf71e7 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/runtime/auth.py.golden @@ -0,0 +1,51 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +"""Authentication helpers.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from collections.abc import Callable + + +class Authenticator(ABC): + """Base class for request authenticators.""" + + @abstractmethod + def auth_headers(self) -> dict[str, str]: + """Return headers to attach to every request.""" + ... + + +class BearerAuth(Authenticator): + """Bearer token authentication. + + Accepts a static token string or a callable that returns the + current token (evaluated on every request). + """ + + def __init__(self, token: str | Callable[[], str]) -> None: + self._token = token + + def auth_headers(self) -> dict[str, str]: + token = self._token() if callable(self._token) else self._token + return {"Authorization": f"Bearer {token}"} + + +class ApiKeyAuth(Authenticator): + """API key authentication via a custom header. + + Accepts a static key string or a callable that returns the + current key (evaluated on every request). + """ + + def __init__(self, header_name: str, api_key: str | Callable[[], str]) -> None: + self._header_name = header_name + self._api_key = api_key + + def auth_headers(self) -> dict[str, str]: + key = self._api_key() if callable(self._api_key) else self._api_key + return {self._header_name: key} diff --git a/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/runtime/client.py.golden b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/runtime/client.py.golden new file mode 100644 index 000000000..8ec6360a1 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/runtime/client.py.golden @@ -0,0 +1,71 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +"""HTTP client wrapping httpx.""" + +from __future__ import annotations + +from typing import Any + +import httpx + +from .auth import Authenticator + + +class Client: + """Synchronous HTTP client for the generated SDK.""" + + def __init__( + self, + base_url: str, + *, + http_client: httpx.Client | None = None, + authenticator: Authenticator | None = None, + ) -> None: + self._base_url = base_url.rstrip("/") + self._http_client = http_client or httpx.Client() + self._authenticator = authenticator + + def request( + self, + method: str, + path: str, + *, + params: dict[str, str] | None = None, + json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, + headers: dict[str, str] | None = None, + ) -> httpx.Response: + """Send an HTTP request and return the raw response.""" + url = f"{self._base_url}{path}" + req_headers: dict[str, str] = {"Accept": "application/json"} + if json is not None: + req_headers["Content-Type"] = "application/json" + if headers: + req_headers.update(headers) + if self._authenticator is not None: + req_headers.update(self._authenticator.auth_headers()) + return self._http_client.request( + method, + url, + params=params, + json=json, + content=content, + data=data, + files=files, + headers=req_headers, + ) + + def close(self) -> None: + """Close the underlying HTTP client.""" + self._http_client.close() + + def __enter__(self) -> Client: + return self + + def __exit__(self, *args: object) -> None: + self.close() diff --git a/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/runtime/errors.py.golden b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/runtime/errors.py.golden new file mode 100644 index 000000000..08c290a21 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-edge-cases/multipart_edge_cases/runtime/errors.py.golden @@ -0,0 +1,18 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +"""API error type.""" + +from __future__ import annotations + + +class ApiError(Exception): + """Raised when the server returns a 4xx or 5xx status code.""" + + def __init__(self, status_code: int, status: str, body: bytes) -> None: + self.status_code = status_code + self.status = status + self.body = body + super().__init__(f"{status_code} {status}") diff --git a/tests/golden/python/python-httpx/multipart-edge-cases/pyproject.toml.golden b/tests/golden/python/python-httpx/multipart-edge-cases/pyproject.toml.golden new file mode 100644 index 000000000..8438fa6a3 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-edge-cases/pyproject.toml.golden @@ -0,0 +1,10 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "multipart_edge_cases" +version = "1.0.0" +description = "Covers optional multipart bodies, optional parts, and text-only multipart fields." +requires-python = ">=3.12" +dependencies = ["httpx>=0.27"] diff --git a/tests/golden/python/python-httpx/multipart-nested-object-parts/README.md.golden b/tests/golden/python/python-httpx/multipart-nested-object-parts/README.md.golden new file mode 100644 index 000000000..22d83ebc7 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-nested-object-parts/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Nested Object Parts + +Covers multipart object parts whose wire names differ from ergonomic names. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart_nested_object_parts`. diff --git a/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/__init__.py.golden b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/__init__.py.golden new file mode 100644 index 000000000..054b30587 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/__init__.py.golden @@ -0,0 +1,10 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +from .runtime import ApiKeyAuth as ApiKeyAuth +from .runtime import Authenticator as Authenticator +from .runtime import BearerAuth as BearerAuth +from .runtime import Client as Client +from .runtime import ApiError as ApiError diff --git a/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/apis/__init__.py.golden b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/apis/__init__.py.golden new file mode 100644 index 000000000..ab7bec874 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/apis/__init__.py.golden @@ -0,0 +1,6 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +from .multipart_api import MultipartApi as MultipartApi diff --git a/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/apis/multipart_api.py.golden b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/apis/multipart_api.py.golden new file mode 100644 index 000000000..9f001468b --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/apis/multipart_api.py.golden @@ -0,0 +1,26 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +from __future__ import annotations + +import json + +from ..models.nested_upload import NestedUpload +from ..runtime.client import Client +from ..runtime.errors import ApiError + +class MultipartApi: + def __init__(self, client: Client) -> None: + self._client = client + + def send_nested_object_part(self, *, body: NestedUpload) -> None: + path = "/multipart/nested-object" + files: dict[str, object] = {} + files["file"] = ("file", body.file, "application/octet-stream") + files["item_config"] = (None, json.dumps(body.item_config.to_dict()), "application/json") + response = self._client.request("POST", path, files=files if files else None) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason_phrase, response.content) + return None diff --git a/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/models/__init__.py.golden b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/models/__init__.py.golden new file mode 100644 index 000000000..2d68dd31f --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/models/__init__.py.golden @@ -0,0 +1,7 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +from .item_config import ItemConfig as ItemConfig +from .nested_upload import NestedUpload as NestedUpload diff --git a/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/models/item_config.py.golden b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/models/item_config.py.golden new file mode 100644 index 000000000..bc1f4a4f6 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/models/item_config.py.golden @@ -0,0 +1,27 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +from __future__ import annotations + +from dataclasses import dataclass + +@dataclass +class ItemConfig: + display_name: str + retention_days: int | None = None + + def to_dict(self) -> dict[str, object]: + result: dict[str, object] = {} + result["display_name"] = self.display_name + if self.retention_days is not None: + result["retention_days"] = self.retention_days + return result + + @classmethod + def from_dict(cls, data: dict[str, object]) -> ItemConfig: + return cls( + display_name=data["display_name"], # type: ignore[assignment] + retention_days=data.get("retention_days"), # type: ignore[assignment] + ) diff --git a/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/models/nested_upload.py.golden b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/models/nested_upload.py.golden new file mode 100644 index 000000000..c96737997 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/models/nested_upload.py.golden @@ -0,0 +1,28 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +from __future__ import annotations + +from dataclasses import dataclass + +from .item_config import ItemConfig + +@dataclass +class NestedUpload: + file: bytes + item_config: ItemConfig + + def to_dict(self) -> dict[str, object]: + result: dict[str, object] = {} + result["file"] = self.file + result["item_config"] = self.item_config.to_dict() + return result + + @classmethod + def from_dict(cls, data: dict[str, object]) -> NestedUpload: + return cls( + file=data["file"], # type: ignore[assignment] + item_config=ItemConfig.from_dict(data["item_config"]), # type: ignore[arg-type] + ) diff --git a/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/py.typed.golden b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/py.typed.golden new file mode 100644 index 000000000..e69de29bb diff --git a/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/runtime/__init__.py.golden b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/runtime/__init__.py.golden new file mode 100644 index 000000000..977ff9b16 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/runtime/__init__.py.golden @@ -0,0 +1,10 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +from .auth import ApiKeyAuth as ApiKeyAuth +from .auth import Authenticator as Authenticator +from .auth import BearerAuth as BearerAuth +from .client import Client as Client +from .errors import ApiError as ApiError diff --git a/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/runtime/auth.py.golden b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/runtime/auth.py.golden new file mode 100644 index 000000000..b491b7565 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/runtime/auth.py.golden @@ -0,0 +1,51 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +"""Authentication helpers.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from collections.abc import Callable + + +class Authenticator(ABC): + """Base class for request authenticators.""" + + @abstractmethod + def auth_headers(self) -> dict[str, str]: + """Return headers to attach to every request.""" + ... + + +class BearerAuth(Authenticator): + """Bearer token authentication. + + Accepts a static token string or a callable that returns the + current token (evaluated on every request). + """ + + def __init__(self, token: str | Callable[[], str]) -> None: + self._token = token + + def auth_headers(self) -> dict[str, str]: + token = self._token() if callable(self._token) else self._token + return {"Authorization": f"Bearer {token}"} + + +class ApiKeyAuth(Authenticator): + """API key authentication via a custom header. + + Accepts a static key string or a callable that returns the + current key (evaluated on every request). + """ + + def __init__(self, header_name: str, api_key: str | Callable[[], str]) -> None: + self._header_name = header_name + self._api_key = api_key + + def auth_headers(self) -> dict[str, str]: + key = self._api_key() if callable(self._api_key) else self._api_key + return {self._header_name: key} diff --git a/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/runtime/client.py.golden b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/runtime/client.py.golden new file mode 100644 index 000000000..7d7af7db9 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/runtime/client.py.golden @@ -0,0 +1,71 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +"""HTTP client wrapping httpx.""" + +from __future__ import annotations + +from typing import Any + +import httpx + +from .auth import Authenticator + + +class Client: + """Synchronous HTTP client for the generated SDK.""" + + def __init__( + self, + base_url: str, + *, + http_client: httpx.Client | None = None, + authenticator: Authenticator | None = None, + ) -> None: + self._base_url = base_url.rstrip("/") + self._http_client = http_client or httpx.Client() + self._authenticator = authenticator + + def request( + self, + method: str, + path: str, + *, + params: dict[str, str] | None = None, + json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, + headers: dict[str, str] | None = None, + ) -> httpx.Response: + """Send an HTTP request and return the raw response.""" + url = f"{self._base_url}{path}" + req_headers: dict[str, str] = {"Accept": "application/json"} + if json is not None: + req_headers["Content-Type"] = "application/json" + if headers: + req_headers.update(headers) + if self._authenticator is not None: + req_headers.update(self._authenticator.auth_headers()) + return self._http_client.request( + method, + url, + params=params, + json=json, + content=content, + data=data, + files=files, + headers=req_headers, + ) + + def close(self) -> None: + """Close the underlying HTTP client.""" + self._http_client.close() + + def __enter__(self) -> Client: + return self + + def __exit__(self, *args: object) -> None: + self.close() diff --git a/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/runtime/errors.py.golden b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/runtime/errors.py.golden new file mode 100644 index 000000000..460f5f0d4 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-nested-object-parts/multipart_nested_object_parts/runtime/errors.py.golden @@ -0,0 +1,18 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +"""API error type.""" + +from __future__ import annotations + + +class ApiError(Exception): + """Raised when the server returns a 4xx or 5xx status code.""" + + def __init__(self, status_code: int, status: str, body: bytes) -> None: + self.status_code = status_code + self.status = status + self.body = body + super().__init__(f"{status_code} {status}") diff --git a/tests/golden/python/python-httpx/multipart-nested-object-parts/pyproject.toml.golden b/tests/golden/python/python-httpx/multipart-nested-object-parts/pyproject.toml.golden new file mode 100644 index 000000000..461ace1f3 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-nested-object-parts/pyproject.toml.golden @@ -0,0 +1,10 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "multipart_nested_object_parts" +version = "1.0.0" +description = "Covers multipart object parts whose wire names differ from ergonomic names." +requires-python = ">=3.12" +dependencies = ["httpx>=0.27"] diff --git a/tests/golden/python/python-httpx/multipart-unsupported-schema/README.md.golden b/tests/golden/python/python-httpx/multipart-unsupported-schema/README.md.golden new file mode 100644 index 000000000..53e55944b --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-unsupported-schema/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Unsupported Schema + +Covers multipart request bodies that are not object-shaped. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart_unsupported_schema`. diff --git a/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/__init__.py.golden b/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/__init__.py.golden new file mode 100644 index 000000000..e0315de8c --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/__init__.py.golden @@ -0,0 +1,10 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Unsupported Schema — 1.0.0 +# Covers multipart request bodies that are not object-shaped. + +from .runtime import ApiKeyAuth as ApiKeyAuth +from .runtime import Authenticator as Authenticator +from .runtime import BearerAuth as BearerAuth +from .runtime import Client as Client +from .runtime import ApiError as ApiError diff --git a/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/apis/__init__.py.golden b/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/apis/__init__.py.golden new file mode 100644 index 000000000..fcab6d1f4 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/apis/__init__.py.golden @@ -0,0 +1,6 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Unsupported Schema — 1.0.0 +# Covers multipart request bodies that are not object-shaped. + +from .transfer_api import TransferApi as TransferApi diff --git a/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/apis/transfer_api.py.golden b/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/apis/transfer_api.py.golden new file mode 100644 index 000000000..c82d36c53 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/apis/transfer_api.py.golden @@ -0,0 +1,21 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Unsupported Schema — 1.0.0 +# Covers multipart request bodies that are not object-shaped. + +from __future__ import annotations + +from ..runtime.client import Client +from ..runtime.errors import ApiError + +class TransferApi: + def __init__(self, client: Client) -> None: + self._client = client + + def upload_raw_multipart(self, *, body: bytes) -> None: + path = "/uploads/raw" + raise ValueError("unsupported multipart request body: schema must be object-shaped") + response = self._client.request("POST", path) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason_phrase, response.content) + return None diff --git a/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/models/__init__.py.golden b/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/models/__init__.py.golden new file mode 100644 index 000000000..1f7489a4a --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/models/__init__.py.golden @@ -0,0 +1,5 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Unsupported Schema — 1.0.0 +# Covers multipart request bodies that are not object-shaped. + diff --git a/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/py.typed.golden b/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/py.typed.golden new file mode 100644 index 000000000..e69de29bb diff --git a/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/runtime/__init__.py.golden b/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/runtime/__init__.py.golden new file mode 100644 index 000000000..d07130e91 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/runtime/__init__.py.golden @@ -0,0 +1,10 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Unsupported Schema — 1.0.0 +# Covers multipart request bodies that are not object-shaped. + +from .auth import ApiKeyAuth as ApiKeyAuth +from .auth import Authenticator as Authenticator +from .auth import BearerAuth as BearerAuth +from .client import Client as Client +from .errors import ApiError as ApiError diff --git a/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/runtime/auth.py.golden b/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/runtime/auth.py.golden new file mode 100644 index 000000000..3aa190459 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/runtime/auth.py.golden @@ -0,0 +1,51 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Unsupported Schema — 1.0.0 +# Covers multipart request bodies that are not object-shaped. + +"""Authentication helpers.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from collections.abc import Callable + + +class Authenticator(ABC): + """Base class for request authenticators.""" + + @abstractmethod + def auth_headers(self) -> dict[str, str]: + """Return headers to attach to every request.""" + ... + + +class BearerAuth(Authenticator): + """Bearer token authentication. + + Accepts a static token string or a callable that returns the + current token (evaluated on every request). + """ + + def __init__(self, token: str | Callable[[], str]) -> None: + self._token = token + + def auth_headers(self) -> dict[str, str]: + token = self._token() if callable(self._token) else self._token + return {"Authorization": f"Bearer {token}"} + + +class ApiKeyAuth(Authenticator): + """API key authentication via a custom header. + + Accepts a static key string or a callable that returns the + current key (evaluated on every request). + """ + + def __init__(self, header_name: str, api_key: str | Callable[[], str]) -> None: + self._header_name = header_name + self._api_key = api_key + + def auth_headers(self) -> dict[str, str]: + key = self._api_key() if callable(self._api_key) else self._api_key + return {self._header_name: key} diff --git a/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/runtime/client.py.golden b/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/runtime/client.py.golden new file mode 100644 index 000000000..23e11ae8a --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/runtime/client.py.golden @@ -0,0 +1,71 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Unsupported Schema — 1.0.0 +# Covers multipart request bodies that are not object-shaped. + +"""HTTP client wrapping httpx.""" + +from __future__ import annotations + +from typing import Any + +import httpx + +from .auth import Authenticator + + +class Client: + """Synchronous HTTP client for the generated SDK.""" + + def __init__( + self, + base_url: str, + *, + http_client: httpx.Client | None = None, + authenticator: Authenticator | None = None, + ) -> None: + self._base_url = base_url.rstrip("/") + self._http_client = http_client or httpx.Client() + self._authenticator = authenticator + + def request( + self, + method: str, + path: str, + *, + params: dict[str, str] | None = None, + json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, + headers: dict[str, str] | None = None, + ) -> httpx.Response: + """Send an HTTP request and return the raw response.""" + url = f"{self._base_url}{path}" + req_headers: dict[str, str] = {"Accept": "application/json"} + if json is not None: + req_headers["Content-Type"] = "application/json" + if headers: + req_headers.update(headers) + if self._authenticator is not None: + req_headers.update(self._authenticator.auth_headers()) + return self._http_client.request( + method, + url, + params=params, + json=json, + content=content, + data=data, + files=files, + headers=req_headers, + ) + + def close(self) -> None: + """Close the underlying HTTP client.""" + self._http_client.close() + + def __enter__(self) -> Client: + return self + + def __exit__(self, *args: object) -> None: + self.close() diff --git a/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/runtime/errors.py.golden b/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/runtime/errors.py.golden new file mode 100644 index 000000000..efee62d21 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-unsupported-schema/multipart_unsupported_schema/runtime/errors.py.golden @@ -0,0 +1,18 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Unsupported Schema — 1.0.0 +# Covers multipart request bodies that are not object-shaped. + +"""API error type.""" + +from __future__ import annotations + + +class ApiError(Exception): + """Raised when the server returns a 4xx or 5xx status code.""" + + def __init__(self, status_code: int, status: str, body: bytes) -> None: + self.status_code = status_code + self.status = status + self.body = body + super().__init__(f"{status_code} {status}") diff --git a/tests/golden/python/python-httpx/multipart-unsupported-schema/pyproject.toml.golden b/tests/golden/python/python-httpx/multipart-unsupported-schema/pyproject.toml.golden new file mode 100644 index 000000000..0d80378f8 --- /dev/null +++ b/tests/golden/python/python-httpx/multipart-unsupported-schema/pyproject.toml.golden @@ -0,0 +1,10 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "multipart_unsupported_schema" +version = "1.0.0" +description = "Covers multipart request bodies that are not object-shaped." +requires-python = ">=3.12" +dependencies = ["httpx>=0.27"] diff --git a/tests/golden/python/python-httpx/multiple-similar-request-schemas/multiple_similar_request_schemas_api/apis/test_api_api.py.golden b/tests/golden/python/python-httpx/multiple-similar-request-schemas/multiple_similar_request_schemas_api/apis/test_api_api.py.golden index 7d69d7d68..cd2addd8e 100644 --- a/tests/golden/python/python-httpx/multiple-similar-request-schemas/multiple_similar_request_schemas_api/apis/test_api_api.py.golden +++ b/tests/golden/python/python-httpx/multiple-similar-request-schemas/multiple_similar_request_schemas_api/apis/test_api_api.py.golden @@ -19,7 +19,9 @@ class TestApiApi: def create_type_a(self, *, body: CreateRequestTypeA) -> ResourceTypeA: """Create TypeA resource.""" path = "/api/v1/type-a/resources" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return ResourceTypeA.from_dict(response.json()) @@ -27,7 +29,9 @@ class TestApiApi: def create_type_b(self, *, body: CreateRequestTypeB) -> ResourceTypeB: """Create TypeB resource.""" path = "/api/v1/type-b/resources" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return ResourceTypeB.from_dict(response.json()) diff --git a/tests/golden/python/python-httpx/multiple-similar-request-schemas/multiple_similar_request_schemas_api/runtime/client.py.golden b/tests/golden/python/python-httpx/multiple-similar-request-schemas/multiple_similar_request_schemas_api/runtime/client.py.golden index 2237b0e6b..444287b2b 100644 --- a/tests/golden/python/python-httpx/multiple-similar-request-schemas/multiple_similar_request_schemas_api/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/multiple-similar-request-schemas/multiple_similar_request_schemas_api/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/naming-conventions/naming_conventions_test/apis/default_api.py.golden b/tests/golden/python/python-httpx/naming-conventions/naming_conventions_test/apis/default_api.py.golden index 6cda819d1..0f0d48526 100644 --- a/tests/golden/python/python-httpx/naming-conventions/naming_conventions_test/apis/default_api.py.golden +++ b/tests/golden/python/python-httpx/naming-conventions/naming_conventions_test/apis/default_api.py.golden @@ -53,7 +53,9 @@ class DefaultApi: def test_naming_conventions(self, *, body: NamingConventionTest) -> NamingConventionTest: """Test endpoint with various parameter naming conventions.""" path = "/test" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return NamingConventionTest.from_dict(response.json()) diff --git a/tests/golden/python/python-httpx/naming-conventions/naming_conventions_test/runtime/client.py.golden b/tests/golden/python/python-httpx/naming-conventions/naming_conventions_test/runtime/client.py.golden index 817e32f65..c68ea80b3 100644 --- a/tests/golden/python/python-httpx/naming-conventions/naming_conventions_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/naming-conventions/naming_conventions_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/petstore/petstore_api/apis/pet_api.py.golden b/tests/golden/python/python-httpx/petstore/petstore_api/apis/pet_api.py.golden index 1f98b0795..f2f3316a6 100644 --- a/tests/golden/python/python-httpx/petstore/petstore_api/apis/pet_api.py.golden +++ b/tests/golden/python/python-httpx/petstore/petstore_api/apis/pet_api.py.golden @@ -17,7 +17,9 @@ class PetApi: def update_pet(self, *, body: Pet) -> Pet: """Update an existing pet.""" path = "/pet" - response = self._client.request("PUT", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("PUT", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return Pet.from_dict(response.json()) @@ -25,7 +27,9 @@ class PetApi: def add_pet(self, *, body: Pet) -> Pet: """Add a new pet to the store.""" path = "/pet" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return Pet.from_dict(response.json()) diff --git a/tests/golden/python/python-httpx/petstore/petstore_api/apis/store_api.py.golden b/tests/golden/python/python-httpx/petstore/petstore_api/apis/store_api.py.golden index 1f716e20e..d672877b8 100644 --- a/tests/golden/python/python-httpx/petstore/petstore_api/apis/store_api.py.golden +++ b/tests/golden/python/python-httpx/petstore/petstore_api/apis/store_api.py.golden @@ -24,7 +24,9 @@ class StoreApi: def place_order(self, *, body: Order) -> Order: """Place an order for a pet.""" path = "/store/order" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return Order.from_dict(response.json()) diff --git a/tests/golden/python/python-httpx/petstore/petstore_api/apis/user_api.py.golden b/tests/golden/python/python-httpx/petstore/petstore_api/apis/user_api.py.golden index c71d9f280..606b909c2 100644 --- a/tests/golden/python/python-httpx/petstore/petstore_api/apis/user_api.py.golden +++ b/tests/golden/python/python-httpx/petstore/petstore_api/apis/user_api.py.golden @@ -16,7 +16,9 @@ class UserApi: def create_user(self, *, body: User) -> User: """Create user.""" path = "/user" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return User.from_dict(response.json()) @@ -24,7 +26,9 @@ class UserApi: def create_users_with_list_input(self, *, body: list[User]) -> User: """Creates list of users with given input array.""" path = "/user/createWithList" - response = self._client.request("POST", path, json=[item.to_dict() for item in body]) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=[item.to_dict() for item in body], headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return User.from_dict(response.json()) @@ -61,7 +65,9 @@ class UserApi: def update_user(self, username: str, *, body: User) -> None: """Update user.""" path = f"/user/{username}" - response = self._client.request("PUT", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("PUT", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return None diff --git a/tests/golden/python/python-httpx/petstore/petstore_api/runtime/client.py.golden b/tests/golden/python/python-httpx/petstore/petstore_api/runtime/client.py.golden index 77cd995c4..a59a6a3c3 100644 --- a/tests/golden/python/python-httpx/petstore/petstore_api/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/petstore/petstore_api/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/query-param-enum/query_parameter_enum_test/runtime/client.py.golden b/tests/golden/python/python-httpx/query-param-enum/query_parameter_enum_test/runtime/client.py.golden index b8ab4629b..dacecd1cc 100644 --- a/tests/golden/python/python-httpx/query-param-enum/query_parameter_enum_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/query-param-enum/query_parameter_enum_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/recursive-json-all-optional-properties/all_optional_properties_test/runtime/client.py.golden b/tests/golden/python/python-httpx/recursive-json-all-optional-properties/all_optional_properties_test/runtime/client.py.golden index b3f8a8c1d..fa290f002 100644 --- a/tests/golden/python/python-httpx/recursive-json-all-optional-properties/all_optional_properties_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/recursive-json-all-optional-properties/all_optional_properties_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/recursive-json-array-of-inline-objects/array_of_inline_objects_test/runtime/client.py.golden b/tests/golden/python/python-httpx/recursive-json-array-of-inline-objects/array_of_inline_objects_test/runtime/client.py.golden index 2bcfb0f21..c1ec3edef 100644 --- a/tests/golden/python/python-httpx/recursive-json-array-of-inline-objects/array_of_inline_objects_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/recursive-json-array-of-inline-objects/array_of_inline_objects_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/recursive-json-array-of-referenced-types/array_of_referenced_types_test/runtime/client.py.golden b/tests/golden/python/python-httpx/recursive-json-array-of-referenced-types/array_of_referenced_types_test/runtime/client.py.golden index c9a8c0ac3..d6a7c8fae 100644 --- a/tests/golden/python/python-httpx/recursive-json-array-of-referenced-types/array_of_referenced_types_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/recursive-json-array-of-referenced-types/array_of_referenced_types_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/recursive-json-array-with-reference-property/array_with_reference_property_test/runtime/client.py.golden b/tests/golden/python/python-httpx/recursive-json-array-with-reference-property/array_with_reference_property_test/runtime/client.py.golden index fdb500e32..44724a05b 100644 --- a/tests/golden/python/python-httpx/recursive-json-array-with-reference-property/array_with_reference_property_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/recursive-json-array-with-reference-property/array_with_reference_property_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/recursive-json-complex-array-structure/complex_array_structure_test/runtime/client.py.golden b/tests/golden/python/python-httpx/recursive-json-complex-array-structure/complex_array_structure_test/runtime/client.py.golden index 4993c0156..b6f349fdb 100644 --- a/tests/golden/python/python-httpx/recursive-json-complex-array-structure/complex_array_structure_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/recursive-json-complex-array-structure/complex_array_structure_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/recursive-json-deeply-nested-inline/deeply_nested_inline_objects_test/runtime/client.py.golden b/tests/golden/python/python-httpx/recursive-json-deeply-nested-inline/deeply_nested_inline_objects_test/runtime/client.py.golden index 1a8335f01..a19b12344 100644 --- a/tests/golden/python/python-httpx/recursive-json-deeply-nested-inline/deeply_nested_inline_objects_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/recursive-json-deeply-nested-inline/deeply_nested_inline_objects_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/recursive-json-empty-array/empty_array_test/runtime/client.py.golden b/tests/golden/python/python-httpx/recursive-json-empty-array/empty_array_test/runtime/client.py.golden index dea190ed6..a0342ce81 100644 --- a/tests/golden/python/python-httpx/recursive-json-empty-array/empty_array_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/recursive-json-empty-array/empty_array_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/recursive-json-inline-object-with-array/inline_object_with_array_test/runtime/client.py.golden b/tests/golden/python/python-httpx/recursive-json-inline-object-with-array/inline_object_with_array_test/runtime/client.py.golden index 9512adafd..b28f50c91 100644 --- a/tests/golden/python/python-httpx/recursive-json-inline-object-with-array/inline_object_with_array_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/recursive-json-inline-object-with-array/inline_object_with_array_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/recursive-json-inline-object/inline_object_test/runtime/client.py.golden b/tests/golden/python/python-httpx/recursive-json-inline-object/inline_object_test/runtime/client.py.golden index 24dfda16e..80d74fc21 100644 --- a/tests/golden/python/python-httpx/recursive-json-inline-object/inline_object_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/recursive-json-inline-object/inline_object_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/recursive-json-mixed-property-types/mixed_property_types_test/runtime/client.py.golden b/tests/golden/python/python-httpx/recursive-json-mixed-property-types/mixed_property_types_test/runtime/client.py.golden index cb920038f..26e1adb52 100644 --- a/tests/golden/python/python-httpx/recursive-json-mixed-property-types/mixed_property_types_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/recursive-json-mixed-property-types/mixed_property_types_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/recursive-json-nested-object-reference/nested_object_reference_test/runtime/client.py.golden b/tests/golden/python/python-httpx/recursive-json-nested-object-reference/nested_object_reference_test/runtime/client.py.golden index cca6a05af..2650633e3 100644 --- a/tests/golden/python/python-httpx/recursive-json-nested-object-reference/nested_object_reference_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/recursive-json-nested-object-reference/nested_object_reference_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/recursive-json-optional-array-of-inline-objects/optional_array_of_inline_objects_test/runtime/client.py.golden b/tests/golden/python/python-httpx/recursive-json-optional-array-of-inline-objects/optional_array_of_inline_objects_test/runtime/client.py.golden index 88845d3ef..e5c66d24c 100644 --- a/tests/golden/python/python-httpx/recursive-json-optional-array-of-inline-objects/optional_array_of_inline_objects_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/recursive-json-optional-array-of-inline-objects/optional_array_of_inline_objects_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/recursive-json-optional-array-of-referenced-types/optional_array_of_referenced_types_test/runtime/client.py.golden b/tests/golden/python/python-httpx/recursive-json-optional-array-of-referenced-types/optional_array_of_referenced_types_test/runtime/client.py.golden index 3cd9e6c19..01f543c39 100644 --- a/tests/golden/python/python-httpx/recursive-json-optional-array-of-referenced-types/optional_array_of_referenced_types_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/recursive-json-optional-array-of-referenced-types/optional_array_of_referenced_types_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/recursive-json-optional-inline-object/optional_inline_object_test/runtime/client.py.golden b/tests/golden/python/python-httpx/recursive-json-optional-inline-object/optional_inline_object_test/runtime/client.py.golden index d74e57dc6..19ca43047 100644 --- a/tests/golden/python/python-httpx/recursive-json-optional-inline-object/optional_inline_object_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/recursive-json-optional-inline-object/optional_inline_object_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/recursive-json-optional-nested-object-reference/optional_nested_object_reference_test/runtime/client.py.golden b/tests/golden/python/python-httpx/recursive-json-optional-nested-object-reference/optional_nested_object_reference_test/runtime/client.py.golden index 43a00f300..83711c4a0 100644 --- a/tests/golden/python/python-httpx/recursive-json-optional-nested-object-reference/optional_nested_object_reference_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/recursive-json-optional-nested-object-reference/optional_nested_object_reference_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/recursive-json-primitive-array/primitive_array_test/runtime/client.py.golden b/tests/golden/python/python-httpx/recursive-json-primitive-array/primitive_array_test/runtime/client.py.golden index 6bd562132..b14bb619a 100644 --- a/tests/golden/python/python-httpx/recursive-json-primitive-array/primitive_array_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/recursive-json-primitive-array/primitive_array_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/request-body-content-types/request_body_content_types/apis/default_api.py.golden b/tests/golden/python/python-httpx/request-body-content-types/request_body_content_types/apis/default_api.py.golden index d9d9ccd0b..6938a189e 100644 --- a/tests/golden/python/python-httpx/request-body-content-types/request_body_content_types/apis/default_api.py.golden +++ b/tests/golden/python/python-httpx/request-body-content-types/request_body_content_types/apis/default_api.py.golden @@ -15,56 +15,71 @@ class DefaultApi: def post_binary(self, *, body: bytes) -> None: path = "/binary" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/octet-stream" + response = self._client.request("POST", path, content=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return None def post_form(self, *, body: Payload) -> None: path = "/form" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/x-www-form-urlencoded" + response = self._client.request("POST", path, data=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return None def post_json(self, *, body: Payload) -> None: path = "/json" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return None def post_json_or_xml(self, *, body: Payload) -> None: path = "/json-or-xml" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return None def patch_merge_json(self, *, body: Payload) -> None: path = "/merge-patch" - response = self._client.request("PATCH", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/merge-patch+json" + response = self._client.request("PATCH", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return None def post_text(self, *, body: str) -> None: path = "/text" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "text/plain" + response = self._client.request("POST", path, content=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return None def post_xml(self, *, body: Payload) -> None: path = "/xml" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/xml" + raise ValueError("unsupported request body media type: application/xml") + response = self._client.request("POST", path, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return None - def get_xml_response(self) -> Payload: + def get_xml_response(self) -> str: path = "/xml-response" response = self._client.request("GET", path) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) - return Payload.from_dict(response.json()) + return response.text diff --git a/tests/golden/python/python-httpx/request-body-content-types/request_body_content_types/runtime/client.py.golden b/tests/golden/python/python-httpx/request-body-content-types/request_body_content_types/runtime/client.py.golden index b8ea190ec..111ed3d21 100644 --- a/tests/golden/python/python-httpx/request-body-content-types/request_body_content_types/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/request-body-content-types/request_body_content_types/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/response-body-default-and-exact/default_and_exact_response_api/runtime/client.py.golden b/tests/golden/python/python-httpx/response-body-default-and-exact/default_and_exact_response_api/runtime/client.py.golden index 7ab5334c7..eef6ab784 100644 --- a/tests/golden/python/python-httpx/response-body-default-and-exact/default_and_exact_response_api/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/response-body-default-and-exact/default_and_exact_response_api/runtime/client.py.golden @@ -34,6 +34,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -50,6 +53,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/response-body-fallback/response_fallback_test_api/runtime/client.py.golden b/tests/golden/python/python-httpx/response-body-fallback/response_fallback_test_api/runtime/client.py.golden index 4e5462d97..089dfa999 100644 --- a/tests/golden/python/python-httpx/response-body-fallback/response_fallback_test_api/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/response-body-fallback/response_fallback_test_api/runtime/client.py.golden @@ -34,6 +34,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -50,6 +53,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/response-body-multi-status-responses/multi_status_api/runtime/client.py.golden b/tests/golden/python/python-httpx/response-body-multi-status-responses/multi_status_api/runtime/client.py.golden index c66ee2d82..a85f5218a 100644 --- a/tests/golden/python/python-httpx/response-body-multi-status-responses/multi_status_api/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/response-body-multi-status-responses/multi_status_api/runtime/client.py.golden @@ -34,6 +34,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -50,6 +53,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/response-body-no-response-body/no_response_body_api/apis/foo_api.py.golden b/tests/golden/python/python-httpx/response-body-no-response-body/no_response_body_api/apis/foo_api.py.golden index b9dbc96b8..4eebefb74 100644 --- a/tests/golden/python/python-httpx/response-body-no-response-body/no_response_body_api/apis/foo_api.py.golden +++ b/tests/golden/python/python-httpx/response-body-no-response-body/no_response_body_api/apis/foo_api.py.golden @@ -15,7 +15,9 @@ class FooApi: def update_foo_bar(self, foo_id: str, *, body: FooRequest) -> ErrorResponse: path = f"/foo/bar" - response = self._client.request("PATCH", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("PATCH", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return ErrorResponse.from_dict(response.json()) diff --git a/tests/golden/python/python-httpx/response-body-no-response-body/no_response_body_api/runtime/client.py.golden b/tests/golden/python/python-httpx/response-body-no-response-body/no_response_body_api/runtime/client.py.golden index 909cd0bc8..a5feec5ba 100644 --- a/tests/golden/python/python-httpx/response-body-no-response-body/no_response_body_api/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/response-body-no-response-body/no_response_body_api/runtime/client.py.golden @@ -34,6 +34,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -50,6 +53,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/server-object/test_api_with_server_objects/runtime/client.py.golden b/tests/golden/python/python-httpx/server-object/test_api_with_server_objects/runtime/client.py.golden index 0447e1101..be91092c5 100644 --- a/tests/golden/python/python-httpx/server-object/test_api_with_server_objects/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/server-object/test_api_with_server_objects/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/server-path-prefix/server_path_prefix_test/runtime/client.py.golden b/tests/golden/python/python-httpx/server-path-prefix/server_path_prefix_test/runtime/client.py.golden index 929a4a1e9..5d57a048d 100644 --- a/tests/golden/python/python-httpx/server-path-prefix/server_path_prefix_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/server-path-prefix/server_path_prefix_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/type-aliases-complex-union/complex_union_type_test/apis/default_api.py.golden b/tests/golden/python/python-httpx/type-aliases-complex-union/complex_union_type_test/apis/default_api.py.golden index 4b47ceef4..77c68b18a 100644 --- a/tests/golden/python/python-httpx/type-aliases-complex-union/complex_union_type_test/apis/default_api.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-complex-union/complex_union_type_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def test_complex_union(self, *, body: Foo) -> Foo: """Test endpoint with complex union type.""" path = "/test" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-httpx/type-aliases-complex-union/complex_union_type_test/runtime/client.py.golden b/tests/golden/python/python-httpx/type-aliases-complex-union/complex_union_type_test/runtime/client.py.golden index 0d12d776d..23b737cc3 100644 --- a/tests/golden/python/python-httpx/type-aliases-complex-union/complex_union_type_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-complex-union/complex_union_type_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/type-aliases-discriminated-union-inline-discriminator-only/discriminated_union_with_inline_discriminator_only_test/apis/default_api.py.golden b/tests/golden/python/python-httpx/type-aliases-discriminated-union-inline-discriminator-only/discriminated_union_with_inline_discriminator_only_test/apis/default_api.py.golden index f6a1c2e7c..d1ffd6414 100644 --- a/tests/golden/python/python-httpx/type-aliases-discriminated-union-inline-discriminator-only/discriminated_union_with_inline_discriminator_only_test/apis/default_api.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-discriminated-union-inline-discriminator-only/discriminated_union_with_inline_discriminator_only_test/apis/default_api.py.golden @@ -25,7 +25,9 @@ class DefaultApi: def create_event(self, *, body: Event) -> Event: """Create event with discriminated union.""" path = "/event" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-httpx/type-aliases-discriminated-union-inline-discriminator-only/discriminated_union_with_inline_discriminator_only_test/runtime/client.py.golden b/tests/golden/python/python-httpx/type-aliases-discriminated-union-inline-discriminator-only/discriminated_union_with_inline_discriminator_only_test/runtime/client.py.golden index a68e70178..a4826236a 100644 --- a/tests/golden/python/python-httpx/type-aliases-discriminated-union-inline-discriminator-only/discriminated_union_with_inline_discriminator_only_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-discriminated-union-inline-discriminator-only/discriminated_union_with_inline_discriminator_only_test/runtime/client.py.golden @@ -44,6 +44,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -60,6 +63,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/type-aliases-discriminated-union-internally-tagged/discriminated_union_internally_tagged_test/apis/default_api.py.golden b/tests/golden/python/python-httpx/type-aliases-discriminated-union-internally-tagged/discriminated_union_internally_tagged_test/apis/default_api.py.golden index a90147337..ff07b0011 100644 --- a/tests/golden/python/python-httpx/type-aliases-discriminated-union-internally-tagged/discriminated_union_internally_tagged_test/apis/default_api.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-discriminated-union-internally-tagged/discriminated_union_internally_tagged_test/apis/default_api.py.golden @@ -18,7 +18,9 @@ class DefaultApi: def create_resource(self, *, body: Resource) -> Resource: """Create resource with discriminated union.""" path = "/resource" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-httpx/type-aliases-discriminated-union-internally-tagged/discriminated_union_internally_tagged_test/runtime/client.py.golden b/tests/golden/python/python-httpx/type-aliases-discriminated-union-internally-tagged/discriminated_union_internally_tagged_test/runtime/client.py.golden index 65da56e6b..a2ee80d9a 100644 --- a/tests/golden/python/python-httpx/type-aliases-discriminated-union-internally-tagged/discriminated_union_internally_tagged_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-discriminated-union-internally-tagged/discriminated_union_internally_tagged_test/runtime/client.py.golden @@ -37,6 +37,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -53,6 +56,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/type-aliases-discriminated-union-long-names/discriminated_union_with_long_type_names_test/apis/default_api.py.golden b/tests/golden/python/python-httpx/type-aliases-discriminated-union-long-names/discriminated_union_with_long_type_names_test/apis/default_api.py.golden index ec3cad108..6c40f0f3d 100644 --- a/tests/golden/python/python-httpx/type-aliases-discriminated-union-long-names/discriminated_union_with_long_type_names_test/apis/default_api.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-discriminated-union-long-names/discriminated_union_with_long_type_names_test/apis/default_api.py.golden @@ -18,7 +18,9 @@ class DefaultApi: def create_resource(self, *, body: VeryLongDiscriminatedUnionTypeNameForTesting20260630) -> VeryLongDiscriminatedUnionTypeNameForTesting20260630: """Create resource.""" path = "/resource" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-httpx/type-aliases-discriminated-union-long-names/discriminated_union_with_long_type_names_test/runtime/client.py.golden b/tests/golden/python/python-httpx/type-aliases-discriminated-union-long-names/discriminated_union_with_long_type_names_test/runtime/client.py.golden index 98e63f5cc..702a07927 100644 --- a/tests/golden/python/python-httpx/type-aliases-discriminated-union-long-names/discriminated_union_with_long_type_names_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-discriminated-union-long-names/discriminated_union_with_long_type_names_test/runtime/client.py.golden @@ -37,6 +37,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -53,6 +56,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/type-aliases-discriminated-union-mixed-unit-and-allof/discriminated_union_mixed_unit_and_all_of_test/apis/default_api.py.golden b/tests/golden/python/python-httpx/type-aliases-discriminated-union-mixed-unit-and-allof/discriminated_union_mixed_unit_and_all_of_test/apis/default_api.py.golden index b55b01292..67545ae20 100644 --- a/tests/golden/python/python-httpx/type-aliases-discriminated-union-mixed-unit-and-allof/discriminated_union_mixed_unit_and_all_of_test/apis/default_api.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-discriminated-union-mixed-unit-and-allof/discriminated_union_mixed_unit_and_all_of_test/apis/default_api.py.golden @@ -21,7 +21,9 @@ class DefaultApi: def create_resource(self, *, body: Setup) -> Setup: """Create resource.""" path = "/resource" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-httpx/type-aliases-discriminated-union-mixed-unit-and-allof/discriminated_union_mixed_unit_and_all_of_test/runtime/client.py.golden b/tests/golden/python/python-httpx/type-aliases-discriminated-union-mixed-unit-and-allof/discriminated_union_mixed_unit_and_all_of_test/runtime/client.py.golden index f1c5a4378..781fb25a1 100644 --- a/tests/golden/python/python-httpx/type-aliases-discriminated-union-mixed-unit-and-allof/discriminated_union_mixed_unit_and_all_of_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-discriminated-union-mixed-unit-and-allof/discriminated_union_mixed_unit_and_all_of_test/runtime/client.py.golden @@ -40,6 +40,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -56,6 +59,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/type-aliases-discriminated-union-multiple/multiple_discriminated_unions_test/apis/default_api.py.golden b/tests/golden/python/python-httpx/type-aliases-discriminated-union-multiple/multiple_discriminated_unions_test/apis/default_api.py.golden index 27c97aeb8..01a5fa8c4 100644 --- a/tests/golden/python/python-httpx/type-aliases-discriminated-union-multiple/multiple_discriminated_unions_test/apis/default_api.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-discriminated-union-multiple/multiple_discriminated_unions_test/apis/default_api.py.golden @@ -20,7 +20,9 @@ class DefaultApi: def create_container(self, *, body: ContainerKind) -> ContainerKind: """Create container with ContainerKind discriminated union.""" path = "/container" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] @@ -28,7 +30,9 @@ class DefaultApi: def create_volume(self, *, body: VolumeKind) -> VolumeKind: """Create volume with VolumeKind discriminated union.""" path = "/volume" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-httpx/type-aliases-discriminated-union-multiple/multiple_discriminated_unions_test/runtime/client.py.golden b/tests/golden/python/python-httpx/type-aliases-discriminated-union-multiple/multiple_discriminated_unions_test/runtime/client.py.golden index 4b2e051c6..dc386b2e7 100644 --- a/tests/golden/python/python-httpx/type-aliases-discriminated-union-multiple/multiple_discriminated_unions_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-discriminated-union-multiple/multiple_discriminated_unions_test/runtime/client.py.golden @@ -38,6 +38,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -54,6 +57,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/type-aliases-discriminated-union-with-refs/discriminated_union_with_references_test/apis/default_api.py.golden b/tests/golden/python/python-httpx/type-aliases-discriminated-union-with-refs/discriminated_union_with_references_test/apis/default_api.py.golden index d23be4359..f084a62f4 100644 --- a/tests/golden/python/python-httpx/type-aliases-discriminated-union-with-refs/discriminated_union_with_references_test/apis/default_api.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-discriminated-union-with-refs/discriminated_union_with_references_test/apis/default_api.py.golden @@ -17,7 +17,9 @@ class DefaultApi: def create_container(self, *, body: ContainerImage) -> ContainerImage: """Create container with discriminated union.""" path = "/container" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-httpx/type-aliases-discriminated-union-with-refs/discriminated_union_with_references_test/runtime/client.py.golden b/tests/golden/python/python-httpx/type-aliases-discriminated-union-with-refs/discriminated_union_with_references_test/runtime/client.py.golden index f477305f9..9b75bafdb 100644 --- a/tests/golden/python/python-httpx/type-aliases-discriminated-union-with-refs/discriminated_union_with_references_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-discriminated-union-with-refs/discriminated_union_with_references_test/runtime/client.py.golden @@ -36,6 +36,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -52,6 +55,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/type-aliases-intersection-allof/intersection_type_all_of_test/apis/default_api.py.golden b/tests/golden/python/python-httpx/type-aliases-intersection-allof/intersection_type_all_of_test/apis/default_api.py.golden index 3655e273d..b6736af08 100644 --- a/tests/golden/python/python-httpx/type-aliases-intersection-allof/intersection_type_all_of_test/apis/default_api.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-intersection-allof/intersection_type_all_of_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def test_intersection(self, *, body: Foo) -> Foo: """Test endpoint with intersection type.""" path = "/test" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return Foo.from_dict(response.json()) diff --git a/tests/golden/python/python-httpx/type-aliases-intersection-allof/intersection_type_all_of_test/runtime/client.py.golden b/tests/golden/python/python-httpx/type-aliases-intersection-allof/intersection_type_all_of_test/runtime/client.py.golden index 71ca98ceb..0b02461fa 100644 --- a/tests/golden/python/python-httpx/type-aliases-intersection-allof/intersection_type_all_of_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-intersection-allof/intersection_type_all_of_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/type-aliases-intersection-with-nullable-reference/intersection_type_with_nullable_reference_test/apis/default_api.py.golden b/tests/golden/python/python-httpx/type-aliases-intersection-with-nullable-reference/intersection_type_with_nullable_reference_test/apis/default_api.py.golden index c21426cce..178159561 100644 --- a/tests/golden/python/python-httpx/type-aliases-intersection-with-nullable-reference/intersection_type_with_nullable_reference_test/apis/default_api.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-intersection-with-nullable-reference/intersection_type_with_nullable_reference_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def create_test(self, *, body: Baz) -> Baz: """Create test with intersection type.""" path = "/test" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return Baz.from_dict(response.json()) diff --git a/tests/golden/python/python-httpx/type-aliases-intersection-with-nullable-reference/intersection_type_with_nullable_reference_test/runtime/client.py.golden b/tests/golden/python/python-httpx/type-aliases-intersection-with-nullable-reference/intersection_type_with_nullable_reference_test/runtime/client.py.golden index 16ec1e247..33a4fa77e 100644 --- a/tests/golden/python/python-httpx/type-aliases-intersection-with-nullable-reference/intersection_type_with_nullable_reference_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-intersection-with-nullable-reference/intersection_type_with_nullable_reference_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/type-aliases-nested-union/nested_union_type_test/apis/default_api.py.golden b/tests/golden/python/python-httpx/type-aliases-nested-union/nested_union_type_test/apis/default_api.py.golden index 63ea00952..f78ce3be5 100644 --- a/tests/golden/python/python-httpx/type-aliases-nested-union/nested_union_type_test/apis/default_api.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-nested-union/nested_union_type_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def test_nested_union(self, *, body: Foo) -> Foo: """Test endpoint with nested union type.""" path = "/test" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-httpx/type-aliases-nested-union/nested_union_type_test/runtime/client.py.golden b/tests/golden/python/python-httpx/type-aliases-nested-union/nested_union_type_test/runtime/client.py.golden index 5c046aff5..6287b8e4d 100644 --- a/tests/golden/python/python-httpx/type-aliases-nested-union/nested_union_type_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-nested-union/nested_union_type_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/type-aliases-simple-type-alias/simple_type_alias_test/apis/default_api.py.golden b/tests/golden/python/python-httpx/type-aliases-simple-type-alias/simple_type_alias_test/apis/default_api.py.golden index 78c6b7a3e..1af8e8358 100644 --- a/tests/golden/python/python-httpx/type-aliases-simple-type-alias/simple_type_alias_test/apis/default_api.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-simple-type-alias/simple_type_alias_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def test_simple_alias(self, *, body: Foo) -> Foo: """Test endpoint with simple type alias.""" path = "/test" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-httpx/type-aliases-simple-type-alias/simple_type_alias_test/runtime/client.py.golden b/tests/golden/python/python-httpx/type-aliases-simple-type-alias/simple_type_alias_test/runtime/client.py.golden index 437a4029e..f2d3f28e1 100644 --- a/tests/golden/python/python-httpx/type-aliases-simple-type-alias/simple_type_alias_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-simple-type-alias/simple_type_alias_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/type-aliases-union-mixed/union_type_with_mixed_members_test/apis/default_api.py.golden b/tests/golden/python/python-httpx/type-aliases-union-mixed/union_type_with_mixed_members_test/apis/default_api.py.golden index 681a0f0bc..61191d695 100644 --- a/tests/golden/python/python-httpx/type-aliases-union-mixed/union_type_with_mixed_members_test/apis/default_api.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-union-mixed/union_type_with_mixed_members_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def test_mixed_union(self, *, body: Foo) -> Foo: """Test endpoint with mixed union type.""" path = "/test" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-httpx/type-aliases-union-mixed/union_type_with_mixed_members_test/runtime/client.py.golden b/tests/golden/python/python-httpx/type-aliases-union-mixed/union_type_with_mixed_members_test/runtime/client.py.golden index c8e321d7b..95e5dede2 100644 --- a/tests/golden/python/python-httpx/type-aliases-union-mixed/union_type_with_mixed_members_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-union-mixed/union_type_with_mixed_members_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/type-aliases-union-with-any/union_type_with_any_test/apis/default_api.py.golden b/tests/golden/python/python-httpx/type-aliases-union-with-any/union_type_with_any_test/apis/default_api.py.golden index d167f60dd..e07b4de9c 100644 --- a/tests/golden/python/python-httpx/type-aliases-union-with-any/union_type_with_any_test/apis/default_api.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-union-with-any/union_type_with_any_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def test_union_with_any(self, *, body: Config) -> Config: """Test endpoint with union type including any.""" path = "/test" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-httpx/type-aliases-union-with-any/union_type_with_any_test/runtime/client.py.golden b/tests/golden/python/python-httpx/type-aliases-union-with-any/union_type_with_any_test/runtime/client.py.golden index c8db5646f..d7d379f17 100644 --- a/tests/golden/python/python-httpx/type-aliases-union-with-any/union_type_with_any_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-union-with-any/union_type_with_any_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/type-aliases-union-with-inline-objects/union_type_with_inline_objects_test/apis/default_api.py.golden b/tests/golden/python/python-httpx/type-aliases-union-with-inline-objects/union_type_with_inline_objects_test/apis/default_api.py.golden index 4c800e003..1b4d6d7a3 100644 --- a/tests/golden/python/python-httpx/type-aliases-union-with-inline-objects/union_type_with_inline_objects_test/apis/default_api.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-union-with-inline-objects/union_type_with_inline_objects_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def test_union_with_inline_objects(self, *, body: Foo) -> Foo: """Test endpoint with union type including inline objects.""" path = "/test" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-httpx/type-aliases-union-with-inline-objects/union_type_with_inline_objects_test/runtime/client.py.golden b/tests/golden/python/python-httpx/type-aliases-union-with-inline-objects/union_type_with_inline_objects_test/runtime/client.py.golden index 62b4cb9ec..d03e54ec4 100644 --- a/tests/golden/python/python-httpx/type-aliases-union-with-inline-objects/union_type_with_inline_objects_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-union-with-inline-objects/union_type_with_inline_objects_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/type-aliases-union-with-interfaces/union_type_with_interfaces_test/apis/default_api.py.golden b/tests/golden/python/python-httpx/type-aliases-union-with-interfaces/union_type_with_interfaces_test/apis/default_api.py.golden index df9538271..e1eff6f47 100644 --- a/tests/golden/python/python-httpx/type-aliases-union-with-interfaces/union_type_with_interfaces_test/apis/default_api.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-union-with-interfaces/union_type_with_interfaces_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def test_union(self, *, body: Foo) -> Foo: """Test endpoint with union type.""" path = "/test" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-httpx/type-aliases-union-with-interfaces/union_type_with_interfaces_test/runtime/client.py.golden b/tests/golden/python/python-httpx/type-aliases-union-with-interfaces/union_type_with_interfaces_test/runtime/client.py.golden index f6bf3276d..9e6cf71ce 100644 --- a/tests/golden/python/python-httpx/type-aliases-union-with-interfaces/union_type_with_interfaces_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-union-with-interfaces/union_type_with_interfaces_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-httpx/type-aliases-union-with-primitives/union_type_with_primitives_test/apis/default_api.py.golden b/tests/golden/python/python-httpx/type-aliases-union-with-primitives/union_type_with_primitives_test/apis/default_api.py.golden index 3613eb7ac..c7eb501cd 100644 --- a/tests/golden/python/python-httpx/type-aliases-union-with-primitives/union_type_with_primitives_test/apis/default_api.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-union-with-primitives/union_type_with_primitives_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def test_union_primitives(self, *, body: Foo) -> Foo: """Test endpoint with union type.""" path = "/test" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason_phrase, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-httpx/type-aliases-union-with-primitives/union_type_with_primitives_test/runtime/client.py.golden b/tests/golden/python/python-httpx/type-aliases-union-with-primitives/union_type_with_primitives_test/runtime/client.py.golden index 8d0933000..f21c36a3d 100644 --- a/tests/golden/python/python-httpx/type-aliases-union-with-primitives/union_type_with_primitives_test/runtime/client.py.golden +++ b/tests/golden/python/python-httpx/type-aliases-union-with-primitives/union_type_with_primitives_test/runtime/client.py.golden @@ -35,6 +35,9 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + content: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Send an HTTP request and return the raw response.""" @@ -51,6 +54,9 @@ class Client: url, params=params, json=json, + content=content, + data=data, + files=files, headers=req_headers, ) diff --git a/tests/golden/python/python-requests/additional-properties/additional_properties_api/apis/additional_properties_api.py.golden b/tests/golden/python/python-requests/additional-properties/additional_properties_api/apis/additional_properties_api.py.golden index 159b70086..dbd3a601f 100644 --- a/tests/golden/python/python-requests/additional-properties/additional_properties_api/apis/additional_properties_api.py.golden +++ b/tests/golden/python/python-requests/additional-properties/additional_properties_api/apis/additional_properties_api.py.golden @@ -18,7 +18,9 @@ class AdditionalPropertiesApi: def post_leaf(self, *, body: LeafValue) -> LeafValue: """Echo leaf payload (attributes map)..""" path = "/leaf" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return LeafValue.from_dict(response.json()) @@ -26,7 +28,9 @@ class AdditionalPropertiesApi: def post_middle(self, *, body: MiddleLevel) -> MiddleLevel: """Echo middle-level payload (nested + extra maps)..""" path = "/middle" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return MiddleLevel.from_dict(response.json()) @@ -34,7 +38,9 @@ class AdditionalPropertiesApi: def post_root(self, *, body: RootLevel) -> RootLevel: """Echo root payload (all three levels with additional-properties-style maps)..""" path = "/root" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return RootLevel.from_dict(response.json()) diff --git a/tests/golden/python/python-requests/additional-properties/additional_properties_api/runtime/client.py.golden b/tests/golden/python/python-requests/additional-properties/additional_properties_api/runtime/client.py.golden index 954d25339..7c96d3b5a 100644 --- a/tests/golden/python/python-requests/additional-properties/additional_properties_api/runtime/client.py.golden +++ b/tests/golden/python/python-requests/additional-properties/additional_properties_api/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/binary-transfer-media-types/binary_transfer_media_types/apis/transfer_api.py.golden b/tests/golden/python/python-requests/binary-transfer-media-types/binary_transfer_media_types/apis/transfer_api.py.golden index 8b7ce77f0..3a5e9b8c2 100644 --- a/tests/golden/python/python-requests/binary-transfer-media-types/binary_transfer_media_types/apis/transfer_api.py.golden +++ b/tests/golden/python/python-requests/binary-transfer-media-types/binary_transfer_media_types/apis/transfer_api.py.golden @@ -5,6 +5,8 @@ from __future__ import annotations +import json + from ..models.upload_asset_request import UploadAssetRequest from ..models.upload_metadata import UploadMetadata from ..runtime.client import Client @@ -14,16 +16,20 @@ class TransferApi: def __init__(self, client: Client) -> None: self._client = client - def download_asset(self, asset_id: str) -> list[int]: + def download_asset(self, asset_id: str) -> bytes: path = f"/assets/{asset_id}/content" response = self._client.request("GET", path) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) - return response.json() # type: ignore[return-value] + return response.content def upload_asset(self, *, body: UploadAssetRequest) -> UploadMetadata: path = "/uploads" - response = self._client.request("POST", path, json=body.to_dict()) + files: dict[str, object] = {} + files["file"] = ("file", body.file, "application/octet-stream") + files["metadata"] = (None, json.dumps(body.metadata.to_dict()), "application/json") + files["purpose"] = (None, str(body.purpose)) + response = self._client.request("POST", path, files=files if files else None) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return UploadMetadata.from_dict(response.json()) diff --git a/tests/golden/python/python-requests/binary-transfer-media-types/binary_transfer_media_types/runtime/client.py.golden b/tests/golden/python/python-requests/binary-transfer-media-types/binary_transfer_media_types/runtime/client.py.golden index 888baf6e9..566974f60 100644 --- a/tests/golden/python/python-requests/binary-transfer-media-types/binary_transfer_media_types/runtime/client.py.golden +++ b/tests/golden/python/python-requests/binary-transfer-media-types/binary_transfer_media_types/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/comprehensive-schemas/comprehensive_schema_types_example/runtime/client.py.golden b/tests/golden/python/python-requests/comprehensive-schemas/comprehensive_schema_types_example/runtime/client.py.golden index cb89f7f3c..9655eb736 100644 --- a/tests/golden/python/python-requests/comprehensive-schemas/comprehensive_schema_types_example/runtime/client.py.golden +++ b/tests/golden/python/python-requests/comprehensive-schemas/comprehensive_schema_types_example/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/delete-with-response-schema/test_api_with_delete_response_schema/apis/test_resource_api.py.golden b/tests/golden/python/python-requests/delete-with-response-schema/test_api_with_delete_response_schema/apis/test_resource_api.py.golden index e5227e1c2..da68032bc 100644 --- a/tests/golden/python/python-requests/delete-with-response-schema/test_api_with_delete_response_schema/apis/test_resource_api.py.golden +++ b/tests/golden/python/python-requests/delete-with-response-schema/test_api_with_delete_response_schema/apis/test_resource_api.py.golden @@ -17,7 +17,9 @@ class TestResourceApi: def create_test_resource(self, *, body: CreateTestResourceRequest) -> CreateTestResourceResponse: path = "/v1/api/test-resource" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return CreateTestResourceResponse.from_dict(response.json()) diff --git a/tests/golden/python/python-requests/delete-with-response-schema/test_api_with_delete_response_schema/runtime/client.py.golden b/tests/golden/python/python-requests/delete-with-response-schema/test_api_with_delete_response_schema/runtime/client.py.golden index 0499cb820..368cde80f 100644 --- a/tests/golden/python/python-requests/delete-with-response-schema/test_api_with_delete_response_schema/runtime/client.py.golden +++ b/tests/golden/python/python-requests/delete-with-response-schema/test_api_with_delete_response_schema/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/duplicate-param-names/duplicate_parameter_names_test_api/apis/items_api.py.golden b/tests/golden/python/python-requests/duplicate-param-names/duplicate_parameter_names_test_api/apis/items_api.py.golden index 3740196ae..931d5e31a 100644 --- a/tests/golden/python/python-requests/duplicate-param-names/duplicate_parameter_names_test_api/apis/items_api.py.golden +++ b/tests/golden/python/python-requests/duplicate-param-names/duplicate_parameter_names_test_api/apis/items_api.py.golden @@ -21,7 +21,9 @@ class ItemsApi: params: dict[str, str] = {} if body is not None: params["body"] = str(body) - response = self._client.request("POST", path, params=params, json=body2.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, params=params, json=body2.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return CreateItemWithBodyConflictResponse200.from_dict(response.json()) diff --git a/tests/golden/python/python-requests/duplicate-param-names/duplicate_parameter_names_test_api/runtime/client.py.golden b/tests/golden/python/python-requests/duplicate-param-names/duplicate_parameter_names_test_api/runtime/client.py.golden index 998074693..aa1f65d4d 100644 --- a/tests/golden/python/python-requests/duplicate-param-names/duplicate_parameter_names_test_api/runtime/client.py.golden +++ b/tests/golden/python/python-requests/duplicate-param-names/duplicate_parameter_names_test_api/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/enum-repr/enum_representation_api/apis/enum_repr_api.py.golden b/tests/golden/python/python-requests/enum-repr/enum_representation_api/apis/enum_repr_api.py.golden index 39c6130aa..6bc0d1e9f 100644 --- a/tests/golden/python/python-requests/enum-repr/enum_representation_api/apis/enum_repr_api.py.golden +++ b/tests/golden/python/python-requests/enum-repr/enum_representation_api/apis/enum_repr_api.py.golden @@ -20,7 +20,9 @@ class EnumReprApi: def handle_adjacently_tagged(self, *, body: AdjacentlyTaggedEnum) -> AdjacentlyTaggedEnum: """Handle adjacently tagged enum.""" path = "/adjacently-tagged" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] @@ -28,7 +30,9 @@ class EnumReprApi: def handle_externally_tagged(self, *, body: ExternallyTaggedEnum) -> ExternallyTaggedEnum: """Handle externally tagged enum.""" path = "/externally-tagged" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] @@ -36,7 +40,9 @@ class EnumReprApi: def handle_internally_tagged(self, *, body: InternallyTaggedEnum) -> InternallyTaggedEnum: """Handle internally tagged enum.""" path = "/internally-tagged" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] @@ -44,7 +50,9 @@ class EnumReprApi: def handle_mixed(self, *, body: MixedEnum) -> MixedEnum: """Handle mixed enum (unit variants SimpleA, SimpleB and tuple variants VariantA(VariantA), VariantB(VariantB)).""" path = "/mixed" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] @@ -52,7 +60,9 @@ class EnumReprApi: def handle_untagged(self, *, body: UntaggedEnum) -> UntaggedEnum: """Handle untagged enum.""" path = "/untagged" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-requests/enum-repr/enum_representation_api/runtime/client.py.golden b/tests/golden/python/python-requests/enum-repr/enum_representation_api/runtime/client.py.golden index c864eff06..8ed19f101 100644 --- a/tests/golden/python/python-requests/enum-repr/enum_representation_api/runtime/client.py.golden +++ b/tests/golden/python/python-requests/enum-repr/enum_representation_api/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/interface-with-enum-reference/interface_with_enum_reference_test/runtime/client.py.golden b/tests/golden/python/python-requests/interface-with-enum-reference/interface_with_enum_reference_test/runtime/client.py.golden index 59036f0f9..c0c2a51ca 100644 --- a/tests/golden/python/python-requests/interface-with-enum-reference/interface_with_enum_reference_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/interface-with-enum-reference/interface_with_enum_reference_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/media-type-selection/README.md.golden b/tests/golden/python/python-requests/media-type-selection/README.md.golden new file mode 100644 index 000000000..c22dde875 --- /dev/null +++ b/tests/golden/python/python-requests/media-type-selection/README.md.golden @@ -0,0 +1,7 @@ +# Media Type Selection + +Covers normalized media-type selection for requests and responses. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `media_type_selection`. diff --git a/tests/golden/python/python-requests/media-type-selection/media_type_selection/__init__.py.golden b/tests/golden/python/python-requests/media-type-selection/media_type_selection/__init__.py.golden new file mode 100644 index 000000000..35a708cb1 --- /dev/null +++ b/tests/golden/python/python-requests/media-type-selection/media_type_selection/__init__.py.golden @@ -0,0 +1,10 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +from .runtime import ApiKeyAuth as ApiKeyAuth +from .runtime import Authenticator as Authenticator +from .runtime import BearerAuth as BearerAuth +from .runtime import Client as Client +from .runtime import ApiError as ApiError diff --git a/tests/golden/python/python-requests/media-type-selection/media_type_selection/apis/__init__.py.golden b/tests/golden/python/python-requests/media-type-selection/media_type_selection/apis/__init__.py.golden new file mode 100644 index 000000000..bc4112fad --- /dev/null +++ b/tests/golden/python/python-requests/media-type-selection/media_type_selection/apis/__init__.py.golden @@ -0,0 +1,6 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +from .media_api import MediaApi as MediaApi diff --git a/tests/golden/python/python-requests/media-type-selection/media_type_selection/apis/media_api.py.golden b/tests/golden/python/python-requests/media-type-selection/media_type_selection/apis/media_api.py.golden new file mode 100644 index 000000000..00e36d6a9 --- /dev/null +++ b/tests/golden/python/python-requests/media-type-selection/media_type_selection/apis/media_api.py.golden @@ -0,0 +1,66 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +from __future__ import annotations + +from ..models.file_envelope import FileEnvelope +from ..models.payload import Payload +from ..runtime.client import Client +from ..runtime.errors import ApiError + +class MediaApi: + def __init__(self, client: Client) -> None: + self._client = client + + def send_json_preferred(self, *, body: Payload) -> None: + path = "/request/json-vs-multipart" + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason, response.content) + return None + + def send_parameterized_multipart(self, *, body: FileEnvelope) -> None: + path = "/request/parameterized-multipart" + files: dict[str, object] = {} + files["file"] = ("file", body.file, "application/octet-stream") + if body.note is not None: + files["note"] = (None, str(body.note)) + + response = self._client.request("POST", path, files=files if files else None) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason, response.content) + return None + + def send_vendor_json(self, *, body: Payload) -> None: + path = "/request/vendor-json" + headers: dict[str, str] = {} + headers["Content-Type"] = "application/vnd.example+json; charset=utf-8" + response = self._client.request("PATCH", path, json=body.to_dict(), headers=headers) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason, response.content) + return None + + def get_octet_preferred(self) -> bytes: + path = "/response/octet-before-xml" + response = self._client.request("GET", path) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason, response.content) + return response.content + + def get_text_preferred(self) -> str: + path = "/response/text-before-xml" + response = self._client.request("GET", path) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason, response.content) + return response.text + + def get_vendor_json(self) -> Payload: + path = "/response/vendor-json" + response = self._client.request("GET", path) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason, response.content) + return Payload.from_dict(response.json()) diff --git a/tests/golden/python/python-requests/media-type-selection/media_type_selection/models/__init__.py.golden b/tests/golden/python/python-requests/media-type-selection/media_type_selection/models/__init__.py.golden new file mode 100644 index 000000000..b37a095eb --- /dev/null +++ b/tests/golden/python/python-requests/media-type-selection/media_type_selection/models/__init__.py.golden @@ -0,0 +1,7 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +from .file_envelope import FileEnvelope as FileEnvelope +from .payload import Payload as Payload diff --git a/tests/golden/python/python-requests/media-type-selection/media_type_selection/models/file_envelope.py.golden b/tests/golden/python/python-requests/media-type-selection/media_type_selection/models/file_envelope.py.golden new file mode 100644 index 000000000..76e861905 --- /dev/null +++ b/tests/golden/python/python-requests/media-type-selection/media_type_selection/models/file_envelope.py.golden @@ -0,0 +1,27 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +from __future__ import annotations + +from dataclasses import dataclass + +@dataclass +class FileEnvelope: + file: bytes + note: str | None = None + + def to_dict(self) -> dict[str, object]: + result: dict[str, object] = {} + result["file"] = self.file + if self.note is not None: + result["note"] = self.note + return result + + @classmethod + def from_dict(cls, data: dict[str, object]) -> FileEnvelope: + return cls( + file=data["file"], # type: ignore[assignment] + note=data.get("note"), # type: ignore[assignment] + ) diff --git a/tests/golden/python/python-requests/media-type-selection/media_type_selection/models/payload.py.golden b/tests/golden/python/python-requests/media-type-selection/media_type_selection/models/payload.py.golden new file mode 100644 index 000000000..801723e3d --- /dev/null +++ b/tests/golden/python/python-requests/media-type-selection/media_type_selection/models/payload.py.golden @@ -0,0 +1,27 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +from __future__ import annotations + +from dataclasses import dataclass + +@dataclass +class Payload: + id: str + count: int | None = None + + def to_dict(self) -> dict[str, object]: + result: dict[str, object] = {} + result["id"] = self.id + if self.count is not None: + result["count"] = self.count + return result + + @classmethod + def from_dict(cls, data: dict[str, object]) -> Payload: + return cls( + id=data["id"], # type: ignore[assignment] + count=data.get("count"), # type: ignore[assignment] + ) diff --git a/tests/golden/python/python-requests/media-type-selection/media_type_selection/py.typed.golden b/tests/golden/python/python-requests/media-type-selection/media_type_selection/py.typed.golden new file mode 100644 index 000000000..e69de29bb diff --git a/tests/golden/python/python-requests/media-type-selection/media_type_selection/runtime/__init__.py.golden b/tests/golden/python/python-requests/media-type-selection/media_type_selection/runtime/__init__.py.golden new file mode 100644 index 000000000..e4f6e6349 --- /dev/null +++ b/tests/golden/python/python-requests/media-type-selection/media_type_selection/runtime/__init__.py.golden @@ -0,0 +1,10 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +from .auth import ApiKeyAuth as ApiKeyAuth +from .auth import Authenticator as Authenticator +from .auth import BearerAuth as BearerAuth +from .client import Client as Client +from .errors import ApiError as ApiError diff --git a/tests/golden/python/python-requests/media-type-selection/media_type_selection/runtime/auth.py.golden b/tests/golden/python/python-requests/media-type-selection/media_type_selection/runtime/auth.py.golden new file mode 100644 index 000000000..6572db864 --- /dev/null +++ b/tests/golden/python/python-requests/media-type-selection/media_type_selection/runtime/auth.py.golden @@ -0,0 +1,51 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +"""Authentication helpers.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from collections.abc import Callable + + +class Authenticator(ABC): + """Base class for request authenticators.""" + + @abstractmethod + def auth_headers(self) -> dict[str, str]: + """Return headers to attach to every request.""" + ... + + +class BearerAuth(Authenticator): + """Bearer token authentication. + + Accepts a static token string or a callable that returns the + current token (evaluated on every request). + """ + + def __init__(self, token: str | Callable[[], str]) -> None: + self._token = token + + def auth_headers(self) -> dict[str, str]: + token = self._token() if callable(self._token) else self._token + return {"Authorization": f"Bearer {token}"} + + +class ApiKeyAuth(Authenticator): + """API key authentication via a custom header. + + Accepts a static key string or a callable that returns the + current key (evaluated on every request). + """ + + def __init__(self, header_name: str, api_key: str | Callable[[], str]) -> None: + self._header_name = header_name + self._api_key = api_key + + def auth_headers(self) -> dict[str, str]: + key = self._api_key() if callable(self._api_key) else self._api_key + return {self._header_name: key} diff --git a/tests/golden/python/python-requests/media-type-selection/media_type_selection/runtime/client.py.golden b/tests/golden/python/python-requests/media-type-selection/media_type_selection/runtime/client.py.golden new file mode 100644 index 000000000..9d501dbbc --- /dev/null +++ b/tests/golden/python/python-requests/media-type-selection/media_type_selection/runtime/client.py.golden @@ -0,0 +1,99 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +"""HTTP client wrapping requests.""" + +from __future__ import annotations + +from typing import Any, cast + +import requests + +from .auth import Authenticator + + +class Response: + """Thin wrapper over requests.Response with strict types.""" + + def __init__(self, raw: requests.Response) -> None: + self._raw = raw + + @property + def status_code(self) -> int: + return self._raw.status_code # type: ignore[return-value] + + @property + def reason(self) -> str: + return self._raw.reason or "" + + @property + def content(self) -> bytes: + return self._raw.content # type: ignore[return-value] + + @property + def text(self) -> str: + return self._raw.text + + def json(self) -> Any: # type: ignore[explicit-override] + return self._raw.json() # type: ignore[reportUnknownMemberType] + + +class Client: + """Synchronous HTTP client for the generated SDK.""" + + def __init__( + self, + base_url: str, + *, + http_client: requests.Session | None = None, + authenticator: Authenticator | None = None, + ) -> None: + self._base_url = base_url.rstrip("/") + self._http_client = http_client or requests.Session() + self._authenticator = authenticator + + def request( + self, + method: str, + path: str, + *, + params: dict[str, str] | None = None, + json: Any = None, + data: Any = None, + files: Any = None, + headers: dict[str, str] | None = None, + ) -> Response: + """Send an HTTP request and return the raw response.""" + url = f"{self._base_url}{path}" + req_headers: dict[str, str] = {"Accept": "application/json"} + if json is not None: + req_headers["Content-Type"] = "application/json" + if headers: + req_headers.update(headers) + if self._authenticator is not None: + req_headers.update(self._authenticator.auth_headers()) + raw = cast( + requests.Response, + self._http_client.request( # type: ignore[reportUnknownMemberType] + method, + url, + params=params, + json=json, + data=data, + files=files, + headers=req_headers, + ), + ) + return Response(raw) + + def close(self) -> None: + """Close the underlying HTTP client.""" + self._http_client.close() + + def __enter__(self) -> Client: + return self + + def __exit__(self, *args: object) -> None: + self.close() diff --git a/tests/golden/python/python-requests/media-type-selection/media_type_selection/runtime/errors.py.golden b/tests/golden/python/python-requests/media-type-selection/media_type_selection/runtime/errors.py.golden new file mode 100644 index 000000000..6253434ae --- /dev/null +++ b/tests/golden/python/python-requests/media-type-selection/media_type_selection/runtime/errors.py.golden @@ -0,0 +1,18 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Media Type Selection — 1.0.0 +# Covers normalized media-type selection for requests and responses. + +"""API error type.""" + +from __future__ import annotations + + +class ApiError(Exception): + """Raised when the server returns a 4xx or 5xx status code.""" + + def __init__(self, status_code: int, status: str, body: bytes) -> None: + self.status_code = status_code + self.status = status + self.body = body + super().__init__(f"{status_code} {status}") diff --git a/tests/golden/python/python-requests/media-type-selection/pyproject.toml.golden b/tests/golden/python/python-requests/media-type-selection/pyproject.toml.golden new file mode 100644 index 000000000..f6e12a806 --- /dev/null +++ b/tests/golden/python/python-requests/media-type-selection/pyproject.toml.golden @@ -0,0 +1,10 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "media_type_selection" +version = "1.0.0" +description = "Covers normalized media-type selection for requests and responses." +requires-python = ">=3.12" +dependencies = ["requests>=2.32"] diff --git a/tests/golden/python/python-requests/minimal/minimal_api/runtime/client.py.golden b/tests/golden/python/python-requests/minimal/minimal_api/runtime/client.py.golden index 6edd20f31..f512d67ad 100644 --- a/tests/golden/python/python-requests/minimal/minimal_api/runtime/client.py.golden +++ b/tests/golden/python/python-requests/minimal/minimal_api/runtime/client.py.golden @@ -60,6 +60,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -78,6 +80,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/multipart-edge-cases/README.md.golden b/tests/golden/python/python-requests/multipart-edge-cases/README.md.golden new file mode 100644 index 000000000..17e37372c --- /dev/null +++ b/tests/golden/python/python-requests/multipart-edge-cases/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Edge Cases + +Covers optional multipart bodies, optional parts, and text-only multipart fields. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart_edge_cases`. diff --git a/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/__init__.py.golden b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/__init__.py.golden new file mode 100644 index 000000000..7fb6a7a96 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/__init__.py.golden @@ -0,0 +1,10 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +from .runtime import ApiKeyAuth as ApiKeyAuth +from .runtime import Authenticator as Authenticator +from .runtime import BearerAuth as BearerAuth +from .runtime import Client as Client +from .runtime import ApiError as ApiError diff --git a/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/apis/__init__.py.golden b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/apis/__init__.py.golden new file mode 100644 index 000000000..1d4c5fadd --- /dev/null +++ b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/apis/__init__.py.golden @@ -0,0 +1,6 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +from .multipart_api import MultipartApi as MultipartApi diff --git a/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/apis/multipart_api.py.golden b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/apis/multipart_api.py.golden new file mode 100644 index 000000000..d5250f440 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/apis/multipart_api.py.golden @@ -0,0 +1,53 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +from __future__ import annotations + +import json + +from ..models.optional_upload import OptionalUpload +from ..models.text_fields import TextFields +from ..runtime.client import Client +from ..runtime.errors import ApiError + +class MultipartApi: + def __init__(self, client: Client) -> None: + self._client = client + + def send_optional_parts(self, *, body: OptionalUpload | None = None) -> None: + path = "/multipart/optional" + files: dict[str, object] = {} + if body is not None: + if body.attributes is not None: + files["attributes"] = (None, json.dumps(body.attributes.to_dict()), "application/json") + + if body.enabled is not None: + files["enabled"] = (None, str(body.enabled)) + + if body.file is not None: + files["file"] = ("file", body.file, "application/octet-stream") + + if body.retry_count is not None: + files["retry_count"] = (None, str(body.retry_count)) + + if body.title is not None: + files["title"] = (None, str(body.title)) + + + response = self._client.request("POST", path, files=files if files else None) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason, response.content) + return None + + def send_text_fields(self, *, body: TextFields) -> None: + path = "/multipart/text-only" + files: dict[str, object] = {} + files["enabled"] = (None, str(body.enabled)) + files["note"] = (None, str(body.note)) + files["retry_count"] = (None, str(body.retry_count)) + response = self._client.request("POST", path, files=files if files else None) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason, response.content) + return None diff --git a/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/models/__init__.py.golden b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/models/__init__.py.golden new file mode 100644 index 000000000..71bfeea0a --- /dev/null +++ b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/models/__init__.py.golden @@ -0,0 +1,8 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +from .attributes import Attributes as Attributes +from .optional_upload import OptionalUpload as OptionalUpload +from .text_fields import TextFields as TextFields diff --git a/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/models/attributes.py.golden b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/models/attributes.py.golden new file mode 100644 index 000000000..1d90f0b99 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/models/attributes.py.golden @@ -0,0 +1,27 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +from __future__ import annotations + +from dataclasses import dataclass + +@dataclass +class Attributes: + label: str + priority: int | None = None + + def to_dict(self) -> dict[str, object]: + result: dict[str, object] = {} + result["label"] = self.label + if self.priority is not None: + result["priority"] = self.priority + return result + + @classmethod + def from_dict(cls, data: dict[str, object]) -> Attributes: + return cls( + label=data["label"], # type: ignore[assignment] + priority=data.get("priority"), # type: ignore[assignment] + ) diff --git a/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/models/optional_upload.py.golden b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/models/optional_upload.py.golden new file mode 100644 index 000000000..7a01c050d --- /dev/null +++ b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/models/optional_upload.py.golden @@ -0,0 +1,42 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +from __future__ import annotations + +from dataclasses import dataclass + +from .attributes import Attributes + +@dataclass +class OptionalUpload: + attributes: Attributes | None = None + enabled: bool | None = None + file: bytes | None = None + retry_count: int | None = None + title: str | None = None + + def to_dict(self) -> dict[str, object]: + result: dict[str, object] = {} + if self.attributes is not None: + result["attributes"] = self.attributes.to_dict() + if self.enabled is not None: + result["enabled"] = self.enabled + if self.file is not None: + result["file"] = self.file + if self.retry_count is not None: + result["retry_count"] = self.retry_count + if self.title is not None: + result["title"] = self.title + return result + + @classmethod + def from_dict(cls, data: dict[str, object]) -> OptionalUpload: + return cls( + attributes=Attributes.from_dict(data.get("attributes")) if data.get("attributes") is not None else None, # type: ignore[arg-type] + enabled=data.get("enabled"), # type: ignore[assignment] + file=data.get("file"), # type: ignore[assignment] + retry_count=data.get("retry_count"), # type: ignore[assignment] + title=data.get("title"), # type: ignore[assignment] + ) diff --git a/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/models/text_fields.py.golden b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/models/text_fields.py.golden new file mode 100644 index 000000000..ca91a6664 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/models/text_fields.py.golden @@ -0,0 +1,29 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +from __future__ import annotations + +from dataclasses import dataclass + +@dataclass +class TextFields: + enabled: bool + note: str + retry_count: int + + def to_dict(self) -> dict[str, object]: + result: dict[str, object] = {} + result["enabled"] = self.enabled + result["note"] = self.note + result["retry_count"] = self.retry_count + return result + + @classmethod + def from_dict(cls, data: dict[str, object]) -> TextFields: + return cls( + enabled=data["enabled"], # type: ignore[assignment] + note=data["note"], # type: ignore[assignment] + retry_count=data["retry_count"], # type: ignore[assignment] + ) diff --git a/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/py.typed.golden b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/py.typed.golden new file mode 100644 index 000000000..e69de29bb diff --git a/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/runtime/__init__.py.golden b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/runtime/__init__.py.golden new file mode 100644 index 000000000..7226802fc --- /dev/null +++ b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/runtime/__init__.py.golden @@ -0,0 +1,10 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +from .auth import ApiKeyAuth as ApiKeyAuth +from .auth import Authenticator as Authenticator +from .auth import BearerAuth as BearerAuth +from .client import Client as Client +from .errors import ApiError as ApiError diff --git a/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/runtime/auth.py.golden b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/runtime/auth.py.golden new file mode 100644 index 000000000..532cf71e7 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/runtime/auth.py.golden @@ -0,0 +1,51 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +"""Authentication helpers.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from collections.abc import Callable + + +class Authenticator(ABC): + """Base class for request authenticators.""" + + @abstractmethod + def auth_headers(self) -> dict[str, str]: + """Return headers to attach to every request.""" + ... + + +class BearerAuth(Authenticator): + """Bearer token authentication. + + Accepts a static token string or a callable that returns the + current token (evaluated on every request). + """ + + def __init__(self, token: str | Callable[[], str]) -> None: + self._token = token + + def auth_headers(self) -> dict[str, str]: + token = self._token() if callable(self._token) else self._token + return {"Authorization": f"Bearer {token}"} + + +class ApiKeyAuth(Authenticator): + """API key authentication via a custom header. + + Accepts a static key string or a callable that returns the + current key (evaluated on every request). + """ + + def __init__(self, header_name: str, api_key: str | Callable[[], str]) -> None: + self._header_name = header_name + self._api_key = api_key + + def auth_headers(self) -> dict[str, str]: + key = self._api_key() if callable(self._api_key) else self._api_key + return {self._header_name: key} diff --git a/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/runtime/client.py.golden b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/runtime/client.py.golden new file mode 100644 index 000000000..58931e8bd --- /dev/null +++ b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/runtime/client.py.golden @@ -0,0 +1,99 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +"""HTTP client wrapping requests.""" + +from __future__ import annotations + +from typing import Any, cast + +import requests + +from .auth import Authenticator + + +class Response: + """Thin wrapper over requests.Response with strict types.""" + + def __init__(self, raw: requests.Response) -> None: + self._raw = raw + + @property + def status_code(self) -> int: + return self._raw.status_code # type: ignore[return-value] + + @property + def reason(self) -> str: + return self._raw.reason or "" + + @property + def content(self) -> bytes: + return self._raw.content # type: ignore[return-value] + + @property + def text(self) -> str: + return self._raw.text + + def json(self) -> Any: # type: ignore[explicit-override] + return self._raw.json() # type: ignore[reportUnknownMemberType] + + +class Client: + """Synchronous HTTP client for the generated SDK.""" + + def __init__( + self, + base_url: str, + *, + http_client: requests.Session | None = None, + authenticator: Authenticator | None = None, + ) -> None: + self._base_url = base_url.rstrip("/") + self._http_client = http_client or requests.Session() + self._authenticator = authenticator + + def request( + self, + method: str, + path: str, + *, + params: dict[str, str] | None = None, + json: Any = None, + data: Any = None, + files: Any = None, + headers: dict[str, str] | None = None, + ) -> Response: + """Send an HTTP request and return the raw response.""" + url = f"{self._base_url}{path}" + req_headers: dict[str, str] = {"Accept": "application/json"} + if json is not None: + req_headers["Content-Type"] = "application/json" + if headers: + req_headers.update(headers) + if self._authenticator is not None: + req_headers.update(self._authenticator.auth_headers()) + raw = cast( + requests.Response, + self._http_client.request( # type: ignore[reportUnknownMemberType] + method, + url, + params=params, + json=json, + data=data, + files=files, + headers=req_headers, + ), + ) + return Response(raw) + + def close(self) -> None: + """Close the underlying HTTP client.""" + self._http_client.close() + + def __enter__(self) -> Client: + return self + + def __exit__(self, *args: object) -> None: + self.close() diff --git a/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/runtime/errors.py.golden b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/runtime/errors.py.golden new file mode 100644 index 000000000..08c290a21 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-edge-cases/multipart_edge_cases/runtime/errors.py.golden @@ -0,0 +1,18 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Edge Cases — 1.0.0 +# Covers optional multipart bodies, optional parts, and text-only multipart fields. + +"""API error type.""" + +from __future__ import annotations + + +class ApiError(Exception): + """Raised when the server returns a 4xx or 5xx status code.""" + + def __init__(self, status_code: int, status: str, body: bytes) -> None: + self.status_code = status_code + self.status = status + self.body = body + super().__init__(f"{status_code} {status}") diff --git a/tests/golden/python/python-requests/multipart-edge-cases/pyproject.toml.golden b/tests/golden/python/python-requests/multipart-edge-cases/pyproject.toml.golden new file mode 100644 index 000000000..596c83a50 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-edge-cases/pyproject.toml.golden @@ -0,0 +1,10 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "multipart_edge_cases" +version = "1.0.0" +description = "Covers optional multipart bodies, optional parts, and text-only multipart fields." +requires-python = ">=3.12" +dependencies = ["requests>=2.32"] diff --git a/tests/golden/python/python-requests/multipart-nested-object-parts/README.md.golden b/tests/golden/python/python-requests/multipart-nested-object-parts/README.md.golden new file mode 100644 index 000000000..22d83ebc7 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-nested-object-parts/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Nested Object Parts + +Covers multipart object parts whose wire names differ from ergonomic names. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart_nested_object_parts`. diff --git a/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/__init__.py.golden b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/__init__.py.golden new file mode 100644 index 000000000..054b30587 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/__init__.py.golden @@ -0,0 +1,10 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +from .runtime import ApiKeyAuth as ApiKeyAuth +from .runtime import Authenticator as Authenticator +from .runtime import BearerAuth as BearerAuth +from .runtime import Client as Client +from .runtime import ApiError as ApiError diff --git a/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/apis/__init__.py.golden b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/apis/__init__.py.golden new file mode 100644 index 000000000..ab7bec874 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/apis/__init__.py.golden @@ -0,0 +1,6 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +from .multipart_api import MultipartApi as MultipartApi diff --git a/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/apis/multipart_api.py.golden b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/apis/multipart_api.py.golden new file mode 100644 index 000000000..a94f9c628 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/apis/multipart_api.py.golden @@ -0,0 +1,26 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +from __future__ import annotations + +import json + +from ..models.nested_upload import NestedUpload +from ..runtime.client import Client +from ..runtime.errors import ApiError + +class MultipartApi: + def __init__(self, client: Client) -> None: + self._client = client + + def send_nested_object_part(self, *, body: NestedUpload) -> None: + path = "/multipart/nested-object" + files: dict[str, object] = {} + files["file"] = ("file", body.file, "application/octet-stream") + files["item_config"] = (None, json.dumps(body.item_config.to_dict()), "application/json") + response = self._client.request("POST", path, files=files if files else None) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason, response.content) + return None diff --git a/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/models/__init__.py.golden b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/models/__init__.py.golden new file mode 100644 index 000000000..2d68dd31f --- /dev/null +++ b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/models/__init__.py.golden @@ -0,0 +1,7 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +from .item_config import ItemConfig as ItemConfig +from .nested_upload import NestedUpload as NestedUpload diff --git a/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/models/item_config.py.golden b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/models/item_config.py.golden new file mode 100644 index 000000000..bc1f4a4f6 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/models/item_config.py.golden @@ -0,0 +1,27 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +from __future__ import annotations + +from dataclasses import dataclass + +@dataclass +class ItemConfig: + display_name: str + retention_days: int | None = None + + def to_dict(self) -> dict[str, object]: + result: dict[str, object] = {} + result["display_name"] = self.display_name + if self.retention_days is not None: + result["retention_days"] = self.retention_days + return result + + @classmethod + def from_dict(cls, data: dict[str, object]) -> ItemConfig: + return cls( + display_name=data["display_name"], # type: ignore[assignment] + retention_days=data.get("retention_days"), # type: ignore[assignment] + ) diff --git a/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/models/nested_upload.py.golden b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/models/nested_upload.py.golden new file mode 100644 index 000000000..c96737997 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/models/nested_upload.py.golden @@ -0,0 +1,28 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +from __future__ import annotations + +from dataclasses import dataclass + +from .item_config import ItemConfig + +@dataclass +class NestedUpload: + file: bytes + item_config: ItemConfig + + def to_dict(self) -> dict[str, object]: + result: dict[str, object] = {} + result["file"] = self.file + result["item_config"] = self.item_config.to_dict() + return result + + @classmethod + def from_dict(cls, data: dict[str, object]) -> NestedUpload: + return cls( + file=data["file"], # type: ignore[assignment] + item_config=ItemConfig.from_dict(data["item_config"]), # type: ignore[arg-type] + ) diff --git a/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/py.typed.golden b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/py.typed.golden new file mode 100644 index 000000000..e69de29bb diff --git a/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/runtime/__init__.py.golden b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/runtime/__init__.py.golden new file mode 100644 index 000000000..977ff9b16 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/runtime/__init__.py.golden @@ -0,0 +1,10 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +from .auth import ApiKeyAuth as ApiKeyAuth +from .auth import Authenticator as Authenticator +from .auth import BearerAuth as BearerAuth +from .client import Client as Client +from .errors import ApiError as ApiError diff --git a/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/runtime/auth.py.golden b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/runtime/auth.py.golden new file mode 100644 index 000000000..b491b7565 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/runtime/auth.py.golden @@ -0,0 +1,51 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +"""Authentication helpers.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from collections.abc import Callable + + +class Authenticator(ABC): + """Base class for request authenticators.""" + + @abstractmethod + def auth_headers(self) -> dict[str, str]: + """Return headers to attach to every request.""" + ... + + +class BearerAuth(Authenticator): + """Bearer token authentication. + + Accepts a static token string or a callable that returns the + current token (evaluated on every request). + """ + + def __init__(self, token: str | Callable[[], str]) -> None: + self._token = token + + def auth_headers(self) -> dict[str, str]: + token = self._token() if callable(self._token) else self._token + return {"Authorization": f"Bearer {token}"} + + +class ApiKeyAuth(Authenticator): + """API key authentication via a custom header. + + Accepts a static key string or a callable that returns the + current key (evaluated on every request). + """ + + def __init__(self, header_name: str, api_key: str | Callable[[], str]) -> None: + self._header_name = header_name + self._api_key = api_key + + def auth_headers(self) -> dict[str, str]: + key = self._api_key() if callable(self._api_key) else self._api_key + return {self._header_name: key} diff --git a/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/runtime/client.py.golden b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/runtime/client.py.golden new file mode 100644 index 000000000..c15b8cac6 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/runtime/client.py.golden @@ -0,0 +1,99 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +"""HTTP client wrapping requests.""" + +from __future__ import annotations + +from typing import Any, cast + +import requests + +from .auth import Authenticator + + +class Response: + """Thin wrapper over requests.Response with strict types.""" + + def __init__(self, raw: requests.Response) -> None: + self._raw = raw + + @property + def status_code(self) -> int: + return self._raw.status_code # type: ignore[return-value] + + @property + def reason(self) -> str: + return self._raw.reason or "" + + @property + def content(self) -> bytes: + return self._raw.content # type: ignore[return-value] + + @property + def text(self) -> str: + return self._raw.text + + def json(self) -> Any: # type: ignore[explicit-override] + return self._raw.json() # type: ignore[reportUnknownMemberType] + + +class Client: + """Synchronous HTTP client for the generated SDK.""" + + def __init__( + self, + base_url: str, + *, + http_client: requests.Session | None = None, + authenticator: Authenticator | None = None, + ) -> None: + self._base_url = base_url.rstrip("/") + self._http_client = http_client or requests.Session() + self._authenticator = authenticator + + def request( + self, + method: str, + path: str, + *, + params: dict[str, str] | None = None, + json: Any = None, + data: Any = None, + files: Any = None, + headers: dict[str, str] | None = None, + ) -> Response: + """Send an HTTP request and return the raw response.""" + url = f"{self._base_url}{path}" + req_headers: dict[str, str] = {"Accept": "application/json"} + if json is not None: + req_headers["Content-Type"] = "application/json" + if headers: + req_headers.update(headers) + if self._authenticator is not None: + req_headers.update(self._authenticator.auth_headers()) + raw = cast( + requests.Response, + self._http_client.request( # type: ignore[reportUnknownMemberType] + method, + url, + params=params, + json=json, + data=data, + files=files, + headers=req_headers, + ), + ) + return Response(raw) + + def close(self) -> None: + """Close the underlying HTTP client.""" + self._http_client.close() + + def __enter__(self) -> Client: + return self + + def __exit__(self, *args: object) -> None: + self.close() diff --git a/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/runtime/errors.py.golden b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/runtime/errors.py.golden new file mode 100644 index 000000000..460f5f0d4 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-nested-object-parts/multipart_nested_object_parts/runtime/errors.py.golden @@ -0,0 +1,18 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Nested Object Parts — 1.0.0 +# Covers multipart object parts whose wire names differ from ergonomic names. + +"""API error type.""" + +from __future__ import annotations + + +class ApiError(Exception): + """Raised when the server returns a 4xx or 5xx status code.""" + + def __init__(self, status_code: int, status: str, body: bytes) -> None: + self.status_code = status_code + self.status = status + self.body = body + super().__init__(f"{status_code} {status}") diff --git a/tests/golden/python/python-requests/multipart-nested-object-parts/pyproject.toml.golden b/tests/golden/python/python-requests/multipart-nested-object-parts/pyproject.toml.golden new file mode 100644 index 000000000..377052991 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-nested-object-parts/pyproject.toml.golden @@ -0,0 +1,10 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "multipart_nested_object_parts" +version = "1.0.0" +description = "Covers multipart object parts whose wire names differ from ergonomic names." +requires-python = ">=3.12" +dependencies = ["requests>=2.32"] diff --git a/tests/golden/python/python-requests/multipart-unsupported-schema/README.md.golden b/tests/golden/python/python-requests/multipart-unsupported-schema/README.md.golden new file mode 100644 index 000000000..53e55944b --- /dev/null +++ b/tests/golden/python/python-requests/multipart-unsupported-schema/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Unsupported Schema + +Covers multipart request bodies that are not object-shaped. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart_unsupported_schema`. diff --git a/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/__init__.py.golden b/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/__init__.py.golden new file mode 100644 index 000000000..e0315de8c --- /dev/null +++ b/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/__init__.py.golden @@ -0,0 +1,10 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Unsupported Schema — 1.0.0 +# Covers multipart request bodies that are not object-shaped. + +from .runtime import ApiKeyAuth as ApiKeyAuth +from .runtime import Authenticator as Authenticator +from .runtime import BearerAuth as BearerAuth +from .runtime import Client as Client +from .runtime import ApiError as ApiError diff --git a/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/apis/__init__.py.golden b/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/apis/__init__.py.golden new file mode 100644 index 000000000..fcab6d1f4 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/apis/__init__.py.golden @@ -0,0 +1,6 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Unsupported Schema — 1.0.0 +# Covers multipart request bodies that are not object-shaped. + +from .transfer_api import TransferApi as TransferApi diff --git a/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/apis/transfer_api.py.golden b/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/apis/transfer_api.py.golden new file mode 100644 index 000000000..c120ff1e0 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/apis/transfer_api.py.golden @@ -0,0 +1,21 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Unsupported Schema — 1.0.0 +# Covers multipart request bodies that are not object-shaped. + +from __future__ import annotations + +from ..runtime.client import Client +from ..runtime.errors import ApiError + +class TransferApi: + def __init__(self, client: Client) -> None: + self._client = client + + def upload_raw_multipart(self, *, body: bytes) -> None: + path = "/uploads/raw" + raise ValueError("unsupported multipart request body: schema must be object-shaped") + response = self._client.request("POST", path) + if response.status_code >= 400: + raise ApiError(response.status_code, response.reason, response.content) + return None diff --git a/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/models/__init__.py.golden b/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/models/__init__.py.golden new file mode 100644 index 000000000..1f7489a4a --- /dev/null +++ b/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/models/__init__.py.golden @@ -0,0 +1,5 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Unsupported Schema — 1.0.0 +# Covers multipart request bodies that are not object-shaped. + diff --git a/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/py.typed.golden b/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/py.typed.golden new file mode 100644 index 000000000..e69de29bb diff --git a/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/runtime/__init__.py.golden b/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/runtime/__init__.py.golden new file mode 100644 index 000000000..d07130e91 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/runtime/__init__.py.golden @@ -0,0 +1,10 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Unsupported Schema — 1.0.0 +# Covers multipart request bodies that are not object-shaped. + +from .auth import ApiKeyAuth as ApiKeyAuth +from .auth import Authenticator as Authenticator +from .auth import BearerAuth as BearerAuth +from .client import Client as Client +from .errors import ApiError as ApiError diff --git a/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/runtime/auth.py.golden b/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/runtime/auth.py.golden new file mode 100644 index 000000000..3aa190459 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/runtime/auth.py.golden @@ -0,0 +1,51 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Unsupported Schema — 1.0.0 +# Covers multipart request bodies that are not object-shaped. + +"""Authentication helpers.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from collections.abc import Callable + + +class Authenticator(ABC): + """Base class for request authenticators.""" + + @abstractmethod + def auth_headers(self) -> dict[str, str]: + """Return headers to attach to every request.""" + ... + + +class BearerAuth(Authenticator): + """Bearer token authentication. + + Accepts a static token string or a callable that returns the + current token (evaluated on every request). + """ + + def __init__(self, token: str | Callable[[], str]) -> None: + self._token = token + + def auth_headers(self) -> dict[str, str]: + token = self._token() if callable(self._token) else self._token + return {"Authorization": f"Bearer {token}"} + + +class ApiKeyAuth(Authenticator): + """API key authentication via a custom header. + + Accepts a static key string or a callable that returns the + current key (evaluated on every request). + """ + + def __init__(self, header_name: str, api_key: str | Callable[[], str]) -> None: + self._header_name = header_name + self._api_key = api_key + + def auth_headers(self) -> dict[str, str]: + key = self._api_key() if callable(self._api_key) else self._api_key + return {self._header_name: key} diff --git a/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/runtime/client.py.golden b/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/runtime/client.py.golden new file mode 100644 index 000000000..0fd06f200 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/runtime/client.py.golden @@ -0,0 +1,99 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Unsupported Schema — 1.0.0 +# Covers multipart request bodies that are not object-shaped. + +"""HTTP client wrapping requests.""" + +from __future__ import annotations + +from typing import Any, cast + +import requests + +from .auth import Authenticator + + +class Response: + """Thin wrapper over requests.Response with strict types.""" + + def __init__(self, raw: requests.Response) -> None: + self._raw = raw + + @property + def status_code(self) -> int: + return self._raw.status_code # type: ignore[return-value] + + @property + def reason(self) -> str: + return self._raw.reason or "" + + @property + def content(self) -> bytes: + return self._raw.content # type: ignore[return-value] + + @property + def text(self) -> str: + return self._raw.text + + def json(self) -> Any: # type: ignore[explicit-override] + return self._raw.json() # type: ignore[reportUnknownMemberType] + + +class Client: + """Synchronous HTTP client for the generated SDK.""" + + def __init__( + self, + base_url: str, + *, + http_client: requests.Session | None = None, + authenticator: Authenticator | None = None, + ) -> None: + self._base_url = base_url.rstrip("/") + self._http_client = http_client or requests.Session() + self._authenticator = authenticator + + def request( + self, + method: str, + path: str, + *, + params: dict[str, str] | None = None, + json: Any = None, + data: Any = None, + files: Any = None, + headers: dict[str, str] | None = None, + ) -> Response: + """Send an HTTP request and return the raw response.""" + url = f"{self._base_url}{path}" + req_headers: dict[str, str] = {"Accept": "application/json"} + if json is not None: + req_headers["Content-Type"] = "application/json" + if headers: + req_headers.update(headers) + if self._authenticator is not None: + req_headers.update(self._authenticator.auth_headers()) + raw = cast( + requests.Response, + self._http_client.request( # type: ignore[reportUnknownMemberType] + method, + url, + params=params, + json=json, + data=data, + files=files, + headers=req_headers, + ), + ) + return Response(raw) + + def close(self) -> None: + """Close the underlying HTTP client.""" + self._http_client.close() + + def __enter__(self) -> Client: + return self + + def __exit__(self, *args: object) -> None: + self.close() diff --git a/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/runtime/errors.py.golden b/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/runtime/errors.py.golden new file mode 100644 index 000000000..efee62d21 --- /dev/null +++ b/tests/golden/python/python-requests/multipart-unsupported-schema/multipart_unsupported_schema/runtime/errors.py.golden @@ -0,0 +1,18 @@ +# Code generated by openapi-nexus. DO NOT EDIT. +# +# Multipart Unsupported Schema — 1.0.0 +# Covers multipart request bodies that are not object-shaped. + +"""API error type.""" + +from __future__ import annotations + + +class ApiError(Exception): + """Raised when the server returns a 4xx or 5xx status code.""" + + def __init__(self, status_code: int, status: str, body: bytes) -> None: + self.status_code = status_code + self.status = status + self.body = body + super().__init__(f"{status_code} {status}") diff --git a/tests/golden/python/python-requests/multipart-unsupported-schema/pyproject.toml.golden b/tests/golden/python/python-requests/multipart-unsupported-schema/pyproject.toml.golden new file mode 100644 index 000000000..2c1a1f79a --- /dev/null +++ b/tests/golden/python/python-requests/multipart-unsupported-schema/pyproject.toml.golden @@ -0,0 +1,10 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "multipart_unsupported_schema" +version = "1.0.0" +description = "Covers multipart request bodies that are not object-shaped." +requires-python = ">=3.12" +dependencies = ["requests>=2.32"] diff --git a/tests/golden/python/python-requests/multiple-similar-request-schemas/multiple_similar_request_schemas_api/apis/test_api_api.py.golden b/tests/golden/python/python-requests/multiple-similar-request-schemas/multiple_similar_request_schemas_api/apis/test_api_api.py.golden index 2f89e0d3c..01fc66ace 100644 --- a/tests/golden/python/python-requests/multiple-similar-request-schemas/multiple_similar_request_schemas_api/apis/test_api_api.py.golden +++ b/tests/golden/python/python-requests/multiple-similar-request-schemas/multiple_similar_request_schemas_api/apis/test_api_api.py.golden @@ -19,7 +19,9 @@ class TestApiApi: def create_type_a(self, *, body: CreateRequestTypeA) -> ResourceTypeA: """Create TypeA resource.""" path = "/api/v1/type-a/resources" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return ResourceTypeA.from_dict(response.json()) @@ -27,7 +29,9 @@ class TestApiApi: def create_type_b(self, *, body: CreateRequestTypeB) -> ResourceTypeB: """Create TypeB resource.""" path = "/api/v1/type-b/resources" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return ResourceTypeB.from_dict(response.json()) diff --git a/tests/golden/python/python-requests/multiple-similar-request-schemas/multiple_similar_request_schemas_api/runtime/client.py.golden b/tests/golden/python/python-requests/multiple-similar-request-schemas/multiple_similar_request_schemas_api/runtime/client.py.golden index 982bd4e1a..8f28da67d 100644 --- a/tests/golden/python/python-requests/multiple-similar-request-schemas/multiple_similar_request_schemas_api/runtime/client.py.golden +++ b/tests/golden/python/python-requests/multiple-similar-request-schemas/multiple_similar_request_schemas_api/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/naming-conventions/naming_conventions_test/apis/default_api.py.golden b/tests/golden/python/python-requests/naming-conventions/naming_conventions_test/apis/default_api.py.golden index c7a85747b..7f0f9aff0 100644 --- a/tests/golden/python/python-requests/naming-conventions/naming_conventions_test/apis/default_api.py.golden +++ b/tests/golden/python/python-requests/naming-conventions/naming_conventions_test/apis/default_api.py.golden @@ -53,7 +53,9 @@ class DefaultApi: def test_naming_conventions(self, *, body: NamingConventionTest) -> NamingConventionTest: """Test endpoint with various parameter naming conventions.""" path = "/test" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return NamingConventionTest.from_dict(response.json()) diff --git a/tests/golden/python/python-requests/naming-conventions/naming_conventions_test/runtime/client.py.golden b/tests/golden/python/python-requests/naming-conventions/naming_conventions_test/runtime/client.py.golden index 7b7ea338d..3fd2fae73 100644 --- a/tests/golden/python/python-requests/naming-conventions/naming_conventions_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/naming-conventions/naming_conventions_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/petstore/petstore_api/apis/pet_api.py.golden b/tests/golden/python/python-requests/petstore/petstore_api/apis/pet_api.py.golden index 719f317df..025eef356 100644 --- a/tests/golden/python/python-requests/petstore/petstore_api/apis/pet_api.py.golden +++ b/tests/golden/python/python-requests/petstore/petstore_api/apis/pet_api.py.golden @@ -17,7 +17,9 @@ class PetApi: def update_pet(self, *, body: Pet) -> Pet: """Update an existing pet.""" path = "/pet" - response = self._client.request("PUT", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("PUT", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return Pet.from_dict(response.json()) @@ -25,7 +27,9 @@ class PetApi: def add_pet(self, *, body: Pet) -> Pet: """Add a new pet to the store.""" path = "/pet" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return Pet.from_dict(response.json()) diff --git a/tests/golden/python/python-requests/petstore/petstore_api/apis/store_api.py.golden b/tests/golden/python/python-requests/petstore/petstore_api/apis/store_api.py.golden index 0b20f4333..025b8c908 100644 --- a/tests/golden/python/python-requests/petstore/petstore_api/apis/store_api.py.golden +++ b/tests/golden/python/python-requests/petstore/petstore_api/apis/store_api.py.golden @@ -24,7 +24,9 @@ class StoreApi: def place_order(self, *, body: Order) -> Order: """Place an order for a pet.""" path = "/store/order" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return Order.from_dict(response.json()) diff --git a/tests/golden/python/python-requests/petstore/petstore_api/apis/user_api.py.golden b/tests/golden/python/python-requests/petstore/petstore_api/apis/user_api.py.golden index 5fcd778aa..4223275e7 100644 --- a/tests/golden/python/python-requests/petstore/petstore_api/apis/user_api.py.golden +++ b/tests/golden/python/python-requests/petstore/petstore_api/apis/user_api.py.golden @@ -16,7 +16,9 @@ class UserApi: def create_user(self, *, body: User) -> User: """Create user.""" path = "/user" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return User.from_dict(response.json()) @@ -24,7 +26,9 @@ class UserApi: def create_users_with_list_input(self, *, body: list[User]) -> User: """Creates list of users with given input array.""" path = "/user/createWithList" - response = self._client.request("POST", path, json=[item.to_dict() for item in body]) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=[item.to_dict() for item in body], headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return User.from_dict(response.json()) @@ -61,7 +65,9 @@ class UserApi: def update_user(self, username: str, *, body: User) -> None: """Update user.""" path = f"/user/{username}" - response = self._client.request("PUT", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("PUT", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return None diff --git a/tests/golden/python/python-requests/petstore/petstore_api/runtime/client.py.golden b/tests/golden/python/python-requests/petstore/petstore_api/runtime/client.py.golden index 9b2a59593..ae1da8b69 100644 --- a/tests/golden/python/python-requests/petstore/petstore_api/runtime/client.py.golden +++ b/tests/golden/python/python-requests/petstore/petstore_api/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/query-param-enum/query_parameter_enum_test/runtime/client.py.golden b/tests/golden/python/python-requests/query-param-enum/query_parameter_enum_test/runtime/client.py.golden index 8c3f58fbe..71b3bd24b 100644 --- a/tests/golden/python/python-requests/query-param-enum/query_parameter_enum_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/query-param-enum/query_parameter_enum_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/recursive-json-all-optional-properties/all_optional_properties_test/runtime/client.py.golden b/tests/golden/python/python-requests/recursive-json-all-optional-properties/all_optional_properties_test/runtime/client.py.golden index e99e1f4f1..c865aade4 100644 --- a/tests/golden/python/python-requests/recursive-json-all-optional-properties/all_optional_properties_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/recursive-json-all-optional-properties/all_optional_properties_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/recursive-json-array-of-inline-objects/array_of_inline_objects_test/runtime/client.py.golden b/tests/golden/python/python-requests/recursive-json-array-of-inline-objects/array_of_inline_objects_test/runtime/client.py.golden index fe68b4ce0..6af678a69 100644 --- a/tests/golden/python/python-requests/recursive-json-array-of-inline-objects/array_of_inline_objects_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/recursive-json-array-of-inline-objects/array_of_inline_objects_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/recursive-json-array-of-referenced-types/array_of_referenced_types_test/runtime/client.py.golden b/tests/golden/python/python-requests/recursive-json-array-of-referenced-types/array_of_referenced_types_test/runtime/client.py.golden index 7a5231274..fe58db0b9 100644 --- a/tests/golden/python/python-requests/recursive-json-array-of-referenced-types/array_of_referenced_types_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/recursive-json-array-of-referenced-types/array_of_referenced_types_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/recursive-json-array-with-reference-property/array_with_reference_property_test/runtime/client.py.golden b/tests/golden/python/python-requests/recursive-json-array-with-reference-property/array_with_reference_property_test/runtime/client.py.golden index d673fb2d3..552792110 100644 --- a/tests/golden/python/python-requests/recursive-json-array-with-reference-property/array_with_reference_property_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/recursive-json-array-with-reference-property/array_with_reference_property_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/recursive-json-complex-array-structure/complex_array_structure_test/runtime/client.py.golden b/tests/golden/python/python-requests/recursive-json-complex-array-structure/complex_array_structure_test/runtime/client.py.golden index 19a0bfb6c..83afe88c5 100644 --- a/tests/golden/python/python-requests/recursive-json-complex-array-structure/complex_array_structure_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/recursive-json-complex-array-structure/complex_array_structure_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/recursive-json-deeply-nested-inline/deeply_nested_inline_objects_test/runtime/client.py.golden b/tests/golden/python/python-requests/recursive-json-deeply-nested-inline/deeply_nested_inline_objects_test/runtime/client.py.golden index ad1a91e3e..a0ba74d67 100644 --- a/tests/golden/python/python-requests/recursive-json-deeply-nested-inline/deeply_nested_inline_objects_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/recursive-json-deeply-nested-inline/deeply_nested_inline_objects_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/recursive-json-empty-array/empty_array_test/runtime/client.py.golden b/tests/golden/python/python-requests/recursive-json-empty-array/empty_array_test/runtime/client.py.golden index 2774094ad..ab423561f 100644 --- a/tests/golden/python/python-requests/recursive-json-empty-array/empty_array_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/recursive-json-empty-array/empty_array_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/recursive-json-inline-object-with-array/inline_object_with_array_test/runtime/client.py.golden b/tests/golden/python/python-requests/recursive-json-inline-object-with-array/inline_object_with_array_test/runtime/client.py.golden index c42b1ab83..b9d9173b9 100644 --- a/tests/golden/python/python-requests/recursive-json-inline-object-with-array/inline_object_with_array_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/recursive-json-inline-object-with-array/inline_object_with_array_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/recursive-json-inline-object/inline_object_test/runtime/client.py.golden b/tests/golden/python/python-requests/recursive-json-inline-object/inline_object_test/runtime/client.py.golden index fca69913a..5ea19e8d4 100644 --- a/tests/golden/python/python-requests/recursive-json-inline-object/inline_object_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/recursive-json-inline-object/inline_object_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/recursive-json-mixed-property-types/mixed_property_types_test/runtime/client.py.golden b/tests/golden/python/python-requests/recursive-json-mixed-property-types/mixed_property_types_test/runtime/client.py.golden index 505dfe078..62cac4c6e 100644 --- a/tests/golden/python/python-requests/recursive-json-mixed-property-types/mixed_property_types_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/recursive-json-mixed-property-types/mixed_property_types_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/recursive-json-nested-object-reference/nested_object_reference_test/runtime/client.py.golden b/tests/golden/python/python-requests/recursive-json-nested-object-reference/nested_object_reference_test/runtime/client.py.golden index 18d72e505..161be2f65 100644 --- a/tests/golden/python/python-requests/recursive-json-nested-object-reference/nested_object_reference_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/recursive-json-nested-object-reference/nested_object_reference_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/recursive-json-optional-array-of-inline-objects/optional_array_of_inline_objects_test/runtime/client.py.golden b/tests/golden/python/python-requests/recursive-json-optional-array-of-inline-objects/optional_array_of_inline_objects_test/runtime/client.py.golden index 3aa37c002..cbe5d1f44 100644 --- a/tests/golden/python/python-requests/recursive-json-optional-array-of-inline-objects/optional_array_of_inline_objects_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/recursive-json-optional-array-of-inline-objects/optional_array_of_inline_objects_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/recursive-json-optional-array-of-referenced-types/optional_array_of_referenced_types_test/runtime/client.py.golden b/tests/golden/python/python-requests/recursive-json-optional-array-of-referenced-types/optional_array_of_referenced_types_test/runtime/client.py.golden index 1c7ded25d..f24fc6c1a 100644 --- a/tests/golden/python/python-requests/recursive-json-optional-array-of-referenced-types/optional_array_of_referenced_types_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/recursive-json-optional-array-of-referenced-types/optional_array_of_referenced_types_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/recursive-json-optional-inline-object/optional_inline_object_test/runtime/client.py.golden b/tests/golden/python/python-requests/recursive-json-optional-inline-object/optional_inline_object_test/runtime/client.py.golden index 2b01da0d8..e97557074 100644 --- a/tests/golden/python/python-requests/recursive-json-optional-inline-object/optional_inline_object_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/recursive-json-optional-inline-object/optional_inline_object_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/recursive-json-optional-nested-object-reference/optional_nested_object_reference_test/runtime/client.py.golden b/tests/golden/python/python-requests/recursive-json-optional-nested-object-reference/optional_nested_object_reference_test/runtime/client.py.golden index 6f5c6d379..1355fd938 100644 --- a/tests/golden/python/python-requests/recursive-json-optional-nested-object-reference/optional_nested_object_reference_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/recursive-json-optional-nested-object-reference/optional_nested_object_reference_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/recursive-json-primitive-array/primitive_array_test/runtime/client.py.golden b/tests/golden/python/python-requests/recursive-json-primitive-array/primitive_array_test/runtime/client.py.golden index 04c0fd499..9059ca753 100644 --- a/tests/golden/python/python-requests/recursive-json-primitive-array/primitive_array_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/recursive-json-primitive-array/primitive_array_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/request-body-content-types/request_body_content_types/apis/default_api.py.golden b/tests/golden/python/python-requests/request-body-content-types/request_body_content_types/apis/default_api.py.golden index f1368489c..4ee3ea06f 100644 --- a/tests/golden/python/python-requests/request-body-content-types/request_body_content_types/apis/default_api.py.golden +++ b/tests/golden/python/python-requests/request-body-content-types/request_body_content_types/apis/default_api.py.golden @@ -15,56 +15,71 @@ class DefaultApi: def post_binary(self, *, body: bytes) -> None: path = "/binary" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/octet-stream" + response = self._client.request("POST", path, data=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return None def post_form(self, *, body: Payload) -> None: path = "/form" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/x-www-form-urlencoded" + response = self._client.request("POST", path, data=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return None def post_json(self, *, body: Payload) -> None: path = "/json" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return None def post_json_or_xml(self, *, body: Payload) -> None: path = "/json-or-xml" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return None def patch_merge_json(self, *, body: Payload) -> None: path = "/merge-patch" - response = self._client.request("PATCH", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/merge-patch+json" + response = self._client.request("PATCH", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return None def post_text(self, *, body: str) -> None: path = "/text" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "text/plain" + response = self._client.request("POST", path, data=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return None def post_xml(self, *, body: Payload) -> None: path = "/xml" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/xml" + raise ValueError("unsupported request body media type: application/xml") + response = self._client.request("POST", path, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return None - def get_xml_response(self) -> Payload: + def get_xml_response(self) -> str: path = "/xml-response" response = self._client.request("GET", path) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) - return Payload.from_dict(response.json()) + return response.text diff --git a/tests/golden/python/python-requests/request-body-content-types/request_body_content_types/runtime/client.py.golden b/tests/golden/python/python-requests/request-body-content-types/request_body_content_types/runtime/client.py.golden index 66d504bd1..4daeb9630 100644 --- a/tests/golden/python/python-requests/request-body-content-types/request_body_content_types/runtime/client.py.golden +++ b/tests/golden/python/python-requests/request-body-content-types/request_body_content_types/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/response-body-default-and-exact/default_and_exact_response_api/runtime/client.py.golden b/tests/golden/python/python-requests/response-body-default-and-exact/default_and_exact_response_api/runtime/client.py.golden index 3c2cead61..1e86ce50c 100644 --- a/tests/golden/python/python-requests/response-body-default-and-exact/default_and_exact_response_api/runtime/client.py.golden +++ b/tests/golden/python/python-requests/response-body-default-and-exact/default_and_exact_response_api/runtime/client.py.golden @@ -60,6 +60,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -78,6 +80,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/response-body-fallback/response_fallback_test_api/runtime/client.py.golden b/tests/golden/python/python-requests/response-body-fallback/response_fallback_test_api/runtime/client.py.golden index 585334cc0..dfbf08d94 100644 --- a/tests/golden/python/python-requests/response-body-fallback/response_fallback_test_api/runtime/client.py.golden +++ b/tests/golden/python/python-requests/response-body-fallback/response_fallback_test_api/runtime/client.py.golden @@ -60,6 +60,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -78,6 +80,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/response-body-multi-status-responses/multi_status_api/runtime/client.py.golden b/tests/golden/python/python-requests/response-body-multi-status-responses/multi_status_api/runtime/client.py.golden index 47da1bd45..693d56a17 100644 --- a/tests/golden/python/python-requests/response-body-multi-status-responses/multi_status_api/runtime/client.py.golden +++ b/tests/golden/python/python-requests/response-body-multi-status-responses/multi_status_api/runtime/client.py.golden @@ -60,6 +60,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -78,6 +80,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/response-body-no-response-body/no_response_body_api/apis/foo_api.py.golden b/tests/golden/python/python-requests/response-body-no-response-body/no_response_body_api/apis/foo_api.py.golden index 01ddbb033..741274870 100644 --- a/tests/golden/python/python-requests/response-body-no-response-body/no_response_body_api/apis/foo_api.py.golden +++ b/tests/golden/python/python-requests/response-body-no-response-body/no_response_body_api/apis/foo_api.py.golden @@ -15,7 +15,9 @@ class FooApi: def update_foo_bar(self, foo_id: str, *, body: FooRequest) -> ErrorResponse: path = f"/foo/bar" - response = self._client.request("PATCH", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("PATCH", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return ErrorResponse.from_dict(response.json()) diff --git a/tests/golden/python/python-requests/response-body-no-response-body/no_response_body_api/runtime/client.py.golden b/tests/golden/python/python-requests/response-body-no-response-body/no_response_body_api/runtime/client.py.golden index 37a9a3ed3..69ffa4b52 100644 --- a/tests/golden/python/python-requests/response-body-no-response-body/no_response_body_api/runtime/client.py.golden +++ b/tests/golden/python/python-requests/response-body-no-response-body/no_response_body_api/runtime/client.py.golden @@ -60,6 +60,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -78,6 +80,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/server-object/test_api_with_server_objects/runtime/client.py.golden b/tests/golden/python/python-requests/server-object/test_api_with_server_objects/runtime/client.py.golden index 81302e61e..c203d0606 100644 --- a/tests/golden/python/python-requests/server-object/test_api_with_server_objects/runtime/client.py.golden +++ b/tests/golden/python/python-requests/server-object/test_api_with_server_objects/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/server-path-prefix/server_path_prefix_test/runtime/client.py.golden b/tests/golden/python/python-requests/server-path-prefix/server_path_prefix_test/runtime/client.py.golden index 3e6ad43c1..69e439583 100644 --- a/tests/golden/python/python-requests/server-path-prefix/server_path_prefix_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/server-path-prefix/server_path_prefix_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/type-aliases-complex-union/complex_union_type_test/apis/default_api.py.golden b/tests/golden/python/python-requests/type-aliases-complex-union/complex_union_type_test/apis/default_api.py.golden index c5a13f799..da43f1de6 100644 --- a/tests/golden/python/python-requests/type-aliases-complex-union/complex_union_type_test/apis/default_api.py.golden +++ b/tests/golden/python/python-requests/type-aliases-complex-union/complex_union_type_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def test_complex_union(self, *, body: Foo) -> Foo: """Test endpoint with complex union type.""" path = "/test" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-requests/type-aliases-complex-union/complex_union_type_test/runtime/client.py.golden b/tests/golden/python/python-requests/type-aliases-complex-union/complex_union_type_test/runtime/client.py.golden index 7b4aff884..e9bc215ec 100644 --- a/tests/golden/python/python-requests/type-aliases-complex-union/complex_union_type_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/type-aliases-complex-union/complex_union_type_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/type-aliases-discriminated-union-inline-discriminator-only/discriminated_union_with_inline_discriminator_only_test/apis/default_api.py.golden b/tests/golden/python/python-requests/type-aliases-discriminated-union-inline-discriminator-only/discriminated_union_with_inline_discriminator_only_test/apis/default_api.py.golden index fd0c52ec1..bea6f1465 100644 --- a/tests/golden/python/python-requests/type-aliases-discriminated-union-inline-discriminator-only/discriminated_union_with_inline_discriminator_only_test/apis/default_api.py.golden +++ b/tests/golden/python/python-requests/type-aliases-discriminated-union-inline-discriminator-only/discriminated_union_with_inline_discriminator_only_test/apis/default_api.py.golden @@ -25,7 +25,9 @@ class DefaultApi: def create_event(self, *, body: Event) -> Event: """Create event with discriminated union.""" path = "/event" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-requests/type-aliases-discriminated-union-inline-discriminator-only/discriminated_union_with_inline_discriminator_only_test/runtime/client.py.golden b/tests/golden/python/python-requests/type-aliases-discriminated-union-inline-discriminator-only/discriminated_union_with_inline_discriminator_only_test/runtime/client.py.golden index 24beafbe9..5ecd8b8f9 100644 --- a/tests/golden/python/python-requests/type-aliases-discriminated-union-inline-discriminator-only/discriminated_union_with_inline_discriminator_only_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/type-aliases-discriminated-union-inline-discriminator-only/discriminated_union_with_inline_discriminator_only_test/runtime/client.py.golden @@ -70,6 +70,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -88,6 +90,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/type-aliases-discriminated-union-internally-tagged/discriminated_union_internally_tagged_test/apis/default_api.py.golden b/tests/golden/python/python-requests/type-aliases-discriminated-union-internally-tagged/discriminated_union_internally_tagged_test/apis/default_api.py.golden index 2efb45ee9..12ea6d28a 100644 --- a/tests/golden/python/python-requests/type-aliases-discriminated-union-internally-tagged/discriminated_union_internally_tagged_test/apis/default_api.py.golden +++ b/tests/golden/python/python-requests/type-aliases-discriminated-union-internally-tagged/discriminated_union_internally_tagged_test/apis/default_api.py.golden @@ -18,7 +18,9 @@ class DefaultApi: def create_resource(self, *, body: Resource) -> Resource: """Create resource with discriminated union.""" path = "/resource" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-requests/type-aliases-discriminated-union-internally-tagged/discriminated_union_internally_tagged_test/runtime/client.py.golden b/tests/golden/python/python-requests/type-aliases-discriminated-union-internally-tagged/discriminated_union_internally_tagged_test/runtime/client.py.golden index 3fd70dce3..8c524a3a8 100644 --- a/tests/golden/python/python-requests/type-aliases-discriminated-union-internally-tagged/discriminated_union_internally_tagged_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/type-aliases-discriminated-union-internally-tagged/discriminated_union_internally_tagged_test/runtime/client.py.golden @@ -63,6 +63,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -81,6 +83,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/type-aliases-discriminated-union-long-names/discriminated_union_with_long_type_names_test/apis/default_api.py.golden b/tests/golden/python/python-requests/type-aliases-discriminated-union-long-names/discriminated_union_with_long_type_names_test/apis/default_api.py.golden index 72b7b7899..e5b4056ae 100644 --- a/tests/golden/python/python-requests/type-aliases-discriminated-union-long-names/discriminated_union_with_long_type_names_test/apis/default_api.py.golden +++ b/tests/golden/python/python-requests/type-aliases-discriminated-union-long-names/discriminated_union_with_long_type_names_test/apis/default_api.py.golden @@ -18,7 +18,9 @@ class DefaultApi: def create_resource(self, *, body: VeryLongDiscriminatedUnionTypeNameForTesting20260630) -> VeryLongDiscriminatedUnionTypeNameForTesting20260630: """Create resource.""" path = "/resource" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-requests/type-aliases-discriminated-union-long-names/discriminated_union_with_long_type_names_test/runtime/client.py.golden b/tests/golden/python/python-requests/type-aliases-discriminated-union-long-names/discriminated_union_with_long_type_names_test/runtime/client.py.golden index 63ce6d135..d5143840a 100644 --- a/tests/golden/python/python-requests/type-aliases-discriminated-union-long-names/discriminated_union_with_long_type_names_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/type-aliases-discriminated-union-long-names/discriminated_union_with_long_type_names_test/runtime/client.py.golden @@ -63,6 +63,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -81,6 +83,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/type-aliases-discriminated-union-mixed-unit-and-allof/discriminated_union_mixed_unit_and_all_of_test/apis/default_api.py.golden b/tests/golden/python/python-requests/type-aliases-discriminated-union-mixed-unit-and-allof/discriminated_union_mixed_unit_and_all_of_test/apis/default_api.py.golden index 8a7a44385..11624a5a4 100644 --- a/tests/golden/python/python-requests/type-aliases-discriminated-union-mixed-unit-and-allof/discriminated_union_mixed_unit_and_all_of_test/apis/default_api.py.golden +++ b/tests/golden/python/python-requests/type-aliases-discriminated-union-mixed-unit-and-allof/discriminated_union_mixed_unit_and_all_of_test/apis/default_api.py.golden @@ -21,7 +21,9 @@ class DefaultApi: def create_resource(self, *, body: Setup) -> Setup: """Create resource.""" path = "/resource" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-requests/type-aliases-discriminated-union-mixed-unit-and-allof/discriminated_union_mixed_unit_and_all_of_test/runtime/client.py.golden b/tests/golden/python/python-requests/type-aliases-discriminated-union-mixed-unit-and-allof/discriminated_union_mixed_unit_and_all_of_test/runtime/client.py.golden index 2e156bebc..b7f0d1049 100644 --- a/tests/golden/python/python-requests/type-aliases-discriminated-union-mixed-unit-and-allof/discriminated_union_mixed_unit_and_all_of_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/type-aliases-discriminated-union-mixed-unit-and-allof/discriminated_union_mixed_unit_and_all_of_test/runtime/client.py.golden @@ -66,6 +66,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -84,6 +86,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/type-aliases-discriminated-union-multiple/multiple_discriminated_unions_test/apis/default_api.py.golden b/tests/golden/python/python-requests/type-aliases-discriminated-union-multiple/multiple_discriminated_unions_test/apis/default_api.py.golden index 42dd2ab07..20a7f42f0 100644 --- a/tests/golden/python/python-requests/type-aliases-discriminated-union-multiple/multiple_discriminated_unions_test/apis/default_api.py.golden +++ b/tests/golden/python/python-requests/type-aliases-discriminated-union-multiple/multiple_discriminated_unions_test/apis/default_api.py.golden @@ -20,7 +20,9 @@ class DefaultApi: def create_container(self, *, body: ContainerKind) -> ContainerKind: """Create container with ContainerKind discriminated union.""" path = "/container" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] @@ -28,7 +30,9 @@ class DefaultApi: def create_volume(self, *, body: VolumeKind) -> VolumeKind: """Create volume with VolumeKind discriminated union.""" path = "/volume" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-requests/type-aliases-discriminated-union-multiple/multiple_discriminated_unions_test/runtime/client.py.golden b/tests/golden/python/python-requests/type-aliases-discriminated-union-multiple/multiple_discriminated_unions_test/runtime/client.py.golden index bb19b5648..3d800828c 100644 --- a/tests/golden/python/python-requests/type-aliases-discriminated-union-multiple/multiple_discriminated_unions_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/type-aliases-discriminated-union-multiple/multiple_discriminated_unions_test/runtime/client.py.golden @@ -64,6 +64,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -82,6 +84,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/type-aliases-discriminated-union-with-refs/discriminated_union_with_references_test/apis/default_api.py.golden b/tests/golden/python/python-requests/type-aliases-discriminated-union-with-refs/discriminated_union_with_references_test/apis/default_api.py.golden index 77c202e40..5c5770d34 100644 --- a/tests/golden/python/python-requests/type-aliases-discriminated-union-with-refs/discriminated_union_with_references_test/apis/default_api.py.golden +++ b/tests/golden/python/python-requests/type-aliases-discriminated-union-with-refs/discriminated_union_with_references_test/apis/default_api.py.golden @@ -17,7 +17,9 @@ class DefaultApi: def create_container(self, *, body: ContainerImage) -> ContainerImage: """Create container with discriminated union.""" path = "/container" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-requests/type-aliases-discriminated-union-with-refs/discriminated_union_with_references_test/runtime/client.py.golden b/tests/golden/python/python-requests/type-aliases-discriminated-union-with-refs/discriminated_union_with_references_test/runtime/client.py.golden index 2871d098a..93339d7d6 100644 --- a/tests/golden/python/python-requests/type-aliases-discriminated-union-with-refs/discriminated_union_with_references_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/type-aliases-discriminated-union-with-refs/discriminated_union_with_references_test/runtime/client.py.golden @@ -62,6 +62,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -80,6 +82,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/type-aliases-intersection-allof/intersection_type_all_of_test/apis/default_api.py.golden b/tests/golden/python/python-requests/type-aliases-intersection-allof/intersection_type_all_of_test/apis/default_api.py.golden index b028266a2..7ee526689 100644 --- a/tests/golden/python/python-requests/type-aliases-intersection-allof/intersection_type_all_of_test/apis/default_api.py.golden +++ b/tests/golden/python/python-requests/type-aliases-intersection-allof/intersection_type_all_of_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def test_intersection(self, *, body: Foo) -> Foo: """Test endpoint with intersection type.""" path = "/test" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return Foo.from_dict(response.json()) diff --git a/tests/golden/python/python-requests/type-aliases-intersection-allof/intersection_type_all_of_test/runtime/client.py.golden b/tests/golden/python/python-requests/type-aliases-intersection-allof/intersection_type_all_of_test/runtime/client.py.golden index 7d296a28c..a23e93416 100644 --- a/tests/golden/python/python-requests/type-aliases-intersection-allof/intersection_type_all_of_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/type-aliases-intersection-allof/intersection_type_all_of_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/type-aliases-intersection-with-nullable-reference/intersection_type_with_nullable_reference_test/apis/default_api.py.golden b/tests/golden/python/python-requests/type-aliases-intersection-with-nullable-reference/intersection_type_with_nullable_reference_test/apis/default_api.py.golden index 84e587639..28d760943 100644 --- a/tests/golden/python/python-requests/type-aliases-intersection-with-nullable-reference/intersection_type_with_nullable_reference_test/apis/default_api.py.golden +++ b/tests/golden/python/python-requests/type-aliases-intersection-with-nullable-reference/intersection_type_with_nullable_reference_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def create_test(self, *, body: Baz) -> Baz: """Create test with intersection type.""" path = "/test" - response = self._client.request("POST", path, json=body.to_dict()) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body.to_dict(), headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return Baz.from_dict(response.json()) diff --git a/tests/golden/python/python-requests/type-aliases-intersection-with-nullable-reference/intersection_type_with_nullable_reference_test/runtime/client.py.golden b/tests/golden/python/python-requests/type-aliases-intersection-with-nullable-reference/intersection_type_with_nullable_reference_test/runtime/client.py.golden index 27f2882e4..dc1543a50 100644 --- a/tests/golden/python/python-requests/type-aliases-intersection-with-nullable-reference/intersection_type_with_nullable_reference_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/type-aliases-intersection-with-nullable-reference/intersection_type_with_nullable_reference_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/type-aliases-nested-union/nested_union_type_test/apis/default_api.py.golden b/tests/golden/python/python-requests/type-aliases-nested-union/nested_union_type_test/apis/default_api.py.golden index f962f97f5..3a6c3d008 100644 --- a/tests/golden/python/python-requests/type-aliases-nested-union/nested_union_type_test/apis/default_api.py.golden +++ b/tests/golden/python/python-requests/type-aliases-nested-union/nested_union_type_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def test_nested_union(self, *, body: Foo) -> Foo: """Test endpoint with nested union type.""" path = "/test" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-requests/type-aliases-nested-union/nested_union_type_test/runtime/client.py.golden b/tests/golden/python/python-requests/type-aliases-nested-union/nested_union_type_test/runtime/client.py.golden index 78f323eef..3c5456228 100644 --- a/tests/golden/python/python-requests/type-aliases-nested-union/nested_union_type_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/type-aliases-nested-union/nested_union_type_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/type-aliases-simple-type-alias/simple_type_alias_test/apis/default_api.py.golden b/tests/golden/python/python-requests/type-aliases-simple-type-alias/simple_type_alias_test/apis/default_api.py.golden index c64dbd97d..9fc044eda 100644 --- a/tests/golden/python/python-requests/type-aliases-simple-type-alias/simple_type_alias_test/apis/default_api.py.golden +++ b/tests/golden/python/python-requests/type-aliases-simple-type-alias/simple_type_alias_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def test_simple_alias(self, *, body: Foo) -> Foo: """Test endpoint with simple type alias.""" path = "/test" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-requests/type-aliases-simple-type-alias/simple_type_alias_test/runtime/client.py.golden b/tests/golden/python/python-requests/type-aliases-simple-type-alias/simple_type_alias_test/runtime/client.py.golden index f38a1f042..94150b9f3 100644 --- a/tests/golden/python/python-requests/type-aliases-simple-type-alias/simple_type_alias_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/type-aliases-simple-type-alias/simple_type_alias_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/type-aliases-union-mixed/union_type_with_mixed_members_test/apis/default_api.py.golden b/tests/golden/python/python-requests/type-aliases-union-mixed/union_type_with_mixed_members_test/apis/default_api.py.golden index 7070005b7..dbe99deb6 100644 --- a/tests/golden/python/python-requests/type-aliases-union-mixed/union_type_with_mixed_members_test/apis/default_api.py.golden +++ b/tests/golden/python/python-requests/type-aliases-union-mixed/union_type_with_mixed_members_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def test_mixed_union(self, *, body: Foo) -> Foo: """Test endpoint with mixed union type.""" path = "/test" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-requests/type-aliases-union-mixed/union_type_with_mixed_members_test/runtime/client.py.golden b/tests/golden/python/python-requests/type-aliases-union-mixed/union_type_with_mixed_members_test/runtime/client.py.golden index f6aa78b97..05364f596 100644 --- a/tests/golden/python/python-requests/type-aliases-union-mixed/union_type_with_mixed_members_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/type-aliases-union-mixed/union_type_with_mixed_members_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/type-aliases-union-with-any/union_type_with_any_test/apis/default_api.py.golden b/tests/golden/python/python-requests/type-aliases-union-with-any/union_type_with_any_test/apis/default_api.py.golden index a012b294a..12caf818e 100644 --- a/tests/golden/python/python-requests/type-aliases-union-with-any/union_type_with_any_test/apis/default_api.py.golden +++ b/tests/golden/python/python-requests/type-aliases-union-with-any/union_type_with_any_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def test_union_with_any(self, *, body: Config) -> Config: """Test endpoint with union type including any.""" path = "/test" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-requests/type-aliases-union-with-any/union_type_with_any_test/runtime/client.py.golden b/tests/golden/python/python-requests/type-aliases-union-with-any/union_type_with_any_test/runtime/client.py.golden index f47349f49..2fbdfbca2 100644 --- a/tests/golden/python/python-requests/type-aliases-union-with-any/union_type_with_any_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/type-aliases-union-with-any/union_type_with_any_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/type-aliases-union-with-inline-objects/union_type_with_inline_objects_test/apis/default_api.py.golden b/tests/golden/python/python-requests/type-aliases-union-with-inline-objects/union_type_with_inline_objects_test/apis/default_api.py.golden index 93ecf81ee..7bc830651 100644 --- a/tests/golden/python/python-requests/type-aliases-union-with-inline-objects/union_type_with_inline_objects_test/apis/default_api.py.golden +++ b/tests/golden/python/python-requests/type-aliases-union-with-inline-objects/union_type_with_inline_objects_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def test_union_with_inline_objects(self, *, body: Foo) -> Foo: """Test endpoint with union type including inline objects.""" path = "/test" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-requests/type-aliases-union-with-inline-objects/union_type_with_inline_objects_test/runtime/client.py.golden b/tests/golden/python/python-requests/type-aliases-union-with-inline-objects/union_type_with_inline_objects_test/runtime/client.py.golden index b1106b9d1..5906da366 100644 --- a/tests/golden/python/python-requests/type-aliases-union-with-inline-objects/union_type_with_inline_objects_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/type-aliases-union-with-inline-objects/union_type_with_inline_objects_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/type-aliases-union-with-interfaces/union_type_with_interfaces_test/apis/default_api.py.golden b/tests/golden/python/python-requests/type-aliases-union-with-interfaces/union_type_with_interfaces_test/apis/default_api.py.golden index 3d75d3c89..2bd0f6f2b 100644 --- a/tests/golden/python/python-requests/type-aliases-union-with-interfaces/union_type_with_interfaces_test/apis/default_api.py.golden +++ b/tests/golden/python/python-requests/type-aliases-union-with-interfaces/union_type_with_interfaces_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def test_union(self, *, body: Foo) -> Foo: """Test endpoint with union type.""" path = "/test" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-requests/type-aliases-union-with-interfaces/union_type_with_interfaces_test/runtime/client.py.golden b/tests/golden/python/python-requests/type-aliases-union-with-interfaces/union_type_with_interfaces_test/runtime/client.py.golden index 7856acce1..da2b72992 100644 --- a/tests/golden/python/python-requests/type-aliases-union-with-interfaces/union_type_with_interfaces_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/type-aliases-union-with-interfaces/union_type_with_interfaces_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/python/python-requests/type-aliases-union-with-primitives/union_type_with_primitives_test/apis/default_api.py.golden b/tests/golden/python/python-requests/type-aliases-union-with-primitives/union_type_with_primitives_test/apis/default_api.py.golden index aa3c42d91..f26bd73ca 100644 --- a/tests/golden/python/python-requests/type-aliases-union-with-primitives/union_type_with_primitives_test/apis/default_api.py.golden +++ b/tests/golden/python/python-requests/type-aliases-union-with-primitives/union_type_with_primitives_test/apis/default_api.py.golden @@ -16,7 +16,9 @@ class DefaultApi: def test_union_primitives(self, *, body: Foo) -> Foo: """Test endpoint with union type.""" path = "/test" - response = self._client.request("POST", path, json=body) + headers: dict[str, str] = {} + headers["Content-Type"] = "application/json" + response = self._client.request("POST", path, json=body, headers=headers) if response.status_code >= 400: raise ApiError(response.status_code, response.reason, response.content) return response.json() # type: ignore[return-value] diff --git a/tests/golden/python/python-requests/type-aliases-union-with-primitives/union_type_with_primitives_test/runtime/client.py.golden b/tests/golden/python/python-requests/type-aliases-union-with-primitives/union_type_with_primitives_test/runtime/client.py.golden index d0deb8363..f7220a5ff 100644 --- a/tests/golden/python/python-requests/type-aliases-union-with-primitives/union_type_with_primitives_test/runtime/client.py.golden +++ b/tests/golden/python/python-requests/type-aliases-union-with-primitives/union_type_with_primitives_test/runtime/client.py.golden @@ -61,6 +61,8 @@ class Client: *, params: dict[str, str] | None = None, json: Any = None, + data: Any = None, + files: Any = None, headers: dict[str, str] | None = None, ) -> Response: """Send an HTTP request and return the raw response.""" @@ -79,6 +81,8 @@ class Client: url, params=params, json=json, + data=data, + files=files, headers=req_headers, ), ) diff --git a/tests/golden/rust/rust-aioduct/media-type-selection/Cargo.toml.golden b/tests/golden/rust/rust-aioduct/media-type-selection/Cargo.toml.golden new file mode 100644 index 000000000..4efda2c34 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/media-type-selection/Cargo.toml.golden @@ -0,0 +1,12 @@ +[package] +name = "media-type-selection" +version = "0.1.0" +edition = "2024" +description = "Covers normalized media-type selection for requests and responses." + +[dependencies] +aioduct = { version = "0.1.8", features = ["tokio", "rustls", "rustls-ring", "json"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" diff --git a/tests/golden/rust/rust-aioduct/media-type-selection/README.md.golden b/tests/golden/rust/rust-aioduct/media-type-selection/README.md.golden new file mode 100644 index 000000000..7a446d9d1 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/media-type-selection/README.md.golden @@ -0,0 +1,7 @@ +# Media Type Selection + +Covers normalized media-type selection for requests and responses. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `media-type-selection`. diff --git a/tests/golden/rust/rust-aioduct/media-type-selection/src/apis/media.rs.golden b/tests/golden/rust/rust-aioduct/media-type-selection/src/apis/media.rs.golden new file mode 100644 index 000000000..8025f547b --- /dev/null +++ b/tests/golden/rust/rust-aioduct/media-type-selection/src/apis/media.rs.golden @@ -0,0 +1,162 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +use crate::runtime::client::Client; +use crate::runtime::error::Error; + +/// API operations under the "media" tag. +pub struct MediaApi<'a, R: aioduct::Runtime> { + client: &'a Client, +} + +impl<'a, R: aioduct::Runtime> MediaApi<'a, R> { + /// Create a new `MediaApi` bound to the given client. + pub fn new(client: &'a Client) -> Self { + Self { + client, + } + } + + /// POST /request/json-vs-multipart + pub async fn send_json_preferred( + &self, + body: &crate::models::Payload, + ) -> Result { + let path = "/request/json-vs-multipart".to_string(); + let mut req = self.client.post(&path)?; + req = req.json(&body)?; + let resp = req.send().await?; + let status_code = resp.status().as_u16(); + Ok(SendJsonPreferredResponse { status_code }) + } + + /// POST /request/parameterized-multipart + pub async fn send_parameterized_multipart( + &self, + body: &crate::models::FileEnvelope, + ) -> Result { + let path = "/request/parameterized-multipart".to_string(); + let mut req = self.client.post(&path)?; + let mut multipart = aioduct::multipart::Multipart::new(); + multipart = multipart.file("file", "file", "application/octet-stream", body.file.clone()); + if let Some(value) = &body.note { + multipart = multipart.text("note", value.to_string()); + } + req = req.multipart(multipart); + let resp = req.send().await?; + let status_code = resp.status().as_u16(); + Ok(SendParameterizedMultipartResponse { status_code }) + } + + /// PATCH /request/vendor-json + pub async fn send_vendor_json( + &self, + body: &crate::models::Payload, + ) -> Result { + let path = "/request/vendor-json".to_string(); + let mut req = self.client.patch(&path)?; + req = req.json(&body)?; + let resp = req.send().await?; + let status_code = resp.status().as_u16(); + Ok(SendVendorJsonResponse { status_code }) + } + + /// GET /response/octet-before-xml + pub async fn get_octet_preferred( + &self, + ) -> Result { + let path = "/response/octet-before-xml".to_string(); + let req = self.client.get(&path)?; + let resp = req.send().await?; + let status_code = resp.status().as_u16(); + let body_bytes = resp.bytes().await?; + let mut result = GetOctetPreferredResponse { status_code, data: None }; + match status_code { + 200 => { + result.data = Some(Ok::, Error>(body_bytes.to_vec())?); + } + _ => {} + } + Ok(result) + } + + /// GET /response/text-before-xml + pub async fn get_text_preferred( + &self, + ) -> Result { + let path = "/response/text-before-xml".to_string(); + let req = self.client.get(&path)?; + let resp = req.send().await?; + let status_code = resp.status().as_u16(); + let body_bytes = resp.bytes().await?; + let mut result = GetTextPreferredResponse { status_code, data: None }; + match status_code { + 200 => { + result.data = Some(Ok::(String::from_utf8_lossy(&body_bytes).into_owned())?); + } + _ => {} + } + Ok(result) + } + + /// GET /response/vendor-json + pub async fn get_vendor_json( + &self, + ) -> Result { + let path = "/response/vendor-json".to_string(); + let req = self.client.get(&path)?; + let resp = req.send().await?; + let status_code = resp.status().as_u16(); + let body_bytes = resp.bytes().await?; + let mut result = GetVendorJsonResponse { status_code, data: None }; + match status_code { + 200 => { + result.data = Some(serde_json::from_slice(&body_bytes).map_err(Error::Deserialize)?); + } + _ => {} + } + Ok(result) + } +} + +/// Response from `send_json_preferred`. +#[derive(Debug)] +pub struct SendJsonPreferredResponse { + pub status_code: u16, +} + +/// Response from `send_parameterized_multipart`. +#[derive(Debug)] +pub struct SendParameterizedMultipartResponse { + pub status_code: u16, +} + +/// Response from `send_vendor_json`. +#[derive(Debug)] +pub struct SendVendorJsonResponse { + pub status_code: u16, +} + +/// Response from `get_octet_preferred`. +#[derive(Debug)] +pub struct GetOctetPreferredResponse { + pub status_code: u16, + pub data: Option>, +} + +/// Response from `get_text_preferred`. +#[derive(Debug)] +pub struct GetTextPreferredResponse { + pub status_code: u16, + pub data: Option, +} + +/// Response from `get_vendor_json`. +#[derive(Debug)] +pub struct GetVendorJsonResponse { + pub status_code: u16, + pub data: Option, +} diff --git a/tests/golden/rust/rust-aioduct/media-type-selection/src/apis/mod.rs.golden b/tests/golden/rust/rust-aioduct/media-type-selection/src/apis/mod.rs.golden new file mode 100644 index 000000000..f5a75cd31 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/media-type-selection/src/apis/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +mod media; +pub use media::*; diff --git a/tests/golden/rust/rust-aioduct/media-type-selection/src/lib.rs.golden b/tests/golden/rust/rust-aioduct/media-type-selection/src/lib.rs.golden new file mode 100644 index 000000000..267625e15 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/media-type-selection/src/lib.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-aioduct/media-type-selection/src/models/file_envelope.rs.golden b/tests/golden/rust/rust-aioduct/media-type-selection/src/models/file_envelope.rs.golden new file mode 100644 index 000000000..5a1351a16 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/media-type-selection/src/models/file_envelope.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FileEnvelope { + pub file: Vec, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub note: Option, +} diff --git a/tests/golden/rust/rust-aioduct/media-type-selection/src/models/mod.rs.golden b/tests/golden/rust/rust-aioduct/media-type-selection/src/models/mod.rs.golden new file mode 100644 index 000000000..f9bb2e882 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/media-type-selection/src/models/mod.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +mod file_envelope; +pub use file_envelope::*; +mod payload; +pub use payload::*; diff --git a/tests/golden/rust/rust-aioduct/media-type-selection/src/models/payload.rs.golden b/tests/golden/rust/rust-aioduct/media-type-selection/src/models/payload.rs.golden new file mode 100644 index 000000000..8c4017aa8 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/media-type-selection/src/models/payload.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Payload { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub count: Option, + pub id: String, +} diff --git a/tests/golden/rust/rust-aioduct/media-type-selection/src/runtime/auth.rs.golden b/tests/golden/rust/rust-aioduct/media-type-selection/src/runtime/auth.rs.golden new file mode 100644 index 000000000..8988c4a44 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/media-type-selection/src/runtime/auth.rs.golden @@ -0,0 +1,180 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +use std::sync::Arc; + +use crate::runtime::error::Error; + +/// Trait for authenticating requests. +pub trait Authenticator: Send + Sync + std::fmt::Debug { + /// Apply authentication to the request builder. + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilder<'a, R>, + ) -> Result, Error>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilder<'a, R>, + ) -> Result, Error> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + Ok(req.bearer_auth(&token)) + } +} + +/// API key authentication. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilder<'a, R>, + ) -> Result, Error> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + Ok(req.header_str(&self.header_name, &key)?) + } +} diff --git a/tests/golden/rust/rust-aioduct/media-type-selection/src/runtime/client.rs.golden b/tests/golden/rust/rust-aioduct/media-type-selection/src/runtime/client.rs.golden new file mode 100644 index 000000000..f115d5af0 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/media-type-selection/src/runtime/client.rs.golden @@ -0,0 +1,112 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +use std::fmt; +use std::sync::Arc; + +use aioduct::Runtime; + +use crate::runtime::auth::Authenticator; +use crate::runtime::error::Error; + +/// Async HTTP client wrapping `aioduct::Client`. +pub struct Client { + inner: aioduct::Client, + base_url: String, + authenticator: Option>>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: aioduct::Client::::with_rustls(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Create a client with a custom `aioduct::Client`. + pub fn with_client(inner: aioduct::Client, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Attach an authenticator to the client. + pub fn with_auth(mut self, auth: Arc>) -> Self { + self.authenticator = Some(auth); + self + } + + fn full_url(&self, path: &str) -> String { + format!("{}{}", self.base_url, path) + } + + /// Build a GET request. + pub fn get(&self, path: &str) -> Result, Error> { + let mut req = self.inner.get(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a POST request. + pub fn post(&self, path: &str) -> Result, Error> { + let mut req = self.inner.post(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a PUT request. + pub fn put(&self, path: &str) -> Result, Error> { + let mut req = self.inner.put(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a DELETE request. + pub fn delete(&self, path: &str) -> Result, Error> { + let mut req = self.inner.delete(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a PATCH request. + pub fn patch(&self, path: &str) -> Result, Error> { + let mut req = self.inner.patch(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a HEAD request. + pub fn head(&self, path: &str) -> Result, Error> { + let mut req = self.inner.head(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } +} + +impl fmt::Debug for Client { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Client") + .field("base_url", &self.base_url) + .finish() + } +} diff --git a/tests/golden/rust/rust-aioduct/media-type-selection/src/runtime/error.rs.golden b/tests/golden/rust/rust-aioduct/media-type-selection/src/runtime/error.rs.golden new file mode 100644 index 000000000..141130bde --- /dev/null +++ b/tests/golden/rust/rust-aioduct/media-type-selection/src/runtime/error.rs.golden @@ -0,0 +1,60 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +use std::fmt; + +/// Error types for the generated SDK. +#[derive(Debug)] +pub enum Error { + /// HTTP/transport error from aioduct. + Http(aioduct::Error), + /// JSON deserialization error. + Deserialize(serde_json::Error), + /// XML serialization error. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Http(e) => write!(f, "HTTP error: {e}"), + Error::Deserialize(e) => write!(f, "Deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Http(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: aioduct::Error) -> Self { + Error::Http(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} diff --git a/tests/golden/rust/rust-aioduct/media-type-selection/src/runtime/mod.rs.golden b/tests/golden/rust/rust-aioduct/media-type-selection/src/runtime/mod.rs.golden new file mode 100644 index 000000000..31db0c2b8 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/media-type-selection/src/runtime/mod.rs.golden @@ -0,0 +1,9 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-aioduct/multipart-edge-cases/Cargo.toml.golden b/tests/golden/rust/rust-aioduct/multipart-edge-cases/Cargo.toml.golden new file mode 100644 index 000000000..812e2a02a --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-edge-cases/Cargo.toml.golden @@ -0,0 +1,12 @@ +[package] +name = "multipart-edge-cases" +version = "0.1.0" +edition = "2024" +description = "Covers optional multipart bodies, optional parts, and text-only multipart fields." + +[dependencies] +aioduct = { version = "0.1.8", features = ["tokio", "rustls", "rustls-ring", "json"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" diff --git a/tests/golden/rust/rust-aioduct/multipart-edge-cases/README.md.golden b/tests/golden/rust/rust-aioduct/multipart-edge-cases/README.md.golden new file mode 100644 index 000000000..61745a51d --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-edge-cases/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Edge Cases + +Covers optional multipart bodies, optional parts, and text-only multipart fields. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart-edge-cases`. diff --git a/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/apis/mod.rs.golden b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/apis/mod.rs.golden new file mode 100644 index 000000000..fa77c450c --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/apis/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +mod multipart; +pub use multipart::*; diff --git a/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/apis/multipart.rs.golden b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/apis/multipart.rs.golden new file mode 100644 index 000000000..8ed74e421 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/apis/multipart.rs.golden @@ -0,0 +1,80 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use crate::runtime::client::Client; +use crate::runtime::error::Error; + +/// API operations under the "multipart" tag. +pub struct MultipartApi<'a, R: aioduct::Runtime> { + client: &'a Client, +} + +impl<'a, R: aioduct::Runtime> MultipartApi<'a, R> { + /// Create a new `MultipartApi` bound to the given client. + pub fn new(client: &'a Client) -> Self { + Self { + client, + } + } + + /// POST /multipart/optional + pub async fn send_optional_parts( + &self, + body: &crate::models::OptionalUpload, + ) -> Result { + let path = "/multipart/optional".to_string(); + let mut req = self.client.post(&path)?; + let mut multipart = aioduct::multipart::Multipart::new(); + if let Some(value) = &body.attributes { + multipart = multipart.text("attributes", serde_json::to_string(value)?); + } + if let Some(value) = &body.enabled { + multipart = multipart.text("enabled", value.to_string()); + } + if let Some(value) = &body.file { + multipart = multipart.file("file", "file", "application/octet-stream", value.clone()); + } + if let Some(value) = &body.retry_count { + multipart = multipart.text("retry_count", value.to_string()); + } + if let Some(value) = &body.title { + multipart = multipart.text("title", value.to_string()); + } + req = req.multipart(multipart); + let resp = req.send().await?; + let status_code = resp.status().as_u16(); + Ok(SendOptionalPartsResponse { status_code }) + } + + /// POST /multipart/text-only + pub async fn send_text_fields( + &self, + body: &crate::models::TextFields, + ) -> Result { + let path = "/multipart/text-only".to_string(); + let mut req = self.client.post(&path)?; + let mut multipart = aioduct::multipart::Multipart::new(); + multipart = multipart.text("enabled", body.enabled.to_string()); + multipart = multipart.text("note", body.note.to_string()); + multipart = multipart.text("retry_count", body.retry_count.to_string()); + req = req.multipart(multipart); + let resp = req.send().await?; + let status_code = resp.status().as_u16(); + Ok(SendTextFieldsResponse { status_code }) + } +} + +/// Response from `send_optional_parts`. +#[derive(Debug)] +pub struct SendOptionalPartsResponse { + pub status_code: u16, +} + +/// Response from `send_text_fields`. +#[derive(Debug)] +pub struct SendTextFieldsResponse { + pub status_code: u16, +} diff --git a/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/lib.rs.golden b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/lib.rs.golden new file mode 100644 index 000000000..42a48e994 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/lib.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/models/attributes.rs.golden b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/models/attributes.rs.golden new file mode 100644 index 000000000..c6a12ac52 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/models/attributes.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Attributes { + pub label: String, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub priority: Option, +} diff --git a/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/models/mod.rs.golden b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/models/mod.rs.golden new file mode 100644 index 000000000..97c6c9a6b --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/models/mod.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +mod attributes; +pub use attributes::*; +mod optional_upload; +pub use optional_upload::*; +mod text_fields; +pub use text_fields::*; diff --git a/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/models/optional_upload.rs.golden b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/models/optional_upload.rs.golden new file mode 100644 index 000000000..d0a8e3123 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/models/optional_upload.rs.golden @@ -0,0 +1,21 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OptionalUpload { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub attributes: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub enabled: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub file: Option>, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub retry_count: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub title: Option, +} diff --git a/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/models/text_fields.rs.golden b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/models/text_fields.rs.golden new file mode 100644 index 000000000..3065d4d48 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/models/text_fields.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TextFields { + pub enabled: bool, + pub note: String, + pub retry_count: i64, +} diff --git a/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/runtime/auth.rs.golden b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/runtime/auth.rs.golden new file mode 100644 index 000000000..44ec63dae --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/runtime/auth.rs.golden @@ -0,0 +1,180 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use std::sync::Arc; + +use crate::runtime::error::Error; + +/// Trait for authenticating requests. +pub trait Authenticator: Send + Sync + std::fmt::Debug { + /// Apply authentication to the request builder. + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilder<'a, R>, + ) -> Result, Error>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilder<'a, R>, + ) -> Result, Error> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + Ok(req.bearer_auth(&token)) + } +} + +/// API key authentication. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilder<'a, R>, + ) -> Result, Error> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + Ok(req.header_str(&self.header_name, &key)?) + } +} diff --git a/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/runtime/client.rs.golden b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/runtime/client.rs.golden new file mode 100644 index 000000000..6bd5255ea --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/runtime/client.rs.golden @@ -0,0 +1,112 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use std::fmt; +use std::sync::Arc; + +use aioduct::Runtime; + +use crate::runtime::auth::Authenticator; +use crate::runtime::error::Error; + +/// Async HTTP client wrapping `aioduct::Client`. +pub struct Client { + inner: aioduct::Client, + base_url: String, + authenticator: Option>>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: aioduct::Client::::with_rustls(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Create a client with a custom `aioduct::Client`. + pub fn with_client(inner: aioduct::Client, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Attach an authenticator to the client. + pub fn with_auth(mut self, auth: Arc>) -> Self { + self.authenticator = Some(auth); + self + } + + fn full_url(&self, path: &str) -> String { + format!("{}{}", self.base_url, path) + } + + /// Build a GET request. + pub fn get(&self, path: &str) -> Result, Error> { + let mut req = self.inner.get(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a POST request. + pub fn post(&self, path: &str) -> Result, Error> { + let mut req = self.inner.post(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a PUT request. + pub fn put(&self, path: &str) -> Result, Error> { + let mut req = self.inner.put(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a DELETE request. + pub fn delete(&self, path: &str) -> Result, Error> { + let mut req = self.inner.delete(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a PATCH request. + pub fn patch(&self, path: &str) -> Result, Error> { + let mut req = self.inner.patch(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a HEAD request. + pub fn head(&self, path: &str) -> Result, Error> { + let mut req = self.inner.head(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } +} + +impl fmt::Debug for Client { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Client") + .field("base_url", &self.base_url) + .finish() + } +} diff --git a/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/runtime/error.rs.golden b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/runtime/error.rs.golden new file mode 100644 index 000000000..4a0a4327f --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/runtime/error.rs.golden @@ -0,0 +1,60 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use std::fmt; + +/// Error types for the generated SDK. +#[derive(Debug)] +pub enum Error { + /// HTTP/transport error from aioduct. + Http(aioduct::Error), + /// JSON deserialization error. + Deserialize(serde_json::Error), + /// XML serialization error. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Http(e) => write!(f, "HTTP error: {e}"), + Error::Deserialize(e) => write!(f, "Deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Http(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: aioduct::Error) -> Self { + Error::Http(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} diff --git a/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/runtime/mod.rs.golden b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/runtime/mod.rs.golden new file mode 100644 index 000000000..ef7165a41 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-edge-cases/src/runtime/mod.rs.golden @@ -0,0 +1,9 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/Cargo.toml.golden b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/Cargo.toml.golden new file mode 100644 index 000000000..6c497981b --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/Cargo.toml.golden @@ -0,0 +1,12 @@ +[package] +name = "multipart-nested-object-parts" +version = "0.1.0" +edition = "2024" +description = "Covers multipart object parts whose wire names differ from ergonomic names." + +[dependencies] +aioduct = { version = "0.1.8", features = ["tokio", "rustls", "rustls-ring", "json"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" diff --git a/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/README.md.golden b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/README.md.golden new file mode 100644 index 000000000..2899b40d5 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Nested Object Parts + +Covers multipart object parts whose wire names differ from ergonomic names. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart-nested-object-parts`. diff --git a/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/apis/mod.rs.golden b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/apis/mod.rs.golden new file mode 100644 index 000000000..322280f3a --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/apis/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +mod multipart; +pub use multipart::*; diff --git a/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/apis/multipart.rs.golden b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/apis/multipart.rs.golden new file mode 100644 index 000000000..f74accae3 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/apis/multipart.rs.golden @@ -0,0 +1,44 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +use crate::runtime::client::Client; +use crate::runtime::error::Error; + +/// API operations under the "multipart" tag. +pub struct MultipartApi<'a, R: aioduct::Runtime> { + client: &'a Client, +} + +impl<'a, R: aioduct::Runtime> MultipartApi<'a, R> { + /// Create a new `MultipartApi` bound to the given client. + pub fn new(client: &'a Client) -> Self { + Self { + client, + } + } + + /// POST /multipart/nested-object + pub async fn send_nested_object_part( + &self, + body: &crate::models::NestedUpload, + ) -> Result { + let path = "/multipart/nested-object".to_string(); + let mut req = self.client.post(&path)?; + let mut multipart = aioduct::multipart::Multipart::new(); + multipart = multipart.file("file", "file", "application/octet-stream", body.file.clone()); + multipart = multipart.text("item_config", serde_json::to_string(&body.item_config)?); + req = req.multipart(multipart); + let resp = req.send().await?; + let status_code = resp.status().as_u16(); + Ok(SendNestedObjectPartResponse { status_code }) + } +} + +/// Response from `send_nested_object_part`. +#[derive(Debug)] +pub struct SendNestedObjectPartResponse { + pub status_code: u16, +} diff --git a/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/lib.rs.golden b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/lib.rs.golden new file mode 100644 index 000000000..acd1067bf --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/lib.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/models/item_config.rs.golden b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/models/item_config.rs.golden new file mode 100644 index 000000000..8f6e6a354 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/models/item_config.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ItemConfig { + pub display_name: String, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub retention_days: Option, +} diff --git a/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/models/mod.rs.golden b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/models/mod.rs.golden new file mode 100644 index 000000000..2f9637c82 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/models/mod.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +mod item_config; +pub use item_config::*; +mod nested_upload; +pub use nested_upload::*; diff --git a/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/models/nested_upload.rs.golden b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/models/nested_upload.rs.golden new file mode 100644 index 000000000..e0a525dde --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/models/nested_upload.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NestedUpload { + pub file: Vec, + pub item_config: super::ItemConfig, +} diff --git a/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/runtime/auth.rs.golden b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/runtime/auth.rs.golden new file mode 100644 index 000000000..300a9b444 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/runtime/auth.rs.golden @@ -0,0 +1,180 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +use std::sync::Arc; + +use crate::runtime::error::Error; + +/// Trait for authenticating requests. +pub trait Authenticator: Send + Sync + std::fmt::Debug { + /// Apply authentication to the request builder. + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilder<'a, R>, + ) -> Result, Error>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilder<'a, R>, + ) -> Result, Error> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + Ok(req.bearer_auth(&token)) + } +} + +/// API key authentication. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilder<'a, R>, + ) -> Result, Error> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + Ok(req.header_str(&self.header_name, &key)?) + } +} diff --git a/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/runtime/client.rs.golden b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/runtime/client.rs.golden new file mode 100644 index 000000000..24ff5605e --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/runtime/client.rs.golden @@ -0,0 +1,112 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +use std::fmt; +use std::sync::Arc; + +use aioduct::Runtime; + +use crate::runtime::auth::Authenticator; +use crate::runtime::error::Error; + +/// Async HTTP client wrapping `aioduct::Client`. +pub struct Client { + inner: aioduct::Client, + base_url: String, + authenticator: Option>>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: aioduct::Client::::with_rustls(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Create a client with a custom `aioduct::Client`. + pub fn with_client(inner: aioduct::Client, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Attach an authenticator to the client. + pub fn with_auth(mut self, auth: Arc>) -> Self { + self.authenticator = Some(auth); + self + } + + fn full_url(&self, path: &str) -> String { + format!("{}{}", self.base_url, path) + } + + /// Build a GET request. + pub fn get(&self, path: &str) -> Result, Error> { + let mut req = self.inner.get(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a POST request. + pub fn post(&self, path: &str) -> Result, Error> { + let mut req = self.inner.post(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a PUT request. + pub fn put(&self, path: &str) -> Result, Error> { + let mut req = self.inner.put(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a DELETE request. + pub fn delete(&self, path: &str) -> Result, Error> { + let mut req = self.inner.delete(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a PATCH request. + pub fn patch(&self, path: &str) -> Result, Error> { + let mut req = self.inner.patch(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a HEAD request. + pub fn head(&self, path: &str) -> Result, Error> { + let mut req = self.inner.head(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } +} + +impl fmt::Debug for Client { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Client") + .field("base_url", &self.base_url) + .finish() + } +} diff --git a/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/runtime/error.rs.golden b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/runtime/error.rs.golden new file mode 100644 index 000000000..f82d173f9 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/runtime/error.rs.golden @@ -0,0 +1,60 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +use std::fmt; + +/// Error types for the generated SDK. +#[derive(Debug)] +pub enum Error { + /// HTTP/transport error from aioduct. + Http(aioduct::Error), + /// JSON deserialization error. + Deserialize(serde_json::Error), + /// XML serialization error. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Http(e) => write!(f, "HTTP error: {e}"), + Error::Deserialize(e) => write!(f, "Deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Http(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: aioduct::Error) -> Self { + Error::Http(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} diff --git a/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/runtime/mod.rs.golden b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/runtime/mod.rs.golden new file mode 100644 index 000000000..61150544a --- /dev/null +++ b/tests/golden/rust/rust-aioduct/multipart-nested-object-parts/src/runtime/mod.rs.golden @@ -0,0 +1,9 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-reqwest/media-type-selection/Cargo.toml.golden b/tests/golden/rust/rust-reqwest/media-type-selection/Cargo.toml.golden new file mode 100644 index 000000000..8c9b4e946 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/media-type-selection/Cargo.toml.golden @@ -0,0 +1,12 @@ +[package] +name = "media-type-selection" +version = "0.1.0" +edition = "2024" +description = "Covers normalized media-type selection for requests and responses." + +[dependencies] +reqwest = { version = "0.12", features = ["json", "multipart"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" diff --git a/tests/golden/rust/rust-reqwest/media-type-selection/README.md.golden b/tests/golden/rust/rust-reqwest/media-type-selection/README.md.golden new file mode 100644 index 000000000..7a446d9d1 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/media-type-selection/README.md.golden @@ -0,0 +1,7 @@ +# Media Type Selection + +Covers normalized media-type selection for requests and responses. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `media-type-selection`. diff --git a/tests/golden/rust/rust-reqwest/media-type-selection/src/apis/media.rs.golden b/tests/golden/rust/rust-reqwest/media-type-selection/src/apis/media.rs.golden new file mode 100644 index 000000000..e714c812f --- /dev/null +++ b/tests/golden/rust/rust-reqwest/media-type-selection/src/apis/media.rs.golden @@ -0,0 +1,162 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +use crate::runtime::client::Client; +use crate::runtime::error::Error; + +/// API operations under the "media" tag. +pub struct MediaApi<'a> { + client: &'a Client, +} + +impl<'a> MediaApi<'a> { + /// Create a new `MediaApi` bound to the given client. + pub fn new(client: &'a Client) -> Self { + Self { + client, + } + } + + /// POST /request/json-vs-multipart + pub async fn send_json_preferred( + &self, + body: &crate::models::Payload, + ) -> Result { + let path = "/request/json-vs-multipart".to_string(); + let mut req = self.client.request(reqwest::Method::POST, &path).await?; + req = req.json(&body); + let resp = self.client.send(req).await?; + let status_code = resp.status().as_u16(); + Ok(SendJsonPreferredResponse { status_code }) + } + + /// POST /request/parameterized-multipart + pub async fn send_parameterized_multipart( + &self, + body: &crate::models::FileEnvelope, + ) -> Result { + let path = "/request/parameterized-multipart".to_string(); + let mut req = self.client.request(reqwest::Method::POST, &path).await?; + let mut multipart = reqwest::multipart::Form::new(); + multipart = multipart.part("file", reqwest::multipart::Part::bytes(body.file.clone()).file_name("file").mime_str("application/octet-stream").map_err(Error::Network)?); + if let Some(value) = &body.note { + multipart = multipart.text("note", value.to_string()); + } + req = req.multipart(multipart); + let resp = self.client.send(req).await?; + let status_code = resp.status().as_u16(); + Ok(SendParameterizedMultipartResponse { status_code }) + } + + /// PATCH /request/vendor-json + pub async fn send_vendor_json( + &self, + body: &crate::models::Payload, + ) -> Result { + let path = "/request/vendor-json".to_string(); + let mut req = self.client.request(reqwest::Method::PATCH, &path).await?; + req = req.json(&body); + let resp = self.client.send(req).await?; + let status_code = resp.status().as_u16(); + Ok(SendVendorJsonResponse { status_code }) + } + + /// GET /response/octet-before-xml + pub async fn get_octet_preferred( + &self, + ) -> Result { + let path = "/response/octet-before-xml".to_string(); + let req = self.client.request(reqwest::Method::GET, &path).await?; + let resp = self.client.send(req).await?; + let status_code = resp.status().as_u16(); + let body_bytes = resp.bytes().await.map_err(Error::Network)?; + let mut result = GetOctetPreferredResponse { status_code, data: None }; + match status_code { + 200 => { + result.data = Some(Ok::, Error>(body_bytes.to_vec())?); + } + _ => {} + } + Ok(result) + } + + /// GET /response/text-before-xml + pub async fn get_text_preferred( + &self, + ) -> Result { + let path = "/response/text-before-xml".to_string(); + let req = self.client.request(reqwest::Method::GET, &path).await?; + let resp = self.client.send(req).await?; + let status_code = resp.status().as_u16(); + let body_bytes = resp.bytes().await.map_err(Error::Network)?; + let mut result = GetTextPreferredResponse { status_code, data: None }; + match status_code { + 200 => { + result.data = Some(Ok::(String::from_utf8_lossy(&body_bytes).into_owned())?); + } + _ => {} + } + Ok(result) + } + + /// GET /response/vendor-json + pub async fn get_vendor_json( + &self, + ) -> Result { + let path = "/response/vendor-json".to_string(); + let req = self.client.request(reqwest::Method::GET, &path).await?; + let resp = self.client.send(req).await?; + let status_code = resp.status().as_u16(); + let body_bytes = resp.bytes().await.map_err(Error::Network)?; + let mut result = GetVendorJsonResponse { status_code, data: None }; + match status_code { + 200 => { + result.data = Some(serde_json::from_slice(&body_bytes).map_err(Error::Deserialize)?); + } + _ => {} + } + Ok(result) + } +} + +/// Response from `send_json_preferred`. +#[derive(Debug)] +pub struct SendJsonPreferredResponse { + pub status_code: u16, +} + +/// Response from `send_parameterized_multipart`. +#[derive(Debug)] +pub struct SendParameterizedMultipartResponse { + pub status_code: u16, +} + +/// Response from `send_vendor_json`. +#[derive(Debug)] +pub struct SendVendorJsonResponse { + pub status_code: u16, +} + +/// Response from `get_octet_preferred`. +#[derive(Debug)] +pub struct GetOctetPreferredResponse { + pub status_code: u16, + pub data: Option>, +} + +/// Response from `get_text_preferred`. +#[derive(Debug)] +pub struct GetTextPreferredResponse { + pub status_code: u16, + pub data: Option, +} + +/// Response from `get_vendor_json`. +#[derive(Debug)] +pub struct GetVendorJsonResponse { + pub status_code: u16, + pub data: Option, +} diff --git a/tests/golden/rust/rust-reqwest/media-type-selection/src/apis/mod.rs.golden b/tests/golden/rust/rust-reqwest/media-type-selection/src/apis/mod.rs.golden new file mode 100644 index 000000000..f5a75cd31 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/media-type-selection/src/apis/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +mod media; +pub use media::*; diff --git a/tests/golden/rust/rust-reqwest/media-type-selection/src/lib.rs.golden b/tests/golden/rust/rust-reqwest/media-type-selection/src/lib.rs.golden new file mode 100644 index 000000000..267625e15 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/media-type-selection/src/lib.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-reqwest/media-type-selection/src/models/file_envelope.rs.golden b/tests/golden/rust/rust-reqwest/media-type-selection/src/models/file_envelope.rs.golden new file mode 100644 index 000000000..5a1351a16 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/media-type-selection/src/models/file_envelope.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FileEnvelope { + pub file: Vec, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub note: Option, +} diff --git a/tests/golden/rust/rust-reqwest/media-type-selection/src/models/mod.rs.golden b/tests/golden/rust/rust-reqwest/media-type-selection/src/models/mod.rs.golden new file mode 100644 index 000000000..f9bb2e882 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/media-type-selection/src/models/mod.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +mod file_envelope; +pub use file_envelope::*; +mod payload; +pub use payload::*; diff --git a/tests/golden/rust/rust-reqwest/media-type-selection/src/models/payload.rs.golden b/tests/golden/rust/rust-reqwest/media-type-selection/src/models/payload.rs.golden new file mode 100644 index 000000000..8c4017aa8 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/media-type-selection/src/models/payload.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Payload { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub count: Option, + pub id: String, +} diff --git a/tests/golden/rust/rust-reqwest/media-type-selection/src/runtime/auth.rs.golden b/tests/golden/rust/rust-reqwest/media-type-selection/src/runtime/auth.rs.golden new file mode 100644 index 000000000..e0431221f --- /dev/null +++ b/tests/golden/rust/rust-reqwest/media-type-selection/src/runtime/auth.rs.golden @@ -0,0 +1,189 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +use std::sync::Arc; + +use reqwest::header::{HeaderMap, HeaderValue}; + +use crate::runtime::error::Error; + +/// Trait for authenticating HTTP requests. +pub trait Authenticator: Send + Sync + std::fmt::Debug { + /// Apply authentication to the given headers. + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + let value = HeaderValue::from_str(&format!("Bearer {}", token)) + .map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid auth header: {e}"), + })?; + headers.insert(reqwest::header::AUTHORIZATION, value); + Ok(()) + } +} + +/// API key authentication via header. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + let name = reqwest::header::HeaderName::from_bytes(self.header_name.as_bytes()) + .map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid header name: {e}"), + })?; + let value = HeaderValue::from_str(&key).map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid header value: {e}"), + })?; + headers.insert(name, value); + Ok(()) + } +} diff --git a/tests/golden/rust/rust-reqwest/media-type-selection/src/runtime/client.rs.golden b/tests/golden/rust/rust-reqwest/media-type-selection/src/runtime/client.rs.golden new file mode 100644 index 000000000..5ec680a2b --- /dev/null +++ b/tests/golden/rust/rust-reqwest/media-type-selection/src/runtime/client.rs.golden @@ -0,0 +1,73 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +use std::sync::Arc; + +use reqwest::header::HeaderMap; + +use crate::runtime::auth::Authenticator; +use crate::runtime::error::Error; + +/// HTTP client wrapper around reqwest. +#[derive(Debug, Clone)] +pub struct Client { + inner: reqwest::Client, + base_url: String, + authenticator: Option>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: reqwest::Client::new(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Create a new client with a custom reqwest::Client. + pub fn with_client(inner: reqwest::Client, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Set the authenticator for this client. + pub fn with_auth(mut self, auth: impl Authenticator + 'static) -> Self { + self.authenticator = Some(Arc::new(auth)); + self + } + + /// Build a request with authentication applied. + pub async fn request( + &self, + method: reqwest::Method, + path: &str, + ) -> Result { + let url = format!("{}{}", self.base_url, path); + let mut builder = self.inner.request(method, &url); + + if let Some(auth) = &self.authenticator { + let mut headers = HeaderMap::new(); + auth.authenticate(&mut headers)?; + builder = builder.headers(headers); + } + + Ok(builder) + } + + /// Send a request and return the response. + pub async fn send( + &self, + request: reqwest::RequestBuilder, + ) -> Result { + let response = request.send().await.map_err(Error::Network)?; + Ok(response) + } +} diff --git a/tests/golden/rust/rust-reqwest/media-type-selection/src/runtime/error.rs.golden b/tests/golden/rust/rust-reqwest/media-type-selection/src/runtime/error.rs.golden new file mode 100644 index 000000000..5f487218e --- /dev/null +++ b/tests/golden/rust/rust-reqwest/media-type-selection/src/runtime/error.rs.golden @@ -0,0 +1,65 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +/// Errors that can occur during API calls. +#[derive(Debug)] +pub enum Error { + /// Network-level error from reqwest. + Network(reqwest::Error), + /// API returned a non-success status code. + Api { + status: reqwest::StatusCode, + body: String, + }, + /// Failed to deserialize the response body. + Deserialize(serde_json::Error), + /// Failed to serialize the request body as XML. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Network(e) => write!(f, "network error: {e}"), + Error::Api { status, body } => write!(f, "API error {status}: {body}"), + Error::Deserialize(e) => write!(f, "deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Network(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Api { .. } => None, + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: reqwest::Error) -> Self { + Error::Network(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} diff --git a/tests/golden/rust/rust-reqwest/media-type-selection/src/runtime/mod.rs.golden b/tests/golden/rust/rust-reqwest/media-type-selection/src/runtime/mod.rs.golden new file mode 100644 index 000000000..31db0c2b8 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/media-type-selection/src/runtime/mod.rs.golden @@ -0,0 +1,9 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-reqwest/multipart-edge-cases/Cargo.toml.golden b/tests/golden/rust/rust-reqwest/multipart-edge-cases/Cargo.toml.golden new file mode 100644 index 000000000..bd4458bd0 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-edge-cases/Cargo.toml.golden @@ -0,0 +1,12 @@ +[package] +name = "multipart-edge-cases" +version = "0.1.0" +edition = "2024" +description = "Covers optional multipart bodies, optional parts, and text-only multipart fields." + +[dependencies] +reqwest = { version = "0.12", features = ["json", "multipart"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" diff --git a/tests/golden/rust/rust-reqwest/multipart-edge-cases/README.md.golden b/tests/golden/rust/rust-reqwest/multipart-edge-cases/README.md.golden new file mode 100644 index 000000000..61745a51d --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-edge-cases/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Edge Cases + +Covers optional multipart bodies, optional parts, and text-only multipart fields. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart-edge-cases`. diff --git a/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/apis/mod.rs.golden b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/apis/mod.rs.golden new file mode 100644 index 000000000..fa77c450c --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/apis/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +mod multipart; +pub use multipart::*; diff --git a/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/apis/multipart.rs.golden b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/apis/multipart.rs.golden new file mode 100644 index 000000000..1d182e7b7 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/apis/multipart.rs.golden @@ -0,0 +1,80 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use crate::runtime::client::Client; +use crate::runtime::error::Error; + +/// API operations under the "multipart" tag. +pub struct MultipartApi<'a> { + client: &'a Client, +} + +impl<'a> MultipartApi<'a> { + /// Create a new `MultipartApi` bound to the given client. + pub fn new(client: &'a Client) -> Self { + Self { + client, + } + } + + /// POST /multipart/optional + pub async fn send_optional_parts( + &self, + body: &crate::models::OptionalUpload, + ) -> Result { + let path = "/multipart/optional".to_string(); + let mut req = self.client.request(reqwest::Method::POST, &path).await?; + let mut multipart = reqwest::multipart::Form::new(); + if let Some(value) = &body.attributes { + multipart = multipart.text("attributes", serde_json::to_string(value)?); + } + if let Some(value) = &body.enabled { + multipart = multipart.text("enabled", value.to_string()); + } + if let Some(value) = &body.file { + multipart = multipart.part("file", reqwest::multipart::Part::bytes(value.clone()).file_name("file").mime_str("application/octet-stream").map_err(Error::Network)?); + } + if let Some(value) = &body.retry_count { + multipart = multipart.text("retry_count", value.to_string()); + } + if let Some(value) = &body.title { + multipart = multipart.text("title", value.to_string()); + } + req = req.multipart(multipart); + let resp = self.client.send(req).await?; + let status_code = resp.status().as_u16(); + Ok(SendOptionalPartsResponse { status_code }) + } + + /// POST /multipart/text-only + pub async fn send_text_fields( + &self, + body: &crate::models::TextFields, + ) -> Result { + let path = "/multipart/text-only".to_string(); + let mut req = self.client.request(reqwest::Method::POST, &path).await?; + let mut multipart = reqwest::multipart::Form::new(); + multipart = multipart.text("enabled", body.enabled.to_string()); + multipart = multipart.text("note", body.note.to_string()); + multipart = multipart.text("retry_count", body.retry_count.to_string()); + req = req.multipart(multipart); + let resp = self.client.send(req).await?; + let status_code = resp.status().as_u16(); + Ok(SendTextFieldsResponse { status_code }) + } +} + +/// Response from `send_optional_parts`. +#[derive(Debug)] +pub struct SendOptionalPartsResponse { + pub status_code: u16, +} + +/// Response from `send_text_fields`. +#[derive(Debug)] +pub struct SendTextFieldsResponse { + pub status_code: u16, +} diff --git a/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/lib.rs.golden b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/lib.rs.golden new file mode 100644 index 000000000..42a48e994 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/lib.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/models/attributes.rs.golden b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/models/attributes.rs.golden new file mode 100644 index 000000000..c6a12ac52 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/models/attributes.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Attributes { + pub label: String, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub priority: Option, +} diff --git a/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/models/mod.rs.golden b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/models/mod.rs.golden new file mode 100644 index 000000000..97c6c9a6b --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/models/mod.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +mod attributes; +pub use attributes::*; +mod optional_upload; +pub use optional_upload::*; +mod text_fields; +pub use text_fields::*; diff --git a/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/models/optional_upload.rs.golden b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/models/optional_upload.rs.golden new file mode 100644 index 000000000..d0a8e3123 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/models/optional_upload.rs.golden @@ -0,0 +1,21 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OptionalUpload { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub attributes: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub enabled: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub file: Option>, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub retry_count: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub title: Option, +} diff --git a/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/models/text_fields.rs.golden b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/models/text_fields.rs.golden new file mode 100644 index 000000000..3065d4d48 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/models/text_fields.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TextFields { + pub enabled: bool, + pub note: String, + pub retry_count: i64, +} diff --git a/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/runtime/auth.rs.golden b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/runtime/auth.rs.golden new file mode 100644 index 000000000..5815352b1 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/runtime/auth.rs.golden @@ -0,0 +1,189 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use std::sync::Arc; + +use reqwest::header::{HeaderMap, HeaderValue}; + +use crate::runtime::error::Error; + +/// Trait for authenticating HTTP requests. +pub trait Authenticator: Send + Sync + std::fmt::Debug { + /// Apply authentication to the given headers. + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + let value = HeaderValue::from_str(&format!("Bearer {}", token)) + .map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid auth header: {e}"), + })?; + headers.insert(reqwest::header::AUTHORIZATION, value); + Ok(()) + } +} + +/// API key authentication via header. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + let name = reqwest::header::HeaderName::from_bytes(self.header_name.as_bytes()) + .map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid header name: {e}"), + })?; + let value = HeaderValue::from_str(&key).map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid header value: {e}"), + })?; + headers.insert(name, value); + Ok(()) + } +} diff --git a/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/runtime/client.rs.golden b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/runtime/client.rs.golden new file mode 100644 index 000000000..159d0215b --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/runtime/client.rs.golden @@ -0,0 +1,73 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use std::sync::Arc; + +use reqwest::header::HeaderMap; + +use crate::runtime::auth::Authenticator; +use crate::runtime::error::Error; + +/// HTTP client wrapper around reqwest. +#[derive(Debug, Clone)] +pub struct Client { + inner: reqwest::Client, + base_url: String, + authenticator: Option>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: reqwest::Client::new(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Create a new client with a custom reqwest::Client. + pub fn with_client(inner: reqwest::Client, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Set the authenticator for this client. + pub fn with_auth(mut self, auth: impl Authenticator + 'static) -> Self { + self.authenticator = Some(Arc::new(auth)); + self + } + + /// Build a request with authentication applied. + pub async fn request( + &self, + method: reqwest::Method, + path: &str, + ) -> Result { + let url = format!("{}{}", self.base_url, path); + let mut builder = self.inner.request(method, &url); + + if let Some(auth) = &self.authenticator { + let mut headers = HeaderMap::new(); + auth.authenticate(&mut headers)?; + builder = builder.headers(headers); + } + + Ok(builder) + } + + /// Send a request and return the response. + pub async fn send( + &self, + request: reqwest::RequestBuilder, + ) -> Result { + let response = request.send().await.map_err(Error::Network)?; + Ok(response) + } +} diff --git a/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/runtime/error.rs.golden b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/runtime/error.rs.golden new file mode 100644 index 000000000..213797f00 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/runtime/error.rs.golden @@ -0,0 +1,65 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +/// Errors that can occur during API calls. +#[derive(Debug)] +pub enum Error { + /// Network-level error from reqwest. + Network(reqwest::Error), + /// API returned a non-success status code. + Api { + status: reqwest::StatusCode, + body: String, + }, + /// Failed to deserialize the response body. + Deserialize(serde_json::Error), + /// Failed to serialize the request body as XML. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Network(e) => write!(f, "network error: {e}"), + Error::Api { status, body } => write!(f, "API error {status}: {body}"), + Error::Deserialize(e) => write!(f, "deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Network(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Api { .. } => None, + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: reqwest::Error) -> Self { + Error::Network(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} diff --git a/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/runtime/mod.rs.golden b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/runtime/mod.rs.golden new file mode 100644 index 000000000..ef7165a41 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-edge-cases/src/runtime/mod.rs.golden @@ -0,0 +1,9 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/Cargo.toml.golden b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/Cargo.toml.golden new file mode 100644 index 000000000..4c2a59af9 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/Cargo.toml.golden @@ -0,0 +1,12 @@ +[package] +name = "multipart-nested-object-parts" +version = "0.1.0" +edition = "2024" +description = "Covers multipart object parts whose wire names differ from ergonomic names." + +[dependencies] +reqwest = { version = "0.12", features = ["json", "multipart"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" diff --git a/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/README.md.golden b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/README.md.golden new file mode 100644 index 000000000..2899b40d5 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Nested Object Parts + +Covers multipart object parts whose wire names differ from ergonomic names. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart-nested-object-parts`. diff --git a/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/apis/mod.rs.golden b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/apis/mod.rs.golden new file mode 100644 index 000000000..322280f3a --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/apis/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +mod multipart; +pub use multipart::*; diff --git a/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/apis/multipart.rs.golden b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/apis/multipart.rs.golden new file mode 100644 index 000000000..0740998ec --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/apis/multipart.rs.golden @@ -0,0 +1,44 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +use crate::runtime::client::Client; +use crate::runtime::error::Error; + +/// API operations under the "multipart" tag. +pub struct MultipartApi<'a> { + client: &'a Client, +} + +impl<'a> MultipartApi<'a> { + /// Create a new `MultipartApi` bound to the given client. + pub fn new(client: &'a Client) -> Self { + Self { + client, + } + } + + /// POST /multipart/nested-object + pub async fn send_nested_object_part( + &self, + body: &crate::models::NestedUpload, + ) -> Result { + let path = "/multipart/nested-object".to_string(); + let mut req = self.client.request(reqwest::Method::POST, &path).await?; + let mut multipart = reqwest::multipart::Form::new(); + multipart = multipart.part("file", reqwest::multipart::Part::bytes(body.file.clone()).file_name("file").mime_str("application/octet-stream").map_err(Error::Network)?); + multipart = multipart.text("item_config", serde_json::to_string(&body.item_config)?); + req = req.multipart(multipart); + let resp = self.client.send(req).await?; + let status_code = resp.status().as_u16(); + Ok(SendNestedObjectPartResponse { status_code }) + } +} + +/// Response from `send_nested_object_part`. +#[derive(Debug)] +pub struct SendNestedObjectPartResponse { + pub status_code: u16, +} diff --git a/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/lib.rs.golden b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/lib.rs.golden new file mode 100644 index 000000000..acd1067bf --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/lib.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/models/item_config.rs.golden b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/models/item_config.rs.golden new file mode 100644 index 000000000..8f6e6a354 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/models/item_config.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ItemConfig { + pub display_name: String, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub retention_days: Option, +} diff --git a/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/models/mod.rs.golden b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/models/mod.rs.golden new file mode 100644 index 000000000..2f9637c82 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/models/mod.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +mod item_config; +pub use item_config::*; +mod nested_upload; +pub use nested_upload::*; diff --git a/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/models/nested_upload.rs.golden b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/models/nested_upload.rs.golden new file mode 100644 index 000000000..e0a525dde --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/models/nested_upload.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NestedUpload { + pub file: Vec, + pub item_config: super::ItemConfig, +} diff --git a/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/runtime/auth.rs.golden b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/runtime/auth.rs.golden new file mode 100644 index 000000000..d73583dc0 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/runtime/auth.rs.golden @@ -0,0 +1,189 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +use std::sync::Arc; + +use reqwest::header::{HeaderMap, HeaderValue}; + +use crate::runtime::error::Error; + +/// Trait for authenticating HTTP requests. +pub trait Authenticator: Send + Sync + std::fmt::Debug { + /// Apply authentication to the given headers. + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + let value = HeaderValue::from_str(&format!("Bearer {}", token)) + .map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid auth header: {e}"), + })?; + headers.insert(reqwest::header::AUTHORIZATION, value); + Ok(()) + } +} + +/// API key authentication via header. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + let name = reqwest::header::HeaderName::from_bytes(self.header_name.as_bytes()) + .map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid header name: {e}"), + })?; + let value = HeaderValue::from_str(&key).map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid header value: {e}"), + })?; + headers.insert(name, value); + Ok(()) + } +} diff --git a/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/runtime/client.rs.golden b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/runtime/client.rs.golden new file mode 100644 index 000000000..528cc19a8 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/runtime/client.rs.golden @@ -0,0 +1,73 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +use std::sync::Arc; + +use reqwest::header::HeaderMap; + +use crate::runtime::auth::Authenticator; +use crate::runtime::error::Error; + +/// HTTP client wrapper around reqwest. +#[derive(Debug, Clone)] +pub struct Client { + inner: reqwest::Client, + base_url: String, + authenticator: Option>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: reqwest::Client::new(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Create a new client with a custom reqwest::Client. + pub fn with_client(inner: reqwest::Client, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Set the authenticator for this client. + pub fn with_auth(mut self, auth: impl Authenticator + 'static) -> Self { + self.authenticator = Some(Arc::new(auth)); + self + } + + /// Build a request with authentication applied. + pub async fn request( + &self, + method: reqwest::Method, + path: &str, + ) -> Result { + let url = format!("{}{}", self.base_url, path); + let mut builder = self.inner.request(method, &url); + + if let Some(auth) = &self.authenticator { + let mut headers = HeaderMap::new(); + auth.authenticate(&mut headers)?; + builder = builder.headers(headers); + } + + Ok(builder) + } + + /// Send a request and return the response. + pub async fn send( + &self, + request: reqwest::RequestBuilder, + ) -> Result { + let response = request.send().await.map_err(Error::Network)?; + Ok(response) + } +} diff --git a/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/runtime/error.rs.golden b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/runtime/error.rs.golden new file mode 100644 index 000000000..35a491a19 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/runtime/error.rs.golden @@ -0,0 +1,65 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +/// Errors that can occur during API calls. +#[derive(Debug)] +pub enum Error { + /// Network-level error from reqwest. + Network(reqwest::Error), + /// API returned a non-success status code. + Api { + status: reqwest::StatusCode, + body: String, + }, + /// Failed to deserialize the response body. + Deserialize(serde_json::Error), + /// Failed to serialize the request body as XML. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Network(e) => write!(f, "network error: {e}"), + Error::Api { status, body } => write!(f, "API error {status}: {body}"), + Error::Deserialize(e) => write!(f, "deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Network(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Api { .. } => None, + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: reqwest::Error) -> Self { + Error::Network(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} diff --git a/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/runtime/mod.rs.golden b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/runtime/mod.rs.golden new file mode 100644 index 000000000..61150544a --- /dev/null +++ b/tests/golden/rust/rust-reqwest/multipart-nested-object-parts/src/runtime/mod.rs.golden @@ -0,0 +1,9 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-ureq/media-type-selection/Cargo.toml.golden b/tests/golden/rust/rust-ureq/media-type-selection/Cargo.toml.golden new file mode 100644 index 000000000..4af40dc6a --- /dev/null +++ b/tests/golden/rust/rust-ureq/media-type-selection/Cargo.toml.golden @@ -0,0 +1,12 @@ +[package] +name = "media-type-selection" +version = "0.1.0" +edition = "2024" +description = "Covers normalized media-type selection for requests and responses." + +[dependencies] +ureq = { version = "3", features = ["json"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" diff --git a/tests/golden/rust/rust-ureq/media-type-selection/README.md.golden b/tests/golden/rust/rust-ureq/media-type-selection/README.md.golden new file mode 100644 index 000000000..7a446d9d1 --- /dev/null +++ b/tests/golden/rust/rust-ureq/media-type-selection/README.md.golden @@ -0,0 +1,7 @@ +# Media Type Selection + +Covers normalized media-type selection for requests and responses. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `media-type-selection`. diff --git a/tests/golden/rust/rust-ureq/media-type-selection/src/apis/media.rs.golden b/tests/golden/rust/rust-ureq/media-type-selection/src/apis/media.rs.golden new file mode 100644 index 000000000..1853146fc --- /dev/null +++ b/tests/golden/rust/rust-ureq/media-type-selection/src/apis/media.rs.golden @@ -0,0 +1,168 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +use crate::runtime::client::Client; +use crate::runtime::error::Error; + +/// API operations under the "media" tag. +pub struct MediaApi<'a> { + client: &'a Client, +} + +impl<'a> MediaApi<'a> { + /// Create a new `MediaApi` bound to the given client. + pub fn new(client: &'a Client) -> Self { + Self { + client, + } + } + + /// POST /request/json-vs-multipart + pub fn send_json_preferred( + &self, + body: &crate::models::Payload, + ) -> Result { + let path = "/request/json-vs-multipart".to_string(); + let req = self.client.post(&path); + let resp = req.send_json(&body)?; + let status_code = resp.status().as_u16(); + Ok(SendJsonPreferredResponse { status_code }) + } + + /// POST /request/parameterized-multipart + pub fn send_parameterized_multipart( + &self, + body: &crate::models::FileEnvelope, + ) -> Result { + let path = "/request/parameterized-multipart".to_string(); + let mut req = self.client.post(&path); + let boundary = format!("openapi-nexus-{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|duration| duration.as_nanos()).unwrap_or(0)); + let mut multipart_body: Vec = Vec::new(); + multipart_body.extend_from_slice(format!("--{}\r\n", boundary).as_bytes()); + multipart_body.extend_from_slice(format!("Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\nContent-Type: application/octet-stream\r\n\r\n", "file", "file").as_bytes()); + multipart_body.extend_from_slice(&body.file.clone()); + multipart_body.extend_from_slice(b"\r\n"); + if let Some(value) = &body.note { + multipart_body.extend_from_slice(format!("--{}\r\n", boundary).as_bytes()); + multipart_body.extend_from_slice(format!("Content-Disposition: form-data; name=\"{}\"\r\n\r\n", "note").as_bytes()); + multipart_body.extend_from_slice(value.to_string().as_bytes()); + multipart_body.extend_from_slice(b"\r\n"); + } + multipart_body.extend_from_slice(format!("--{}--\r\n", boundary).as_bytes()); + req = req.header("Content-Type", &format!("multipart/form-data; boundary={}", boundary)); + let resp = req.send(multipart_body)?; + let status_code = resp.status().as_u16(); + Ok(SendParameterizedMultipartResponse { status_code }) + } + + /// PATCH /request/vendor-json + pub fn send_vendor_json( + &self, + body: &crate::models::Payload, + ) -> Result { + let path = "/request/vendor-json".to_string(); + let req = self.client.patch(&path); + let resp = req.send_json(&body)?; + let status_code = resp.status().as_u16(); + Ok(SendVendorJsonResponse { status_code }) + } + + /// GET /response/octet-before-xml + pub fn get_octet_preferred( + &self, + ) -> Result { + let path = "/response/octet-before-xml".to_string(); + let req = self.client.get(&path); + let resp = req.call()?; + let status_code = resp.status().as_u16(); + let body_bytes = resp.into_body().read_to_vec()?; + let mut result = GetOctetPreferredResponse { status_code, data: None }; + match status_code { + 200 => { + result.data = Some(Ok::, Error>(body_bytes.as_slice().to_vec())?); + } + _ => {} + } + Ok(result) + } + + /// GET /response/text-before-xml + pub fn get_text_preferred( + &self, + ) -> Result { + let path = "/response/text-before-xml".to_string(); + let req = self.client.get(&path); + let resp = req.call()?; + let status_code = resp.status().as_u16(); + let body_bytes = resp.into_body().read_to_vec()?; + let mut result = GetTextPreferredResponse { status_code, data: None }; + match status_code { + 200 => { + result.data = Some(Ok::(String::from_utf8_lossy(body_bytes.as_slice()).into_owned())?); + } + _ => {} + } + Ok(result) + } + + /// GET /response/vendor-json + pub fn get_vendor_json( + &self, + ) -> Result { + let path = "/response/vendor-json".to_string(); + let req = self.client.get(&path); + let resp = req.call()?; + let status_code = resp.status().as_u16(); + let body_bytes = resp.into_body().read_to_vec()?; + let mut result = GetVendorJsonResponse { status_code, data: None }; + match status_code { + 200 => { + result.data = Some(serde_json::from_slice(body_bytes.as_slice()).map_err(Error::Deserialize)?); + } + _ => {} + } + Ok(result) + } +} + +/// Response from `send_json_preferred`. +#[derive(Debug)] +pub struct SendJsonPreferredResponse { + pub status_code: u16, +} + +/// Response from `send_parameterized_multipart`. +#[derive(Debug)] +pub struct SendParameterizedMultipartResponse { + pub status_code: u16, +} + +/// Response from `send_vendor_json`. +#[derive(Debug)] +pub struct SendVendorJsonResponse { + pub status_code: u16, +} + +/// Response from `get_octet_preferred`. +#[derive(Debug)] +pub struct GetOctetPreferredResponse { + pub status_code: u16, + pub data: Option>, +} + +/// Response from `get_text_preferred`. +#[derive(Debug)] +pub struct GetTextPreferredResponse { + pub status_code: u16, + pub data: Option, +} + +/// Response from `get_vendor_json`. +#[derive(Debug)] +pub struct GetVendorJsonResponse { + pub status_code: u16, + pub data: Option, +} diff --git a/tests/golden/rust/rust-ureq/media-type-selection/src/apis/mod.rs.golden b/tests/golden/rust/rust-ureq/media-type-selection/src/apis/mod.rs.golden new file mode 100644 index 000000000..f5a75cd31 --- /dev/null +++ b/tests/golden/rust/rust-ureq/media-type-selection/src/apis/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +mod media; +pub use media::*; diff --git a/tests/golden/rust/rust-ureq/media-type-selection/src/lib.rs.golden b/tests/golden/rust/rust-ureq/media-type-selection/src/lib.rs.golden new file mode 100644 index 000000000..267625e15 --- /dev/null +++ b/tests/golden/rust/rust-ureq/media-type-selection/src/lib.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-ureq/media-type-selection/src/models/file_envelope.rs.golden b/tests/golden/rust/rust-ureq/media-type-selection/src/models/file_envelope.rs.golden new file mode 100644 index 000000000..5a1351a16 --- /dev/null +++ b/tests/golden/rust/rust-ureq/media-type-selection/src/models/file_envelope.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FileEnvelope { + pub file: Vec, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub note: Option, +} diff --git a/tests/golden/rust/rust-ureq/media-type-selection/src/models/mod.rs.golden b/tests/golden/rust/rust-ureq/media-type-selection/src/models/mod.rs.golden new file mode 100644 index 000000000..f9bb2e882 --- /dev/null +++ b/tests/golden/rust/rust-ureq/media-type-selection/src/models/mod.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +mod file_envelope; +pub use file_envelope::*; +mod payload; +pub use payload::*; diff --git a/tests/golden/rust/rust-ureq/media-type-selection/src/models/payload.rs.golden b/tests/golden/rust/rust-ureq/media-type-selection/src/models/payload.rs.golden new file mode 100644 index 000000000..8c4017aa8 --- /dev/null +++ b/tests/golden/rust/rust-ureq/media-type-selection/src/models/payload.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Payload { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub count: Option, + pub id: String, +} diff --git a/tests/golden/rust/rust-ureq/media-type-selection/src/runtime/auth.rs.golden b/tests/golden/rust/rust-ureq/media-type-selection/src/runtime/auth.rs.golden new file mode 100644 index 000000000..0c700ecd4 --- /dev/null +++ b/tests/golden/rust/rust-ureq/media-type-selection/src/runtime/auth.rs.golden @@ -0,0 +1,169 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +use std::sync::Arc; + +/// Trait for authenticating requests. +pub trait Authenticator: Send + Sync + std::fmt::Debug { + /// Return header key-value pairs to apply to the request. + fn auth_headers(&self) -> Vec<(&str, String)>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn auth_headers(&self) -> Vec<(&str, String)> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + vec![("Authorization", format!("Bearer {}", token))] + } +} + +/// API key authentication. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn auth_headers(&self) -> Vec<(&str, String)> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + vec![(&self.header_name, key)] + } +} diff --git a/tests/golden/rust/rust-ureq/media-type-selection/src/runtime/client.rs.golden b/tests/golden/rust/rust-ureq/media-type-selection/src/runtime/client.rs.golden new file mode 100644 index 000000000..a6e04d47e --- /dev/null +++ b/tests/golden/rust/rust-ureq/media-type-selection/src/runtime/client.rs.golden @@ -0,0 +1,94 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +use std::fmt; +use std::sync::Arc; + +use crate::runtime::auth::Authenticator; + +/// Synchronous HTTP client wrapping `ureq::Agent`. +pub struct Client { + inner: ureq::Agent, + base_url: String, + authenticator: Option>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: ureq::Agent::new_with_defaults(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Create a client with a custom `ureq::Agent`. + pub fn with_agent(inner: ureq::Agent, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Attach an authenticator to the client. + pub fn with_auth(mut self, auth: Arc) -> Self { + self.authenticator = Some(auth); + self + } + + fn full_url(&self, path: &str) -> String { + format!("{}{}", self.base_url, path) + } + + fn apply_auth(&self, mut req: ureq::RequestBuilder) -> ureq::RequestBuilder { + if let Some(auth) = &self.authenticator { + for (key, value) in auth.auth_headers() { + req = req.header(key, &value); + } + } + req + } + + /// Build a GET request. + pub fn get(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.get(&self.full_url(path))) + } + + /// Build a POST request. + pub fn post(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.post(&self.full_url(path))) + } + + /// Build a PUT request. + pub fn put(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.put(&self.full_url(path))) + } + + /// Build a DELETE request. + pub fn delete(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.delete(&self.full_url(path))) + } + + /// Build a PATCH request. + pub fn patch(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.patch(&self.full_url(path))) + } + + /// Build a HEAD request. + pub fn head(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.head(&self.full_url(path))) + } +} + +impl fmt::Debug for Client { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Client") + .field("base_url", &self.base_url) + .finish() + } +} diff --git a/tests/golden/rust/rust-ureq/media-type-selection/src/runtime/error.rs.golden b/tests/golden/rust/rust-ureq/media-type-selection/src/runtime/error.rs.golden new file mode 100644 index 000000000..fc5872d6f --- /dev/null +++ b/tests/golden/rust/rust-ureq/media-type-selection/src/runtime/error.rs.golden @@ -0,0 +1,64 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +use std::fmt; + +/// Error types for the generated SDK. +#[derive(Debug)] +pub enum Error { + /// HTTP transport error. + Http(ureq::Error), + /// I/O error while reading response body. + Io(std::io::Error), + /// JSON deserialization error. + Deserialize(serde_json::Error), + /// XML serialization error. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Http(e) => write!(f, "HTTP error: {e}"), + Error::Io(e) => write!(f, "IO error: {e}"), + Error::Deserialize(e) => write!(f, "Deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Http(e) => Some(e), + Error::Io(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: ureq::Error) -> Self { + Error::Http(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} diff --git a/tests/golden/rust/rust-ureq/media-type-selection/src/runtime/mod.rs.golden b/tests/golden/rust/rust-ureq/media-type-selection/src/runtime/mod.rs.golden new file mode 100644 index 000000000..31db0c2b8 --- /dev/null +++ b/tests/golden/rust/rust-ureq/media-type-selection/src/runtime/mod.rs.golden @@ -0,0 +1,9 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Media Type Selection — 1.0.0 +// Covers normalized media-type selection for requests and responses. + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-ureq/multipart-edge-cases/Cargo.toml.golden b/tests/golden/rust/rust-ureq/multipart-edge-cases/Cargo.toml.golden new file mode 100644 index 000000000..d472cbab6 --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-edge-cases/Cargo.toml.golden @@ -0,0 +1,12 @@ +[package] +name = "multipart-edge-cases" +version = "0.1.0" +edition = "2024" +description = "Covers optional multipart bodies, optional parts, and text-only multipart fields." + +[dependencies] +ureq = { version = "3", features = ["json"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" diff --git a/tests/golden/rust/rust-ureq/multipart-edge-cases/README.md.golden b/tests/golden/rust/rust-ureq/multipart-edge-cases/README.md.golden new file mode 100644 index 000000000..61745a51d --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-edge-cases/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Edge Cases + +Covers optional multipart bodies, optional parts, and text-only multipart fields. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart-edge-cases`. diff --git a/tests/golden/rust/rust-ureq/multipart-edge-cases/src/apis/mod.rs.golden b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/apis/mod.rs.golden new file mode 100644 index 000000000..fa77c450c --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/apis/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +mod multipart; +pub use multipart::*; diff --git a/tests/golden/rust/rust-ureq/multipart-edge-cases/src/apis/multipart.rs.golden b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/apis/multipart.rs.golden new file mode 100644 index 000000000..fdbf66440 --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/apis/multipart.rs.golden @@ -0,0 +1,108 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use crate::runtime::client::Client; +use crate::runtime::error::Error; + +/// API operations under the "multipart" tag. +pub struct MultipartApi<'a> { + client: &'a Client, +} + +impl<'a> MultipartApi<'a> { + /// Create a new `MultipartApi` bound to the given client. + pub fn new(client: &'a Client) -> Self { + Self { + client, + } + } + + /// POST /multipart/optional + pub fn send_optional_parts( + &self, + body: &crate::models::OptionalUpload, + ) -> Result { + let path = "/multipart/optional".to_string(); + let mut req = self.client.post(&path); + let boundary = format!("openapi-nexus-{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|duration| duration.as_nanos()).unwrap_or(0)); + let mut multipart_body: Vec = Vec::new(); + if let Some(value) = &body.attributes { + multipart_body.extend_from_slice(format!("--{}\r\n", boundary).as_bytes()); + multipart_body.extend_from_slice(format!("Content-Disposition: form-data; name=\"{}\"\r\n\r\n", "attributes").as_bytes()); + multipart_body.extend_from_slice(serde_json::to_string(value)?.as_bytes()); + multipart_body.extend_from_slice(b"\r\n"); + } + if let Some(value) = &body.enabled { + multipart_body.extend_from_slice(format!("--{}\r\n", boundary).as_bytes()); + multipart_body.extend_from_slice(format!("Content-Disposition: form-data; name=\"{}\"\r\n\r\n", "enabled").as_bytes()); + multipart_body.extend_from_slice(value.to_string().as_bytes()); + multipart_body.extend_from_slice(b"\r\n"); + } + if let Some(value) = &body.file { + multipart_body.extend_from_slice(format!("--{}\r\n", boundary).as_bytes()); + multipart_body.extend_from_slice(format!("Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\nContent-Type: application/octet-stream\r\n\r\n", "file", "file").as_bytes()); + multipart_body.extend_from_slice(&value.clone()); + multipart_body.extend_from_slice(b"\r\n"); + } + if let Some(value) = &body.retry_count { + multipart_body.extend_from_slice(format!("--{}\r\n", boundary).as_bytes()); + multipart_body.extend_from_slice(format!("Content-Disposition: form-data; name=\"{}\"\r\n\r\n", "retry_count").as_bytes()); + multipart_body.extend_from_slice(value.to_string().as_bytes()); + multipart_body.extend_from_slice(b"\r\n"); + } + if let Some(value) = &body.title { + multipart_body.extend_from_slice(format!("--{}\r\n", boundary).as_bytes()); + multipart_body.extend_from_slice(format!("Content-Disposition: form-data; name=\"{}\"\r\n\r\n", "title").as_bytes()); + multipart_body.extend_from_slice(value.to_string().as_bytes()); + multipart_body.extend_from_slice(b"\r\n"); + } + multipart_body.extend_from_slice(format!("--{}--\r\n", boundary).as_bytes()); + req = req.header("Content-Type", &format!("multipart/form-data; boundary={}", boundary)); + let resp = req.send(multipart_body)?; + let status_code = resp.status().as_u16(); + Ok(SendOptionalPartsResponse { status_code }) + } + + /// POST /multipart/text-only + pub fn send_text_fields( + &self, + body: &crate::models::TextFields, + ) -> Result { + let path = "/multipart/text-only".to_string(); + let mut req = self.client.post(&path); + let boundary = format!("openapi-nexus-{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|duration| duration.as_nanos()).unwrap_or(0)); + let mut multipart_body: Vec = Vec::new(); + multipart_body.extend_from_slice(format!("--{}\r\n", boundary).as_bytes()); + multipart_body.extend_from_slice(format!("Content-Disposition: form-data; name=\"{}\"\r\n\r\n", "enabled").as_bytes()); + multipart_body.extend_from_slice(body.enabled.to_string().as_bytes()); + multipart_body.extend_from_slice(b"\r\n"); + multipart_body.extend_from_slice(format!("--{}\r\n", boundary).as_bytes()); + multipart_body.extend_from_slice(format!("Content-Disposition: form-data; name=\"{}\"\r\n\r\n", "note").as_bytes()); + multipart_body.extend_from_slice(body.note.to_string().as_bytes()); + multipart_body.extend_from_slice(b"\r\n"); + multipart_body.extend_from_slice(format!("--{}\r\n", boundary).as_bytes()); + multipart_body.extend_from_slice(format!("Content-Disposition: form-data; name=\"{}\"\r\n\r\n", "retry_count").as_bytes()); + multipart_body.extend_from_slice(body.retry_count.to_string().as_bytes()); + multipart_body.extend_from_slice(b"\r\n"); + multipart_body.extend_from_slice(format!("--{}--\r\n", boundary).as_bytes()); + req = req.header("Content-Type", &format!("multipart/form-data; boundary={}", boundary)); + let resp = req.send(multipart_body)?; + let status_code = resp.status().as_u16(); + Ok(SendTextFieldsResponse { status_code }) + } +} + +/// Response from `send_optional_parts`. +#[derive(Debug)] +pub struct SendOptionalPartsResponse { + pub status_code: u16, +} + +/// Response from `send_text_fields`. +#[derive(Debug)] +pub struct SendTextFieldsResponse { + pub status_code: u16, +} diff --git a/tests/golden/rust/rust-ureq/multipart-edge-cases/src/lib.rs.golden b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/lib.rs.golden new file mode 100644 index 000000000..42a48e994 --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/lib.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-ureq/multipart-edge-cases/src/models/attributes.rs.golden b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/models/attributes.rs.golden new file mode 100644 index 000000000..c6a12ac52 --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/models/attributes.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Attributes { + pub label: String, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub priority: Option, +} diff --git a/tests/golden/rust/rust-ureq/multipart-edge-cases/src/models/mod.rs.golden b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/models/mod.rs.golden new file mode 100644 index 000000000..97c6c9a6b --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/models/mod.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +mod attributes; +pub use attributes::*; +mod optional_upload; +pub use optional_upload::*; +mod text_fields; +pub use text_fields::*; diff --git a/tests/golden/rust/rust-ureq/multipart-edge-cases/src/models/optional_upload.rs.golden b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/models/optional_upload.rs.golden new file mode 100644 index 000000000..d0a8e3123 --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/models/optional_upload.rs.golden @@ -0,0 +1,21 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OptionalUpload { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub attributes: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub enabled: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub file: Option>, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub retry_count: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub title: Option, +} diff --git a/tests/golden/rust/rust-ureq/multipart-edge-cases/src/models/text_fields.rs.golden b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/models/text_fields.rs.golden new file mode 100644 index 000000000..3065d4d48 --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/models/text_fields.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TextFields { + pub enabled: bool, + pub note: String, + pub retry_count: i64, +} diff --git a/tests/golden/rust/rust-ureq/multipart-edge-cases/src/runtime/auth.rs.golden b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/runtime/auth.rs.golden new file mode 100644 index 000000000..74bd1b1ec --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/runtime/auth.rs.golden @@ -0,0 +1,169 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use std::sync::Arc; + +/// Trait for authenticating requests. +pub trait Authenticator: Send + Sync + std::fmt::Debug { + /// Return header key-value pairs to apply to the request. + fn auth_headers(&self) -> Vec<(&str, String)>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn auth_headers(&self) -> Vec<(&str, String)> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + vec![("Authorization", format!("Bearer {}", token))] + } +} + +/// API key authentication. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn auth_headers(&self) -> Vec<(&str, String)> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + vec![(&self.header_name, key)] + } +} diff --git a/tests/golden/rust/rust-ureq/multipart-edge-cases/src/runtime/client.rs.golden b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/runtime/client.rs.golden new file mode 100644 index 000000000..dbe43ecb2 --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/runtime/client.rs.golden @@ -0,0 +1,94 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use std::fmt; +use std::sync::Arc; + +use crate::runtime::auth::Authenticator; + +/// Synchronous HTTP client wrapping `ureq::Agent`. +pub struct Client { + inner: ureq::Agent, + base_url: String, + authenticator: Option>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: ureq::Agent::new_with_defaults(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Create a client with a custom `ureq::Agent`. + pub fn with_agent(inner: ureq::Agent, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Attach an authenticator to the client. + pub fn with_auth(mut self, auth: Arc) -> Self { + self.authenticator = Some(auth); + self + } + + fn full_url(&self, path: &str) -> String { + format!("{}{}", self.base_url, path) + } + + fn apply_auth(&self, mut req: ureq::RequestBuilder) -> ureq::RequestBuilder { + if let Some(auth) = &self.authenticator { + for (key, value) in auth.auth_headers() { + req = req.header(key, &value); + } + } + req + } + + /// Build a GET request. + pub fn get(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.get(&self.full_url(path))) + } + + /// Build a POST request. + pub fn post(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.post(&self.full_url(path))) + } + + /// Build a PUT request. + pub fn put(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.put(&self.full_url(path))) + } + + /// Build a DELETE request. + pub fn delete(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.delete(&self.full_url(path))) + } + + /// Build a PATCH request. + pub fn patch(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.patch(&self.full_url(path))) + } + + /// Build a HEAD request. + pub fn head(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.head(&self.full_url(path))) + } +} + +impl fmt::Debug for Client { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Client") + .field("base_url", &self.base_url) + .finish() + } +} diff --git a/tests/golden/rust/rust-ureq/multipart-edge-cases/src/runtime/error.rs.golden b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/runtime/error.rs.golden new file mode 100644 index 000000000..d117d1e03 --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/runtime/error.rs.golden @@ -0,0 +1,64 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +use std::fmt; + +/// Error types for the generated SDK. +#[derive(Debug)] +pub enum Error { + /// HTTP transport error. + Http(ureq::Error), + /// I/O error while reading response body. + Io(std::io::Error), + /// JSON deserialization error. + Deserialize(serde_json::Error), + /// XML serialization error. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Http(e) => write!(f, "HTTP error: {e}"), + Error::Io(e) => write!(f, "IO error: {e}"), + Error::Deserialize(e) => write!(f, "Deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Http(e) => Some(e), + Error::Io(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: ureq::Error) -> Self { + Error::Http(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} diff --git a/tests/golden/rust/rust-ureq/multipart-edge-cases/src/runtime/mod.rs.golden b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/runtime/mod.rs.golden new file mode 100644 index 000000000..ef7165a41 --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-edge-cases/src/runtime/mod.rs.golden @@ -0,0 +1,9 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Edge Cases — 1.0.0 +// Covers optional multipart bodies, optional parts, and text-only multipart fields. + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-ureq/multipart-nested-object-parts/Cargo.toml.golden b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/Cargo.toml.golden new file mode 100644 index 000000000..814ef974a --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/Cargo.toml.golden @@ -0,0 +1,12 @@ +[package] +name = "multipart-nested-object-parts" +version = "0.1.0" +edition = "2024" +description = "Covers multipart object parts whose wire names differ from ergonomic names." + +[dependencies] +ureq = { version = "3", features = ["json"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" diff --git a/tests/golden/rust/rust-ureq/multipart-nested-object-parts/README.md.golden b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/README.md.golden new file mode 100644 index 000000000..2899b40d5 --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/README.md.golden @@ -0,0 +1,7 @@ +# Multipart Nested Object Parts + +Covers multipart object parts whose wire names differ from ergonomic names. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/adamcavendish/openapi-nexus) for `multipart-nested-object-parts`. diff --git a/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/apis/mod.rs.golden b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/apis/mod.rs.golden new file mode 100644 index 000000000..322280f3a --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/apis/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +mod multipart; +pub use multipart::*; diff --git a/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/apis/multipart.rs.golden b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/apis/multipart.rs.golden new file mode 100644 index 000000000..a93584d6c --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/apis/multipart.rs.golden @@ -0,0 +1,52 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +use crate::runtime::client::Client; +use crate::runtime::error::Error; + +/// API operations under the "multipart" tag. +pub struct MultipartApi<'a> { + client: &'a Client, +} + +impl<'a> MultipartApi<'a> { + /// Create a new `MultipartApi` bound to the given client. + pub fn new(client: &'a Client) -> Self { + Self { + client, + } + } + + /// POST /multipart/nested-object + pub fn send_nested_object_part( + &self, + body: &crate::models::NestedUpload, + ) -> Result { + let path = "/multipart/nested-object".to_string(); + let mut req = self.client.post(&path); + let boundary = format!("openapi-nexus-{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|duration| duration.as_nanos()).unwrap_or(0)); + let mut multipart_body: Vec = Vec::new(); + multipart_body.extend_from_slice(format!("--{}\r\n", boundary).as_bytes()); + multipart_body.extend_from_slice(format!("Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\nContent-Type: application/octet-stream\r\n\r\n", "file", "file").as_bytes()); + multipart_body.extend_from_slice(&body.file.clone()); + multipart_body.extend_from_slice(b"\r\n"); + multipart_body.extend_from_slice(format!("--{}\r\n", boundary).as_bytes()); + multipart_body.extend_from_slice(format!("Content-Disposition: form-data; name=\"{}\"\r\n\r\n", "item_config").as_bytes()); + multipart_body.extend_from_slice(serde_json::to_string(&body.item_config)?.as_bytes()); + multipart_body.extend_from_slice(b"\r\n"); + multipart_body.extend_from_slice(format!("--{}--\r\n", boundary).as_bytes()); + req = req.header("Content-Type", &format!("multipart/form-data; boundary={}", boundary)); + let resp = req.send(multipart_body)?; + let status_code = resp.status().as_u16(); + Ok(SendNestedObjectPartResponse { status_code }) + } +} + +/// Response from `send_nested_object_part`. +#[derive(Debug)] +pub struct SendNestedObjectPartResponse { + pub status_code: u16, +} diff --git a/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/lib.rs.golden b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/lib.rs.golden new file mode 100644 index 000000000..acd1067bf --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/lib.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/models/item_config.rs.golden b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/models/item_config.rs.golden new file mode 100644 index 000000000..8f6e6a354 --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/models/item_config.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ItemConfig { + pub display_name: String, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub retention_days: Option, +} diff --git a/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/models/mod.rs.golden b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/models/mod.rs.golden new file mode 100644 index 000000000..2f9637c82 --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/models/mod.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +mod item_config; +pub use item_config::*; +mod nested_upload; +pub use nested_upload::*; diff --git a/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/models/nested_upload.rs.golden b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/models/nested_upload.rs.golden new file mode 100644 index 000000000..e0a525dde --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/models/nested_upload.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NestedUpload { + pub file: Vec, + pub item_config: super::ItemConfig, +} diff --git a/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/runtime/auth.rs.golden b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/runtime/auth.rs.golden new file mode 100644 index 000000000..8c12f1646 --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/runtime/auth.rs.golden @@ -0,0 +1,169 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +use std::sync::Arc; + +/// Trait for authenticating requests. +pub trait Authenticator: Send + Sync + std::fmt::Debug { + /// Return header key-value pairs to apply to the request. + fn auth_headers(&self) -> Vec<(&str, String)>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn auth_headers(&self) -> Vec<(&str, String)> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + vec![("Authorization", format!("Bearer {}", token))] + } +} + +/// API key authentication. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn auth_headers(&self) -> Vec<(&str, String)> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + vec![(&self.header_name, key)] + } +} diff --git a/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/runtime/client.rs.golden b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/runtime/client.rs.golden new file mode 100644 index 000000000..748e49fe3 --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/runtime/client.rs.golden @@ -0,0 +1,94 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +use std::fmt; +use std::sync::Arc; + +use crate::runtime::auth::Authenticator; + +/// Synchronous HTTP client wrapping `ureq::Agent`. +pub struct Client { + inner: ureq::Agent, + base_url: String, + authenticator: Option>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: ureq::Agent::new_with_defaults(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Create a client with a custom `ureq::Agent`. + pub fn with_agent(inner: ureq::Agent, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Attach an authenticator to the client. + pub fn with_auth(mut self, auth: Arc) -> Self { + self.authenticator = Some(auth); + self + } + + fn full_url(&self, path: &str) -> String { + format!("{}{}", self.base_url, path) + } + + fn apply_auth(&self, mut req: ureq::RequestBuilder) -> ureq::RequestBuilder { + if let Some(auth) = &self.authenticator { + for (key, value) in auth.auth_headers() { + req = req.header(key, &value); + } + } + req + } + + /// Build a GET request. + pub fn get(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.get(&self.full_url(path))) + } + + /// Build a POST request. + pub fn post(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.post(&self.full_url(path))) + } + + /// Build a PUT request. + pub fn put(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.put(&self.full_url(path))) + } + + /// Build a DELETE request. + pub fn delete(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.delete(&self.full_url(path))) + } + + /// Build a PATCH request. + pub fn patch(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.patch(&self.full_url(path))) + } + + /// Build a HEAD request. + pub fn head(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.head(&self.full_url(path))) + } +} + +impl fmt::Debug for Client { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Client") + .field("base_url", &self.base_url) + .finish() + } +} diff --git a/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/runtime/error.rs.golden b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/runtime/error.rs.golden new file mode 100644 index 000000000..e79fef14e --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/runtime/error.rs.golden @@ -0,0 +1,64 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +use std::fmt; + +/// Error types for the generated SDK. +#[derive(Debug)] +pub enum Error { + /// HTTP transport error. + Http(ureq::Error), + /// I/O error while reading response body. + Io(std::io::Error), + /// JSON deserialization error. + Deserialize(serde_json::Error), + /// XML serialization error. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Http(e) => write!(f, "HTTP error: {e}"), + Error::Io(e) => write!(f, "IO error: {e}"), + Error::Deserialize(e) => write!(f, "Deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Http(e) => Some(e), + Error::Io(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: ureq::Error) -> Self { + Error::Http(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} diff --git a/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/runtime/mod.rs.golden b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/runtime/mod.rs.golden new file mode 100644 index 000000000..61150544a --- /dev/null +++ b/tests/golden/rust/rust-ureq/multipart-nested-object-parts/src/runtime/mod.rs.golden @@ -0,0 +1,9 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Multipart Nested Object Parts — 1.0.0 +// Covers multipart object parts whose wire names differ from ergonomic names. + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/typescript/typescript-fetch/additional-properties/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/additional-properties/runtime/runtime.ts.golden index 01945cf42..926145713 100644 --- a/tests/golden/typescript/typescript-fetch/additional-properties/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/additional-properties/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/binary-transfer-media-types/apis/TransferApi.ts.golden b/tests/golden/typescript/typescript-fetch/binary-transfer-media-types/apis/TransferApi.ts.golden index f1ccedc4f..95ca27666 100644 --- a/tests/golden/typescript/typescript-fetch/binary-transfer-media-types/apis/TransferApi.ts.golden +++ b/tests/golden/typescript/typescript-fetch/binary-transfer-media-types/apis/TransferApi.ts.golden @@ -101,11 +101,13 @@ export class TransferApi extends BaseAPI implements TransferApiInterface { const queryParameters: HTTPQuery = {}; // Build headers const headerParameters: Record = { - 'Content-Type': 'multipart/form-data', }; // Prepare request body - const requestBody = requestParameters.body; + const requestBody = new FormData(); + requestBody.append('file', requestParameters.body.file); + requestBody.append('metadata', JSON.stringify(requestParameters.body.metadata)); + requestBody.append('purpose', String(requestParameters.body.purpose)); // Make request const response = await this.request({ path: urlPath, diff --git a/tests/golden/typescript/typescript-fetch/binary-transfer-media-types/models/UploadAssetRequest.ts.golden b/tests/golden/typescript/typescript-fetch/binary-transfer-media-types/models/UploadAssetRequest.ts.golden index a61d90e18..6a571c410 100644 --- a/tests/golden/typescript/typescript-fetch/binary-transfer-media-types/models/UploadAssetRequest.ts.golden +++ b/tests/golden/typescript/typescript-fetch/binary-transfer-media-types/models/UploadAssetRequest.ts.golden @@ -10,7 +10,7 @@ export interface UploadAssetRequest { /** * Binary content */ - readonly file: string; + readonly file: Blob | File; readonly metadata: UploadAttributes; /** * Upload purpose diff --git a/tests/golden/typescript/typescript-fetch/binary-transfer-media-types/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/binary-transfer-media-types/runtime/runtime.ts.golden index 3319c55fc..d1de0494a 100644 --- a/tests/golden/typescript/typescript-fetch/binary-transfer-media-types/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/binary-transfer-media-types/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/comprehensive-schemas/models/BinaryString.ts.golden b/tests/golden/typescript/typescript-fetch/comprehensive-schemas/models/BinaryString.ts.golden index 866fc78f1..458ee098e 100644 --- a/tests/golden/typescript/typescript-fetch/comprehensive-schemas/models/BinaryString.ts.golden +++ b/tests/golden/typescript/typescript-fetch/comprehensive-schemas/models/BinaryString.ts.golden @@ -6,4 +6,4 @@ */ /** String with binary format */ -export type BinaryString = string; +export type BinaryString = Blob | File; diff --git a/tests/golden/typescript/typescript-fetch/comprehensive-schemas/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/comprehensive-schemas/runtime/runtime.ts.golden index 7d42b3934..f5de55d55 100644 --- a/tests/golden/typescript/typescript-fetch/comprehensive-schemas/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/comprehensive-schemas/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/delete-with-response-schema/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/delete-with-response-schema/runtime/runtime.ts.golden index 0c2aae43d..e76d1b637 100644 --- a/tests/golden/typescript/typescript-fetch/delete-with-response-schema/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/delete-with-response-schema/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/duplicate-param-names/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/duplicate-param-names/runtime/runtime.ts.golden index a43932cfe..159640f43 100644 --- a/tests/golden/typescript/typescript-fetch/duplicate-param-names/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/duplicate-param-names/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/enum-repr/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/enum-repr/runtime/runtime.ts.golden index 4c76003a4..df88c849e 100644 --- a/tests/golden/typescript/typescript-fetch/enum-repr/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/enum-repr/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/interface-with-enum-reference/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/interface-with-enum-reference/runtime/runtime.ts.golden index 1c8d117f9..025c8a969 100644 --- a/tests/golden/typescript/typescript-fetch/interface-with-enum-reference/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/interface-with-enum-reference/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/media-type-selection/README.md.golden b/tests/golden/typescript/typescript-fetch/media-type-selection/README.md.golden new file mode 100644 index 000000000..f1e97fbeb --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/media-type-selection/README.md.golden @@ -0,0 +1,248 @@ +# media-type-selection + +Covers normalized media-type selection for requests and responses. + +**Version:** 1.0.0 + +## Overview + +This package provides a TypeScript/JavaScript client for the Media Type Selection API. It uses the native [Fetch API](https://fetch.spec.whatwg.org/) for HTTP requests and works in both Node.js and browser environments. + +## Features + +- ✨ **Type-safe** - Full TypeScript support with generated types +- 🚀 **Modern** - Uses native Fetch API, no external HTTP dependencies +- 🔧 **Configurable** - Flexible configuration options +- 🎯 **Middleware** - Support for request/response interceptors +- 📦 **Tree-shakeable** - Import only what you need +- 🌐 **Universal** - Works in Node.js and browsers + +## Installation + +### From npm (published package) + +```bash +npm install media-type-selection +``` + +### From local path (development) + +Add the package to your `package.json` using the `file:` protocol: + +```json +{ + "dependencies": { + "media-type-selection": "file:../../path/to/generated/package" + } +} +``` + +Then run: + +```bash +npm install +``` + +## Quick Start + +```typescript +import { Configuration, MediaApi } from 'media-type-selection'; + +// Create a configuration +const config = new Configuration({ + basePath: 'https://api.example.com', + headers: { + 'Authorization': 'Bearer YOUR_TOKEN' + } +}); + +// Initialize the API client +const api = new MediaApi(config); + +// Make API calls +try { + const result = await api.someMethod(); + console.log(result); +} catch (error) { + console.error('API Error:', error); +} +``` + +## Configuration + +The `Configuration` class accepts the following options: + +```typescript +interface ConfigurationParameters { + /** Base URL for API requests */ + basePath?: string; + + /** Custom fetch implementation */ + fetchApi?: typeof fetch; + + /** Request/response middleware */ + middleware?: Middleware[]; + + /** Custom query string serializer */ + queryParamsStringify?: (params: HTTPQuery) => string; + + /** Default headers for all requests */ + headers?: Record; + + /** Credentials mode for requests */ + credentials?: RequestCredentials; +} +``` + +### Example with custom configuration + +```typescript +const config = new Configuration({ + basePath: 'https://api.example.com', + headers: { + 'X-API-Key': 'your-api-key', + 'Content-Type': 'application/json' + }, + credentials: 'include' +}); +``` + +## Middleware + +Add custom middleware to intercept requests and responses: + +```typescript +import { Configuration, Middleware } from 'media-type-selection'; + +const loggingMiddleware: Middleware = { + pre: async (context) => { + console.log('Request:', context.url); + return context; + }, + post: async (context) => { + console.log('Response:', context.response.status); + return context.response; + }, + onError: async (context) => { + console.error('Error:', context.error); + return undefined; + } +}; + +const config = new Configuration({ + basePath: 'https://api.example.com', + middleware: [loggingMiddleware] +}); +``` + +## Error Handling + +The client throws typed errors for different failure scenarios: + +```typescript +import { ResponseError, FetchError, RequiredError } from 'media-type-selection'; + +try { + const result = await api.someMethod(); +} catch (error) { + if (error instanceof ResponseError) { + // HTTP error response (4xx, 5xx) + console.error('HTTP Error:', error.response.status); + } else if (error instanceof FetchError) { + // Network or fetch error + console.error('Network Error:', error.cause); + } else if (error instanceof RequiredError) { + // Missing required parameter + console.error('Missing field:', error.field); + } +} +``` + +## API Reference + +This package exports the following: + +- **Configuration** - Client configuration class +- **BaseAPI** - Base class for all API clients +- **API Classes** - Generated API client classes (e.g., `UserApi`, `PostApi`) +- **Models** - Generated TypeScript interfaces for request/response types +- **Errors** - `ResponseError`, `FetchError`, `RequiredError` +- **Types** - TypeScript type definitions + +## Development + +### Building + +To build the package: + +```bash +npm install +npm run build +``` + +This will compile TypeScript to JavaScript in the `dist/` directory. + +### Building for ESM + +To build ES modules: + +```bash +npm run build +``` + +## TypeScript Support + +This package includes TypeScript type definitions. No additional `@types` package is needed. + +### TypeScript Configuration + +This package works with standard TypeScript configurations. If you're using a bundler-based setup, you may want to configure: + +```json +{ + "compilerOptions": { + "moduleResolution": "bundler" + } +} +``` + +### Type Imports + +```typescript +import type { User, CreateUserRequest } from 'media-type-selection'; + +const user: User = { + id: 1, + name: 'John Doe', + email: 'john@example.com' +}; +``` + +## Browser Support + +This package uses the native Fetch API, which is supported in: + +- Chrome 42+ +- Firefox 39+ +- Safari 10.1+ +- Edge 14+ +- Node.js 18+ (native fetch) +- Node.js <18 (with `node-fetch` polyfill) + +For older browsers, you may need to include a fetch polyfill. + +## License + +This is an auto-generated API client. Please refer to your API documentation for license information. + +## Support + +For issues related to the API itself, please contact the API provider. + +For issues with this generated client, please check the OpenAPI specification used to generate it. + +--- + +**Generated by OpenAPI Generator** + +API Version: 1.0.0 diff --git a/tests/golden/typescript/typescript-fetch/media-type-selection/apis/MediaApi.ts.golden b/tests/golden/typescript/typescript-fetch/media-type-selection/apis/MediaApi.ts.golden new file mode 100644 index 000000000..faed93c04 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/media-type-selection/apis/MediaApi.ts.golden @@ -0,0 +1,299 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Media Type Selection — 1.0.0 + * Covers normalized media-type selection for requests and responses. + */ +import type { FileEnvelope } from '../models/FileEnvelope'; +import type { Payload } from '../models/Payload'; +import type { Configuration, HTTPQuery, InitOverrideFunction } from '../runtime/runtime'; +import { BaseAPI, BlobApiResponse, DefaultConfig, JSONApiResponse, RequiredError, TextApiResponse, VoidApiResponse } from '../runtime/runtime'; + +export interface ApiSendJsonPreferredRequest { + body: Payload; +} + +export interface ApiSendParameterizedMultipartRequest { + body: FileEnvelope; +} + +export interface ApiSendVendorJsonRequest { + body: Payload; +} + +export type SendJsonPreferredRawResponse = + | VoidApiResponse & { status: 204 } + | VoidApiResponse & { status: number }; + +export type SendParameterizedMultipartRawResponse = + | VoidApiResponse & { status: 204 } + | VoidApiResponse & { status: number }; + +export type SendVendorJsonRawResponse = + | VoidApiResponse & { status: 204 } + | VoidApiResponse & { status: number }; + +export type GetOctetPreferredRawResponse = + | BlobApiResponse & { status: 200 } + | JSONApiResponse & { status: number }; + +export type GetTextPreferredRawResponse = + | TextApiResponse & { status: 200 } + | JSONApiResponse & { status: number }; + +export type GetVendorJsonRawResponse = + | JSONApiResponse & { status: 200 } + | JSONApiResponse & { status: number }; + + +export interface MediaApiInterface { + sendJsonPreferredRaw: (requestParameters: ApiSendJsonPreferredRequest, initOverrides?: RequestInit | InitOverrideFunction) => Promise; + sendJsonPreferred: (requestParameters: ApiSendJsonPreferredRequest, initOverrides?: RequestInit | InitOverrideFunction) => Promise; + sendParameterizedMultipartRaw: (requestParameters: ApiSendParameterizedMultipartRequest, initOverrides?: RequestInit | InitOverrideFunction) => Promise; + sendParameterizedMultipart: (requestParameters: ApiSendParameterizedMultipartRequest, initOverrides?: RequestInit | InitOverrideFunction) => Promise; + sendVendorJsonRaw: (requestParameters: ApiSendVendorJsonRequest, initOverrides?: RequestInit | InitOverrideFunction) => Promise; + sendVendorJson: (requestParameters: ApiSendVendorJsonRequest, initOverrides?: RequestInit | InitOverrideFunction) => Promise; + getOctetPreferredRaw: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + getOctetPreferred: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + getTextPreferredRaw: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + getTextPreferred: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + getVendorJsonRaw: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + getVendorJson: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; +} + +export class MediaApi extends BaseAPI implements MediaApiInterface { + /** + * Initialize the API client + */ + constructor(configuration?: Configuration) { + super(configuration ?? DefaultConfig); + } + + async sendJsonPreferredRaw(requestParameters: ApiSendJsonPreferredRequest, + initOverrides?: RequestInit | InitOverrideFunction): Promise { + if (requestParameters.body === undefined || requestParameters.body === null) { + throw new RequiredError( + 'body', + 'Required parameter "body" was null or undefined when calling sendJsonPreferredRaw().' + ); + } + // Build path with path parameters + const urlPath = `/request/json-vs-multipart`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + 'Content-Type': 'application/json', + }; + + // Prepare request body + const requestBody = requestParameters.body; + // Make request + const response = await this.request({ + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: requestBody, + }, initOverrides); + + // Handle responses + if (response.status === 204) { + return new VoidApiResponse(response) as VoidApiResponse & { status: 204 }; + } + else { + return new VoidApiResponse(response) as VoidApiResponse & { status: number }; + } + } + + async sendJsonPreferred(requestParameters: ApiSendJsonPreferredRequest, + initOverrides?: RequestInit | InitOverrideFunction): Promise { + const response = await this.sendJsonPreferredRaw(requestParameters, initOverrides); + return await response.value(); + } + + async sendParameterizedMultipartRaw(requestParameters: ApiSendParameterizedMultipartRequest, + initOverrides?: RequestInit | InitOverrideFunction): Promise { + if (requestParameters.body === undefined || requestParameters.body === null) { + throw new RequiredError( + 'body', + 'Required parameter "body" was null or undefined when calling sendParameterizedMultipartRaw().' + ); + } + // Build path with path parameters + const urlPath = `/request/parameterized-multipart`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + }; + + // Prepare request body + const requestBody = new FormData(); + requestBody.append('file', requestParameters.body.file); + if (requestParameters.body.note !== undefined && requestParameters.body.note !== null) { + requestBody.append('note', String(requestParameters.body.note)); + } + // Make request + const response = await this.request({ + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: requestBody, + }, initOverrides); + + // Handle responses + if (response.status === 204) { + return new VoidApiResponse(response) as VoidApiResponse & { status: 204 }; + } + else { + return new VoidApiResponse(response) as VoidApiResponse & { status: number }; + } + } + + async sendParameterizedMultipart(requestParameters: ApiSendParameterizedMultipartRequest, + initOverrides?: RequestInit | InitOverrideFunction): Promise { + const response = await this.sendParameterizedMultipartRaw(requestParameters, initOverrides); + return await response.value(); + } + + async sendVendorJsonRaw(requestParameters: ApiSendVendorJsonRequest, initOverrides?: RequestInit + | InitOverrideFunction): Promise { + if (requestParameters.body === undefined || requestParameters.body === null) { + throw new RequiredError( + 'body', + 'Required parameter "body" was null or undefined when calling sendVendorJsonRaw().' + ); + } + // Build path with path parameters + const urlPath = `/request/vendor-json`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + 'Content-Type': 'application/vnd.example+json; charset=utf-8', + }; + + // Prepare request body + const requestBody = requestParameters.body; + // Make request + const response = await this.request({ + path: urlPath, + method: 'PATCH', + headers: headerParameters, + query: queryParameters, + body: requestBody, + }, initOverrides); + + // Handle responses + if (response.status === 204) { + return new VoidApiResponse(response) as VoidApiResponse & { status: 204 }; + } + else { + return new VoidApiResponse(response) as VoidApiResponse & { status: number }; + } + } + + async sendVendorJson(requestParameters: ApiSendVendorJsonRequest, initOverrides?: RequestInit + | InitOverrideFunction): Promise { + const response = await this.sendVendorJsonRaw(requestParameters, initOverrides); + return await response.value(); + } + + async getOctetPreferredRaw(initOverrides?: RequestInit | InitOverrideFunction): Promise { + // Build path with path parameters + const urlPath = `/response/octet-before-xml`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + }; + + // Make request + const response = await this.request({ + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + body: undefined, + }, initOverrides); + + // Handle responses + if (response.status === 200) { + return new BlobApiResponse(response) as BlobApiResponse & { status: 200 }; + } + else { + return new JSONApiResponse(response) as JSONApiResponse & { status: number }; + } + } + + async getOctetPreferred(initOverrides?: RequestInit | InitOverrideFunction): Promise { + const response = await this.getOctetPreferredRaw(initOverrides); + return await response.value() as Blob; + } + + async getTextPreferredRaw(initOverrides?: RequestInit | InitOverrideFunction): Promise { + // Build path with path parameters + const urlPath = `/response/text-before-xml`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + }; + + // Make request + const response = await this.request({ + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + body: undefined, + }, initOverrides); + + // Handle responses + if (response.status === 200) { + return new TextApiResponse(response) as TextApiResponse & { status: 200 }; + } + else { + return new JSONApiResponse(response) as JSONApiResponse & { status: number }; + } + } + + async getTextPreferred(initOverrides?: RequestInit | InitOverrideFunction): Promise { + const response = await this.getTextPreferredRaw(initOverrides); + return await response.value() as string; + } + + async getVendorJsonRaw(initOverrides?: RequestInit | InitOverrideFunction): Promise { + // Build path with path parameters + const urlPath = `/response/vendor-json`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + }; + + // Make request + const response = await this.request({ + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + body: undefined, + }, initOverrides); + + // Handle responses + if (response.status === 200) { + return new JSONApiResponse(response) as JSONApiResponse & { status: 200 }; + } + else { + return new JSONApiResponse(response) as JSONApiResponse & { status: number }; + } + } + + async getVendorJson(initOverrides?: RequestInit | InitOverrideFunction): Promise { + const response = await this.getVendorJsonRaw(initOverrides); + return await response.value() as Payload; + } +} diff --git a/tests/golden/typescript/typescript-fetch/media-type-selection/apis/index.ts.golden b/tests/golden/typescript/typescript-fetch/media-type-selection/apis/index.ts.golden new file mode 100644 index 000000000..d6779f0cf --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/media-type-selection/apis/index.ts.golden @@ -0,0 +1,19 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Media Type Selection — 1.0.0 + * Covers normalized media-type selection for requests and responses. + */ +export type { + ApiSendJsonPreferredRequest, + SendJsonPreferredRawResponse, + ApiSendParameterizedMultipartRequest, + SendParameterizedMultipartRawResponse, + ApiSendVendorJsonRequest, + SendVendorJsonRawResponse, + GetOctetPreferredRawResponse, + GetTextPreferredRawResponse, + GetVendorJsonRawResponse, + MediaApiInterface, +} from './MediaApi'; +export { MediaApi } from './MediaApi'; diff --git a/tests/golden/typescript/typescript-fetch/media-type-selection/index.ts.golden b/tests/golden/typescript/typescript-fetch/media-type-selection/index.ts.golden new file mode 100644 index 000000000..fcc40fd74 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/media-type-selection/index.ts.golden @@ -0,0 +1,9 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Media Type Selection — 1.0.0 + * Covers normalized media-type selection for requests and responses. + */ +export * from './runtime/runtime'; +export * from './apis'; +export * from './models'; diff --git a/tests/golden/typescript/typescript-fetch/media-type-selection/models/FileEnvelope.ts.golden b/tests/golden/typescript/typescript-fetch/media-type-selection/models/FileEnvelope.ts.golden new file mode 100644 index 000000000..061a3aa5f --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/media-type-selection/models/FileEnvelope.ts.golden @@ -0,0 +1,10 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Media Type Selection — 1.0.0 + * Covers normalized media-type selection for requests and responses. + */ +export interface FileEnvelope { + readonly file: Blob | File; + readonly note?: string; +} diff --git a/tests/golden/typescript/typescript-fetch/media-type-selection/models/Payload.ts.golden b/tests/golden/typescript/typescript-fetch/media-type-selection/models/Payload.ts.golden new file mode 100644 index 000000000..7744ec5a9 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/media-type-selection/models/Payload.ts.golden @@ -0,0 +1,10 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Media Type Selection — 1.0.0 + * Covers normalized media-type selection for requests and responses. + */ +export interface Payload { + readonly count?: number; + readonly id: string; +} diff --git a/tests/golden/typescript/typescript-fetch/media-type-selection/models/index.ts.golden b/tests/golden/typescript/typescript-fetch/media-type-selection/models/index.ts.golden new file mode 100644 index 000000000..e2ee19b27 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/media-type-selection/models/index.ts.golden @@ -0,0 +1,8 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Media Type Selection — 1.0.0 + * Covers normalized media-type selection for requests and responses. + */ +export type { FileEnvelope } from './FileEnvelope'; +export type { Payload } from './Payload'; diff --git a/tests/golden/typescript/typescript-fetch/media-type-selection/package.json.golden b/tests/golden/typescript/typescript-fetch/media-type-selection/package.json.golden new file mode 100644 index 000000000..8f68aac7c --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/media-type-selection/package.json.golden @@ -0,0 +1,26 @@ +{ + "description": "Covers normalized media-type selection for requests and responses.", + "exports": { + ".": { + "default": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "files": [ + "dist" + ], + "keywords": [ + "openapi", + "api-client", + "typescript", + "generated" + ], + "main": "./dist/index.js", + "name": "media-type-selection", + "scripts": { + "build": "tsc" + }, + "type": "module", + "types": "./dist/index.d.ts", + "version": "1.0.0" +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/media-type-selection/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/media-type-selection/runtime/runtime.ts.golden new file mode 100644 index 000000000..0343f0502 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/media-type-selection/runtime/runtime.ts.golden @@ -0,0 +1,461 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Media Type Selection — 1.0.0 + * Covers normalized media-type selection for requests and responses. + */ +export const BASE_PATH = "http://localhost".replace(/\/+$/, ""); + +export interface ConfigurationParameters { + basePath?: string; // override base path + fetchApi?: FetchAPI; // override for fetch implementation + middleware?: Middleware[]; // middleware to apply before/after fetch requests + queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings + username?: string | (() => string | Promise); // parameter for basic security + password?: string | (() => string | Promise); // parameter for basic security + apiKey?: string | Promise | ((name: string) => string | Promise); // parameter for apiKey security + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string | Promise); // parameter for oauth2 security + headers?: HTTPHeaders; //header params we want to use on every request + credentials?: RequestCredentials; //value for the credentials param we want to use on each request +} + +export class Configuration { + constructor(private configuration: ConfigurationParameters = {}) {} + + set config(configuration: Configuration) { + this.configuration = configuration; + } + + get basePath(): string { + return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH; + } + + get fetchApi(): FetchAPI | undefined { + return this.configuration.fetchApi; + } + + get middleware(): Middleware[] { + return this.configuration.middleware || []; + } + + get queryParamsStringify(): (params: HTTPQuery) => string { + return this.configuration.queryParamsStringify || querystring; + } + + get username(): (() => string | Promise) | undefined { + const username = this.configuration.username; + if (username) { + return typeof username === 'function' ? username : async () => username; + } + return undefined; + } + + get password(): (() => string | Promise) | undefined { + const password = this.configuration.password; + if (password) { + return typeof password === 'function' ? password : async () => password; + } + return undefined; + } + + get apiKey(): ((name: string) => string | Promise) | undefined { + const apiKey = this.configuration.apiKey; + if (apiKey) { + return typeof apiKey === 'function' ? apiKey : () => apiKey; + } + return undefined; + } + + get accessToken(): ((name?: string, scopes?: string[]) => string | Promise) | undefined { + const accessToken = this.configuration.accessToken; + if (accessToken) { + return typeof accessToken === 'function' ? accessToken : async () => accessToken; + } + return undefined; + } + + get headers(): HTTPHeaders | undefined { + return this.configuration.headers; + } + + get credentials(): RequestCredentials | undefined { + return this.configuration.credentials; + } +} + +export const DefaultConfig = new Configuration(); + +/** + * This is the base class for all generated API classes. + */ +export class BaseAPI { + + private static readonly jsonRegex = new RegExp('^(:?application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(:?;.*)?$', 'i'); + private middleware: Middleware[]; + + constructor(protected configuration = DefaultConfig) { + this.middleware = configuration.middleware; + } + + withMiddleware(this: T, ...middlewares: Middleware[]) { + const next = this.clone(); + next.middleware = next.middleware.concat(...middlewares); + return next; + } + + withPreMiddleware(this: T, ...preMiddlewares: Array) { + const middlewares = preMiddlewares.map((pre) => ({ pre })); + return this.withMiddleware(...middlewares); + } + + withPostMiddleware(this: T, ...postMiddlewares: Array) { + const middlewares = postMiddlewares.map((post) => ({ post })); + return this.withMiddleware(...middlewares); + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + protected isJsonMime(mime: string | null | undefined): boolean { + if (!mime) { + return false; + } + return BaseAPI.jsonRegex.test(mime); + } + + protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise { + const { url, init } = await this.createFetchParams(context, initOverrides); + const response = await this.fetchApi(url, init); + if (response && (response.status >= 200 && response.status < 300)) { + return response; + } + throw new ResponseError(response, 'Response returned an error code'); + } + + private async createFetchParams(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction) { + let url = this.configuration.basePath + context.path; + if (context.query !== undefined && Object.keys(context.query).length !== 0) { + // only add the querystring to the URL if there are query parameters. + // this is done to avoid urls ending with a "?" character which buggy webservers + // do not handle correctly sometimes. + url += '?' + this.configuration.queryParamsStringify(context.query); + } + + const headers = Object.assign({}, this.configuration.headers, context.headers); + Object.keys(headers).forEach(key => headers[key] === undefined ? delete headers[key] : {}); + + const token = await this.configuration.accessToken?.(); + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + if (!headers['Authorization']) { + const username = await this.configuration.username?.(); + const password = await this.configuration.password?.(); + if (username && password) { + headers['Authorization'] = `Basic ${btoa(`${username}:${password}`)}`; + } + } + + const initOverrideFn = + typeof initOverrides === "function" + ? initOverrides + : async () => initOverrides; + + const initParams: HTTPRequestInit = { + method: context.method, + headers, + body: context.body, + credentials: this.configuration.credentials, + signal: context.signal, + }; + + const overriddenInit = { + ...initParams, + ...(await initOverrideFn({ + init: initParams, + context, + })) + }; + + let body: BodyInit | null | undefined; + if (isFormData(overriddenInit.body) + || (overriddenInit.body instanceof URLSearchParams) + || isBlob(overriddenInit.body)) { + body = overriddenInit.body; + } else if (this.isJsonMime(headers['Content-Type'])) { + body = JSON.stringify(overriddenInit.body); + } else { + body = overriddenInit.body as BodyInit | null | undefined; + } + + const init: RequestInit = { + ...overriddenInit, + body + }; + + return { url, init }; + } + + private fetchApi = async (url: string, init: RequestInit) => { + let fetchParams = { url, init }; + for (const middleware of this.middleware) { + if (middleware.pre) { + fetchParams = await middleware.pre({ + fetch: this.fetchApi, + ...fetchParams, + }) || fetchParams; + } + } + let response: Response | undefined = undefined; + try { + response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init); + } catch (e) { + for (const middleware of this.middleware) { + if (middleware.onError) { + response = await middleware.onError({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + error: e, + response: response ? response.clone() : undefined, + }) || response; + } + } + if (response === undefined) { + if (e instanceof Error) { + throw new FetchError(e, 'The request failed and the interceptors did not return an alternative response'); + } else { + throw e; + } + } + } + for (const middleware of this.middleware) { + if (middleware.post) { + response = await middleware.post({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + response: response.clone(), + }) || response; + } + } + return response; + } + + /** + * Create a shallow clone of `this` by constructing a new instance + * and then shallow cloning data members. + */ + private clone(this: T): T { + const constructor = this.constructor as new (configuration: Configuration) => T; + const next = new constructor(this.configuration); + next.middleware = this.middleware.slice(); + return next; + } +} + +function isBlob(value: unknown): value is Blob { + return typeof Blob !== 'undefined' && value instanceof Blob; +} + +function isFormData(value: unknown): value is FormData { + return typeof FormData !== "undefined" && value instanceof FormData; +} + +export class ResponseError extends Error { + override name = "ResponseError" as const; + constructor(public response: Response, msg?: string) { + super(msg); + } +} + +export class FetchError extends Error { + override name = "FetchError" as const; + constructor(public cause: Error, msg?: string) { + super(msg); + } +} + +export class RequiredError extends Error { + override name = "RequiredError" as const; + constructor(public field: string, msg?: string) { + super(msg); + } +} + +export type FetchAPI = WindowOrWorkerGlobalScope['fetch']; + +export type Json = unknown; +export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; +export type HTTPHeaders = { [key: string]: string }; +export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; +export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; + +export type InitOverrideFunction = (requestContext: { init: HTTPRequestInit, context: RequestOpts }) => Promise + +export interface FetchParams { + url: string; + init: RequestInit; +} + +export interface RequestOpts { + path: string; + method: HTTPMethod; + headers: HTTPHeaders; + query?: HTTPQuery; + body?: HTTPBody; + signal?: AbortSignal; +} + +export function querystring(params: HTTPQuery, prefix: string = ''): string { + return Object.keys(params) + .map(key => querystringSingleKey(key, params[key], prefix)) + .filter(part => part.length > 0) + .join('&'); +} + +function querystringSingleKey(key: string, value: string | number | null | undefined | boolean | Array | Set | HTTPQuery, keyPrefix: string = ''): string { + const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key); + if (value instanceof Array) { + const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue))) + .join(`&${encodeURIComponent(fullKey)}=`); + return `${encodeURIComponent(fullKey)}=${multiValue}`; + } + if (value instanceof Set) { + const valueAsArray = Array.from(value); + return querystringSingleKey(key, valueAsArray, keyPrefix); + } + if (value instanceof Date) { + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`; + } + if (value instanceof Object) { + return querystring(value as HTTPQuery, fullKey); + } + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`; +} + +export function exists(json: Record, key: string): boolean { + const value = json[key]; + return value !== null && value !== undefined; +} + +export function mapValues(data: Record, fn: (item: T) => U): Record { + const result: Record = {}; + for (const key of Object.keys(data)) { + result[key] = fn(data[key]); + } + return result; +} + +export function canConsumeForm(consumes: Consume[]): boolean { + for (const consume of consumes) { + if ('multipart/form-data' === consume.contentType) { + return true; + } + } + return false; +} + +export interface Consume { + contentType: string; +} + +export interface RequestContext { + fetch: FetchAPI; + url: string; + init: RequestInit; +} + +export interface ResponseContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + response: Response; +} + +export interface ErrorContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + error: unknown; + response?: Response; +} + +export interface Middleware { + pre?(context: RequestContext): Promise; + post?(context: ResponseContext): Promise; + onError?(context: ErrorContext): Promise; +} + +export interface ApiResponse { + raw: Response; + value(): Promise; +} + +export interface ResponseTransformer { + (json: unknown): T; +} + +class ApiResponseBase { + public readonly status: number; + public readonly ok: boolean; + public readonly statusText: string; + public readonly headers: Headers; + + constructor(public readonly raw: Response) { + this.status = raw.status; + this.ok = raw.ok; + this.statusText = raw.statusText; + this.headers = raw.headers; + } +} + +export class JSONApiResponse extends ApiResponseBase { + constructor(raw: Response, private transformer: ResponseTransformer = (jsonValue: unknown) => jsonValue as T) { + super(raw); + } + + async value(): Promise { + return this.transformer(await this.raw.json()); + } +} + +export class VoidApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return undefined; + } +} + +export class BlobApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return await this.raw.blob(); + }; +} + +export class TextApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return await this.raw.text(); + }; +} diff --git a/tests/golden/typescript/typescript-fetch/media-type-selection/tsconfig.esm.json.golden b/tests/golden/typescript/typescript-fetch/media-type-selection/tsconfig.esm.json.golden new file mode 100644 index 000000000..bb8350cb6 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/media-type-selection/tsconfig.esm.json.golden @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "ES2020", + "outDir": "dist/esm" + }, + "extends": "./tsconfig.json" +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/media-type-selection/tsconfig.json.golden b/tests/golden/typescript/typescript-fetch/media-type-selection/tsconfig.json.golden new file mode 100644 index 000000000..4b3a8cf7f --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/media-type-selection/tsconfig.json.golden @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "lib": [ + "ES2020", + "DOM" + ], + "module": "ES2020", + "moduleResolution": "bundler", + "outDir": "./dist", + "resolveJsonModule": true, + "rootDir": "./", + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "target": "ES2020", + "typeRoots": [ + "node_modules/@types" + ] + }, + "exclude": [ + "dist", + "node_modules" + ], + "include": [ + "**/*.ts" + ] +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/minimal/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/minimal/runtime/runtime.ts.golden index b7b281767..9bf8631da 100644 --- a/tests/golden/typescript/typescript-fetch/minimal/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/minimal/runtime/runtime.ts.golden @@ -296,7 +296,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/multipart-edge-cases/README.md.golden b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/README.md.golden new file mode 100644 index 000000000..49e338310 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/README.md.golden @@ -0,0 +1,248 @@ +# multipart-edge-cases + +Covers optional multipart bodies, optional parts, and text-only multipart fields. + +**Version:** 1.0.0 + +## Overview + +This package provides a TypeScript/JavaScript client for the Multipart Edge Cases API. It uses the native [Fetch API](https://fetch.spec.whatwg.org/) for HTTP requests and works in both Node.js and browser environments. + +## Features + +- ✨ **Type-safe** - Full TypeScript support with generated types +- 🚀 **Modern** - Uses native Fetch API, no external HTTP dependencies +- 🔧 **Configurable** - Flexible configuration options +- 🎯 **Middleware** - Support for request/response interceptors +- 📦 **Tree-shakeable** - Import only what you need +- 🌐 **Universal** - Works in Node.js and browsers + +## Installation + +### From npm (published package) + +```bash +npm install multipart-edge-cases +``` + +### From local path (development) + +Add the package to your `package.json` using the `file:` protocol: + +```json +{ + "dependencies": { + "multipart-edge-cases": "file:../../path/to/generated/package" + } +} +``` + +Then run: + +```bash +npm install +``` + +## Quick Start + +```typescript +import { Configuration, MultipartApi } from 'multipart-edge-cases'; + +// Create a configuration +const config = new Configuration({ + basePath: 'https://api.example.com', + headers: { + 'Authorization': 'Bearer YOUR_TOKEN' + } +}); + +// Initialize the API client +const api = new MultipartApi(config); + +// Make API calls +try { + const result = await api.someMethod(); + console.log(result); +} catch (error) { + console.error('API Error:', error); +} +``` + +## Configuration + +The `Configuration` class accepts the following options: + +```typescript +interface ConfigurationParameters { + /** Base URL for API requests */ + basePath?: string; + + /** Custom fetch implementation */ + fetchApi?: typeof fetch; + + /** Request/response middleware */ + middleware?: Middleware[]; + + /** Custom query string serializer */ + queryParamsStringify?: (params: HTTPQuery) => string; + + /** Default headers for all requests */ + headers?: Record; + + /** Credentials mode for requests */ + credentials?: RequestCredentials; +} +``` + +### Example with custom configuration + +```typescript +const config = new Configuration({ + basePath: 'https://api.example.com', + headers: { + 'X-API-Key': 'your-api-key', + 'Content-Type': 'application/json' + }, + credentials: 'include' +}); +``` + +## Middleware + +Add custom middleware to intercept requests and responses: + +```typescript +import { Configuration, Middleware } from 'multipart-edge-cases'; + +const loggingMiddleware: Middleware = { + pre: async (context) => { + console.log('Request:', context.url); + return context; + }, + post: async (context) => { + console.log('Response:', context.response.status); + return context.response; + }, + onError: async (context) => { + console.error('Error:', context.error); + return undefined; + } +}; + +const config = new Configuration({ + basePath: 'https://api.example.com', + middleware: [loggingMiddleware] +}); +``` + +## Error Handling + +The client throws typed errors for different failure scenarios: + +```typescript +import { ResponseError, FetchError, RequiredError } from 'multipart-edge-cases'; + +try { + const result = await api.someMethod(); +} catch (error) { + if (error instanceof ResponseError) { + // HTTP error response (4xx, 5xx) + console.error('HTTP Error:', error.response.status); + } else if (error instanceof FetchError) { + // Network or fetch error + console.error('Network Error:', error.cause); + } else if (error instanceof RequiredError) { + // Missing required parameter + console.error('Missing field:', error.field); + } +} +``` + +## API Reference + +This package exports the following: + +- **Configuration** - Client configuration class +- **BaseAPI** - Base class for all API clients +- **API Classes** - Generated API client classes (e.g., `UserApi`, `PostApi`) +- **Models** - Generated TypeScript interfaces for request/response types +- **Errors** - `ResponseError`, `FetchError`, `RequiredError` +- **Types** - TypeScript type definitions + +## Development + +### Building + +To build the package: + +```bash +npm install +npm run build +``` + +This will compile TypeScript to JavaScript in the `dist/` directory. + +### Building for ESM + +To build ES modules: + +```bash +npm run build +``` + +## TypeScript Support + +This package includes TypeScript type definitions. No additional `@types` package is needed. + +### TypeScript Configuration + +This package works with standard TypeScript configurations. If you're using a bundler-based setup, you may want to configure: + +```json +{ + "compilerOptions": { + "moduleResolution": "bundler" + } +} +``` + +### Type Imports + +```typescript +import type { User, CreateUserRequest } from 'multipart-edge-cases'; + +const user: User = { + id: 1, + name: 'John Doe', + email: 'john@example.com' +}; +``` + +## Browser Support + +This package uses the native Fetch API, which is supported in: + +- Chrome 42+ +- Firefox 39+ +- Safari 10.1+ +- Edge 14+ +- Node.js 18+ (native fetch) +- Node.js <18 (with `node-fetch` polyfill) + +For older browsers, you may need to include a fetch polyfill. + +## License + +This is an auto-generated API client. Please refer to your API documentation for license information. + +## Support + +For issues related to the API itself, please contact the API provider. + +For issues with this generated client, please check the OpenAPI specification used to generate it. + +--- + +**Generated by OpenAPI Generator** + +API Version: 1.0.0 diff --git a/tests/golden/typescript/typescript-fetch/multipart-edge-cases/apis/MultipartApi.ts.golden b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/apis/MultipartApi.ts.golden new file mode 100644 index 000000000..38dc1ea7b --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/apis/MultipartApi.ts.golden @@ -0,0 +1,142 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Edge Cases — 1.0.0 + * Covers optional multipart bodies, optional parts, and text-only multipart fields. + */ +import type { OptionalUpload } from '../models/OptionalUpload'; +import type { TextFields } from '../models/TextFields'; +import type { Configuration, HTTPQuery, InitOverrideFunction } from '../runtime/runtime'; +import { BaseAPI, DefaultConfig, RequiredError, VoidApiResponse } from '../runtime/runtime'; + +export interface ApiSendOptionalPartsRequest { + body?: OptionalUpload; +} + +export interface ApiSendTextFieldsRequest { + body: TextFields; +} + +export type SendOptionalPartsRawResponse = + | VoidApiResponse & { status: 204 } + | VoidApiResponse & { status: number }; + +export type SendTextFieldsRawResponse = + | VoidApiResponse & { status: 204 } + | VoidApiResponse & { status: number }; + + +export interface MultipartApiInterface { + sendOptionalPartsRaw: (requestParameters: ApiSendOptionalPartsRequest, initOverrides?: RequestInit | InitOverrideFunction) => Promise; + sendOptionalParts: (requestParameters: ApiSendOptionalPartsRequest, initOverrides?: RequestInit | InitOverrideFunction) => Promise; + sendTextFieldsRaw: (requestParameters: ApiSendTextFieldsRequest, initOverrides?: RequestInit | InitOverrideFunction) => Promise; + sendTextFields: (requestParameters: ApiSendTextFieldsRequest, initOverrides?: RequestInit | InitOverrideFunction) => Promise; +} + +export class MultipartApi extends BaseAPI implements MultipartApiInterface { + /** + * Initialize the API client + */ + constructor(configuration?: Configuration) { + super(configuration ?? DefaultConfig); + } + + async sendOptionalPartsRaw(requestParameters: ApiSendOptionalPartsRequest, + initOverrides?: RequestInit | InitOverrideFunction): Promise { + // Build path with path parameters + const urlPath = `/multipart/optional`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + }; + + // Prepare request body + let requestBody: FormData | undefined = undefined; + if (requestParameters.body !== undefined && requestParameters.body !== null) { + requestBody = new FormData(); + if (requestParameters.body.attributes !== undefined && requestParameters.body.attributes !== null) { + requestBody.append('attributes', JSON.stringify(requestParameters.body.attributes)); + } + if (requestParameters.body.enabled !== undefined && requestParameters.body.enabled !== null) { + requestBody.append('enabled', String(requestParameters.body.enabled)); + } + if (requestParameters.body.file !== undefined && requestParameters.body.file !== null) { + requestBody.append('file', requestParameters.body.file); + } + if (requestParameters.body.retry_count !== undefined && requestParameters.body.retry_count !== null) { + requestBody.append('retry_count', String(requestParameters.body.retry_count)); + } + if (requestParameters.body.title !== undefined && requestParameters.body.title !== null) { + requestBody.append('title', String(requestParameters.body.title)); + } + } + // Make request + const response = await this.request({ + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: requestBody, + }, initOverrides); + + // Handle responses + if (response.status === 204) { + return new VoidApiResponse(response) as VoidApiResponse & { status: 204 }; + } + else { + return new VoidApiResponse(response) as VoidApiResponse & { status: number }; + } + } + + async sendOptionalParts(requestParameters: ApiSendOptionalPartsRequest, + initOverrides?: RequestInit | InitOverrideFunction): Promise { + const response = await this.sendOptionalPartsRaw(requestParameters, initOverrides); + return await response.value(); + } + + async sendTextFieldsRaw(requestParameters: ApiSendTextFieldsRequest, initOverrides?: RequestInit + | InitOverrideFunction): Promise { + if (requestParameters.body === undefined || requestParameters.body === null) { + throw new RequiredError( + 'body', + 'Required parameter "body" was null or undefined when calling sendTextFieldsRaw().' + ); + } + // Build path with path parameters + const urlPath = `/multipart/text-only`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + }; + + // Prepare request body + const requestBody = new FormData(); + requestBody.append('enabled', String(requestParameters.body.enabled)); + requestBody.append('note', String(requestParameters.body.note)); + requestBody.append('retry_count', String(requestParameters.body.retry_count)); + // Make request + const response = await this.request({ + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: requestBody, + }, initOverrides); + + // Handle responses + if (response.status === 204) { + return new VoidApiResponse(response) as VoidApiResponse & { status: 204 }; + } + else { + return new VoidApiResponse(response) as VoidApiResponse & { status: number }; + } + } + + async sendTextFields(requestParameters: ApiSendTextFieldsRequest, initOverrides?: RequestInit + | InitOverrideFunction): Promise { + const response = await this.sendTextFieldsRaw(requestParameters, initOverrides); + return await response.value(); + } +} diff --git a/tests/golden/typescript/typescript-fetch/multipart-edge-cases/apis/index.ts.golden b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/apis/index.ts.golden new file mode 100644 index 000000000..421be7223 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/apis/index.ts.golden @@ -0,0 +1,14 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Edge Cases — 1.0.0 + * Covers optional multipart bodies, optional parts, and text-only multipart fields. + */ +export type { + ApiSendOptionalPartsRequest, + SendOptionalPartsRawResponse, + ApiSendTextFieldsRequest, + SendTextFieldsRawResponse, + MultipartApiInterface, +} from './MultipartApi'; +export { MultipartApi } from './MultipartApi'; diff --git a/tests/golden/typescript/typescript-fetch/multipart-edge-cases/index.ts.golden b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/index.ts.golden new file mode 100644 index 000000000..a2c3db556 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/index.ts.golden @@ -0,0 +1,9 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Edge Cases — 1.0.0 + * Covers optional multipart bodies, optional parts, and text-only multipart fields. + */ +export * from './runtime/runtime'; +export * from './apis'; +export * from './models'; diff --git a/tests/golden/typescript/typescript-fetch/multipart-edge-cases/models/Attributes.ts.golden b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/models/Attributes.ts.golden new file mode 100644 index 000000000..7f1e7a52b --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/models/Attributes.ts.golden @@ -0,0 +1,10 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Edge Cases — 1.0.0 + * Covers optional multipart bodies, optional parts, and text-only multipart fields. + */ +export interface Attributes { + readonly label: string; + readonly priority?: number; +} diff --git a/tests/golden/typescript/typescript-fetch/multipart-edge-cases/models/OptionalUpload.ts.golden b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/models/OptionalUpload.ts.golden new file mode 100644 index 000000000..27b69dc24 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/models/OptionalUpload.ts.golden @@ -0,0 +1,15 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Edge Cases — 1.0.0 + * Covers optional multipart bodies, optional parts, and text-only multipart fields. + */ +import type { Attributes } from './Attributes'; + +export interface OptionalUpload { + readonly attributes?: Attributes; + readonly enabled?: boolean; + readonly file?: Blob | File; + readonly retry_count?: number; + readonly title?: string; +} diff --git a/tests/golden/typescript/typescript-fetch/multipart-edge-cases/models/TextFields.ts.golden b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/models/TextFields.ts.golden new file mode 100644 index 000000000..e9560bbee --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/models/TextFields.ts.golden @@ -0,0 +1,11 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Edge Cases — 1.0.0 + * Covers optional multipart bodies, optional parts, and text-only multipart fields. + */ +export interface TextFields { + readonly enabled: boolean; + readonly note: string; + readonly retry_count: number; +} diff --git a/tests/golden/typescript/typescript-fetch/multipart-edge-cases/models/index.ts.golden b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/models/index.ts.golden new file mode 100644 index 000000000..db8b8692e --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/models/index.ts.golden @@ -0,0 +1,9 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Edge Cases — 1.0.0 + * Covers optional multipart bodies, optional parts, and text-only multipart fields. + */ +export type { Attributes } from './Attributes'; +export type { OptionalUpload } from './OptionalUpload'; +export type { TextFields } from './TextFields'; diff --git a/tests/golden/typescript/typescript-fetch/multipart-edge-cases/package.json.golden b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/package.json.golden new file mode 100644 index 000000000..44bd1c150 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/package.json.golden @@ -0,0 +1,26 @@ +{ + "description": "Covers optional multipart bodies, optional parts, and text-only multipart fields.", + "exports": { + ".": { + "default": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "files": [ + "dist" + ], + "keywords": [ + "openapi", + "api-client", + "typescript", + "generated" + ], + "main": "./dist/index.js", + "name": "multipart-edge-cases", + "scripts": { + "build": "tsc" + }, + "type": "module", + "types": "./dist/index.d.ts", + "version": "1.0.0" +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/multipart-edge-cases/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/runtime/runtime.ts.golden new file mode 100644 index 000000000..365f9872a --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/runtime/runtime.ts.golden @@ -0,0 +1,461 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Edge Cases — 1.0.0 + * Covers optional multipart bodies, optional parts, and text-only multipart fields. + */ +export const BASE_PATH = "http://localhost".replace(/\/+$/, ""); + +export interface ConfigurationParameters { + basePath?: string; // override base path + fetchApi?: FetchAPI; // override for fetch implementation + middleware?: Middleware[]; // middleware to apply before/after fetch requests + queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings + username?: string | (() => string | Promise); // parameter for basic security + password?: string | (() => string | Promise); // parameter for basic security + apiKey?: string | Promise | ((name: string) => string | Promise); // parameter for apiKey security + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string | Promise); // parameter for oauth2 security + headers?: HTTPHeaders; //header params we want to use on every request + credentials?: RequestCredentials; //value for the credentials param we want to use on each request +} + +export class Configuration { + constructor(private configuration: ConfigurationParameters = {}) {} + + set config(configuration: Configuration) { + this.configuration = configuration; + } + + get basePath(): string { + return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH; + } + + get fetchApi(): FetchAPI | undefined { + return this.configuration.fetchApi; + } + + get middleware(): Middleware[] { + return this.configuration.middleware || []; + } + + get queryParamsStringify(): (params: HTTPQuery) => string { + return this.configuration.queryParamsStringify || querystring; + } + + get username(): (() => string | Promise) | undefined { + const username = this.configuration.username; + if (username) { + return typeof username === 'function' ? username : async () => username; + } + return undefined; + } + + get password(): (() => string | Promise) | undefined { + const password = this.configuration.password; + if (password) { + return typeof password === 'function' ? password : async () => password; + } + return undefined; + } + + get apiKey(): ((name: string) => string | Promise) | undefined { + const apiKey = this.configuration.apiKey; + if (apiKey) { + return typeof apiKey === 'function' ? apiKey : () => apiKey; + } + return undefined; + } + + get accessToken(): ((name?: string, scopes?: string[]) => string | Promise) | undefined { + const accessToken = this.configuration.accessToken; + if (accessToken) { + return typeof accessToken === 'function' ? accessToken : async () => accessToken; + } + return undefined; + } + + get headers(): HTTPHeaders | undefined { + return this.configuration.headers; + } + + get credentials(): RequestCredentials | undefined { + return this.configuration.credentials; + } +} + +export const DefaultConfig = new Configuration(); + +/** + * This is the base class for all generated API classes. + */ +export class BaseAPI { + + private static readonly jsonRegex = new RegExp('^(:?application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(:?;.*)?$', 'i'); + private middleware: Middleware[]; + + constructor(protected configuration = DefaultConfig) { + this.middleware = configuration.middleware; + } + + withMiddleware(this: T, ...middlewares: Middleware[]) { + const next = this.clone(); + next.middleware = next.middleware.concat(...middlewares); + return next; + } + + withPreMiddleware(this: T, ...preMiddlewares: Array) { + const middlewares = preMiddlewares.map((pre) => ({ pre })); + return this.withMiddleware(...middlewares); + } + + withPostMiddleware(this: T, ...postMiddlewares: Array) { + const middlewares = postMiddlewares.map((post) => ({ post })); + return this.withMiddleware(...middlewares); + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + protected isJsonMime(mime: string | null | undefined): boolean { + if (!mime) { + return false; + } + return BaseAPI.jsonRegex.test(mime); + } + + protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise { + const { url, init } = await this.createFetchParams(context, initOverrides); + const response = await this.fetchApi(url, init); + if (response && (response.status >= 200 && response.status < 300)) { + return response; + } + throw new ResponseError(response, 'Response returned an error code'); + } + + private async createFetchParams(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction) { + let url = this.configuration.basePath + context.path; + if (context.query !== undefined && Object.keys(context.query).length !== 0) { + // only add the querystring to the URL if there are query parameters. + // this is done to avoid urls ending with a "?" character which buggy webservers + // do not handle correctly sometimes. + url += '?' + this.configuration.queryParamsStringify(context.query); + } + + const headers = Object.assign({}, this.configuration.headers, context.headers); + Object.keys(headers).forEach(key => headers[key] === undefined ? delete headers[key] : {}); + + const token = await this.configuration.accessToken?.(); + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + if (!headers['Authorization']) { + const username = await this.configuration.username?.(); + const password = await this.configuration.password?.(); + if (username && password) { + headers['Authorization'] = `Basic ${btoa(`${username}:${password}`)}`; + } + } + + const initOverrideFn = + typeof initOverrides === "function" + ? initOverrides + : async () => initOverrides; + + const initParams: HTTPRequestInit = { + method: context.method, + headers, + body: context.body, + credentials: this.configuration.credentials, + signal: context.signal, + }; + + const overriddenInit = { + ...initParams, + ...(await initOverrideFn({ + init: initParams, + context, + })) + }; + + let body: BodyInit | null | undefined; + if (isFormData(overriddenInit.body) + || (overriddenInit.body instanceof URLSearchParams) + || isBlob(overriddenInit.body)) { + body = overriddenInit.body; + } else if (this.isJsonMime(headers['Content-Type'])) { + body = JSON.stringify(overriddenInit.body); + } else { + body = overriddenInit.body as BodyInit | null | undefined; + } + + const init: RequestInit = { + ...overriddenInit, + body + }; + + return { url, init }; + } + + private fetchApi = async (url: string, init: RequestInit) => { + let fetchParams = { url, init }; + for (const middleware of this.middleware) { + if (middleware.pre) { + fetchParams = await middleware.pre({ + fetch: this.fetchApi, + ...fetchParams, + }) || fetchParams; + } + } + let response: Response | undefined = undefined; + try { + response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init); + } catch (e) { + for (const middleware of this.middleware) { + if (middleware.onError) { + response = await middleware.onError({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + error: e, + response: response ? response.clone() : undefined, + }) || response; + } + } + if (response === undefined) { + if (e instanceof Error) { + throw new FetchError(e, 'The request failed and the interceptors did not return an alternative response'); + } else { + throw e; + } + } + } + for (const middleware of this.middleware) { + if (middleware.post) { + response = await middleware.post({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + response: response.clone(), + }) || response; + } + } + return response; + } + + /** + * Create a shallow clone of `this` by constructing a new instance + * and then shallow cloning data members. + */ + private clone(this: T): T { + const constructor = this.constructor as new (configuration: Configuration) => T; + const next = new constructor(this.configuration); + next.middleware = this.middleware.slice(); + return next; + } +} + +function isBlob(value: unknown): value is Blob { + return typeof Blob !== 'undefined' && value instanceof Blob; +} + +function isFormData(value: unknown): value is FormData { + return typeof FormData !== "undefined" && value instanceof FormData; +} + +export class ResponseError extends Error { + override name = "ResponseError" as const; + constructor(public response: Response, msg?: string) { + super(msg); + } +} + +export class FetchError extends Error { + override name = "FetchError" as const; + constructor(public cause: Error, msg?: string) { + super(msg); + } +} + +export class RequiredError extends Error { + override name = "RequiredError" as const; + constructor(public field: string, msg?: string) { + super(msg); + } +} + +export type FetchAPI = WindowOrWorkerGlobalScope['fetch']; + +export type Json = unknown; +export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; +export type HTTPHeaders = { [key: string]: string }; +export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; +export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; + +export type InitOverrideFunction = (requestContext: { init: HTTPRequestInit, context: RequestOpts }) => Promise + +export interface FetchParams { + url: string; + init: RequestInit; +} + +export interface RequestOpts { + path: string; + method: HTTPMethod; + headers: HTTPHeaders; + query?: HTTPQuery; + body?: HTTPBody; + signal?: AbortSignal; +} + +export function querystring(params: HTTPQuery, prefix: string = ''): string { + return Object.keys(params) + .map(key => querystringSingleKey(key, params[key], prefix)) + .filter(part => part.length > 0) + .join('&'); +} + +function querystringSingleKey(key: string, value: string | number | null | undefined | boolean | Array | Set | HTTPQuery, keyPrefix: string = ''): string { + const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key); + if (value instanceof Array) { + const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue))) + .join(`&${encodeURIComponent(fullKey)}=`); + return `${encodeURIComponent(fullKey)}=${multiValue}`; + } + if (value instanceof Set) { + const valueAsArray = Array.from(value); + return querystringSingleKey(key, valueAsArray, keyPrefix); + } + if (value instanceof Date) { + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`; + } + if (value instanceof Object) { + return querystring(value as HTTPQuery, fullKey); + } + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`; +} + +export function exists(json: Record, key: string): boolean { + const value = json[key]; + return value !== null && value !== undefined; +} + +export function mapValues(data: Record, fn: (item: T) => U): Record { + const result: Record = {}; + for (const key of Object.keys(data)) { + result[key] = fn(data[key]); + } + return result; +} + +export function canConsumeForm(consumes: Consume[]): boolean { + for (const consume of consumes) { + if ('multipart/form-data' === consume.contentType) { + return true; + } + } + return false; +} + +export interface Consume { + contentType: string; +} + +export interface RequestContext { + fetch: FetchAPI; + url: string; + init: RequestInit; +} + +export interface ResponseContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + response: Response; +} + +export interface ErrorContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + error: unknown; + response?: Response; +} + +export interface Middleware { + pre?(context: RequestContext): Promise; + post?(context: ResponseContext): Promise; + onError?(context: ErrorContext): Promise; +} + +export interface ApiResponse { + raw: Response; + value(): Promise; +} + +export interface ResponseTransformer { + (json: unknown): T; +} + +class ApiResponseBase { + public readonly status: number; + public readonly ok: boolean; + public readonly statusText: string; + public readonly headers: Headers; + + constructor(public readonly raw: Response) { + this.status = raw.status; + this.ok = raw.ok; + this.statusText = raw.statusText; + this.headers = raw.headers; + } +} + +export class JSONApiResponse extends ApiResponseBase { + constructor(raw: Response, private transformer: ResponseTransformer = (jsonValue: unknown) => jsonValue as T) { + super(raw); + } + + async value(): Promise { + return this.transformer(await this.raw.json()); + } +} + +export class VoidApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return undefined; + } +} + +export class BlobApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return await this.raw.blob(); + }; +} + +export class TextApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return await this.raw.text(); + }; +} diff --git a/tests/golden/typescript/typescript-fetch/multipart-edge-cases/tsconfig.esm.json.golden b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/tsconfig.esm.json.golden new file mode 100644 index 000000000..bb8350cb6 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/tsconfig.esm.json.golden @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "ES2020", + "outDir": "dist/esm" + }, + "extends": "./tsconfig.json" +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/multipart-edge-cases/tsconfig.json.golden b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/tsconfig.json.golden new file mode 100644 index 000000000..4b3a8cf7f --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-edge-cases/tsconfig.json.golden @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "lib": [ + "ES2020", + "DOM" + ], + "module": "ES2020", + "moduleResolution": "bundler", + "outDir": "./dist", + "resolveJsonModule": true, + "rootDir": "./", + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "target": "ES2020", + "typeRoots": [ + "node_modules/@types" + ] + }, + "exclude": [ + "dist", + "node_modules" + ], + "include": [ + "**/*.ts" + ] +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/README.md.golden b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/README.md.golden new file mode 100644 index 000000000..cde8d2ef6 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/README.md.golden @@ -0,0 +1,248 @@ +# multipart-nested-object-parts + +Covers multipart object parts whose wire names differ from ergonomic names. + +**Version:** 1.0.0 + +## Overview + +This package provides a TypeScript/JavaScript client for the Multipart Nested Object Parts API. It uses the native [Fetch API](https://fetch.spec.whatwg.org/) for HTTP requests and works in both Node.js and browser environments. + +## Features + +- ✨ **Type-safe** - Full TypeScript support with generated types +- 🚀 **Modern** - Uses native Fetch API, no external HTTP dependencies +- 🔧 **Configurable** - Flexible configuration options +- 🎯 **Middleware** - Support for request/response interceptors +- 📦 **Tree-shakeable** - Import only what you need +- 🌐 **Universal** - Works in Node.js and browsers + +## Installation + +### From npm (published package) + +```bash +npm install multipart-nested-object-parts +``` + +### From local path (development) + +Add the package to your `package.json` using the `file:` protocol: + +```json +{ + "dependencies": { + "multipart-nested-object-parts": "file:../../path/to/generated/package" + } +} +``` + +Then run: + +```bash +npm install +``` + +## Quick Start + +```typescript +import { Configuration, MultipartApi } from 'multipart-nested-object-parts'; + +// Create a configuration +const config = new Configuration({ + basePath: 'https://api.example.com', + headers: { + 'Authorization': 'Bearer YOUR_TOKEN' + } +}); + +// Initialize the API client +const api = new MultipartApi(config); + +// Make API calls +try { + const result = await api.someMethod(); + console.log(result); +} catch (error) { + console.error('API Error:', error); +} +``` + +## Configuration + +The `Configuration` class accepts the following options: + +```typescript +interface ConfigurationParameters { + /** Base URL for API requests */ + basePath?: string; + + /** Custom fetch implementation */ + fetchApi?: typeof fetch; + + /** Request/response middleware */ + middleware?: Middleware[]; + + /** Custom query string serializer */ + queryParamsStringify?: (params: HTTPQuery) => string; + + /** Default headers for all requests */ + headers?: Record; + + /** Credentials mode for requests */ + credentials?: RequestCredentials; +} +``` + +### Example with custom configuration + +```typescript +const config = new Configuration({ + basePath: 'https://api.example.com', + headers: { + 'X-API-Key': 'your-api-key', + 'Content-Type': 'application/json' + }, + credentials: 'include' +}); +``` + +## Middleware + +Add custom middleware to intercept requests and responses: + +```typescript +import { Configuration, Middleware } from 'multipart-nested-object-parts'; + +const loggingMiddleware: Middleware = { + pre: async (context) => { + console.log('Request:', context.url); + return context; + }, + post: async (context) => { + console.log('Response:', context.response.status); + return context.response; + }, + onError: async (context) => { + console.error('Error:', context.error); + return undefined; + } +}; + +const config = new Configuration({ + basePath: 'https://api.example.com', + middleware: [loggingMiddleware] +}); +``` + +## Error Handling + +The client throws typed errors for different failure scenarios: + +```typescript +import { ResponseError, FetchError, RequiredError } from 'multipart-nested-object-parts'; + +try { + const result = await api.someMethod(); +} catch (error) { + if (error instanceof ResponseError) { + // HTTP error response (4xx, 5xx) + console.error('HTTP Error:', error.response.status); + } else if (error instanceof FetchError) { + // Network or fetch error + console.error('Network Error:', error.cause); + } else if (error instanceof RequiredError) { + // Missing required parameter + console.error('Missing field:', error.field); + } +} +``` + +## API Reference + +This package exports the following: + +- **Configuration** - Client configuration class +- **BaseAPI** - Base class for all API clients +- **API Classes** - Generated API client classes (e.g., `UserApi`, `PostApi`) +- **Models** - Generated TypeScript interfaces for request/response types +- **Errors** - `ResponseError`, `FetchError`, `RequiredError` +- **Types** - TypeScript type definitions + +## Development + +### Building + +To build the package: + +```bash +npm install +npm run build +``` + +This will compile TypeScript to JavaScript in the `dist/` directory. + +### Building for ESM + +To build ES modules: + +```bash +npm run build +``` + +## TypeScript Support + +This package includes TypeScript type definitions. No additional `@types` package is needed. + +### TypeScript Configuration + +This package works with standard TypeScript configurations. If you're using a bundler-based setup, you may want to configure: + +```json +{ + "compilerOptions": { + "moduleResolution": "bundler" + } +} +``` + +### Type Imports + +```typescript +import type { User, CreateUserRequest } from 'multipart-nested-object-parts'; + +const user: User = { + id: 1, + name: 'John Doe', + email: 'john@example.com' +}; +``` + +## Browser Support + +This package uses the native Fetch API, which is supported in: + +- Chrome 42+ +- Firefox 39+ +- Safari 10.1+ +- Edge 14+ +- Node.js 18+ (native fetch) +- Node.js <18 (with `node-fetch` polyfill) + +For older browsers, you may need to include a fetch polyfill. + +## License + +This is an auto-generated API client. Please refer to your API documentation for license information. + +## Support + +For issues related to the API itself, please contact the API provider. + +For issues with this generated client, please check the OpenAPI specification used to generate it. + +--- + +**Generated by OpenAPI Generator** + +API Version: 1.0.0 diff --git a/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/apis/MultipartApi.ts.golden b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/apis/MultipartApi.ts.golden new file mode 100644 index 000000000..c24bcc9a1 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/apis/MultipartApi.ts.golden @@ -0,0 +1,76 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Nested Object Parts — 1.0.0 + * Covers multipart object parts whose wire names differ from ergonomic names. + */ +import type { NestedUpload } from '../models/NestedUpload'; +import type { Configuration, HTTPQuery, InitOverrideFunction } from '../runtime/runtime'; +import { BaseAPI, DefaultConfig, RequiredError, VoidApiResponse } from '../runtime/runtime'; + +export interface ApiSendNestedObjectPartRequest { + body: NestedUpload; +} + +export type SendNestedObjectPartRawResponse = + | VoidApiResponse & { status: 204 } + | VoidApiResponse & { status: number }; + + +export interface MultipartApiInterface { + sendNestedObjectPartRaw: (requestParameters: ApiSendNestedObjectPartRequest, initOverrides?: RequestInit | InitOverrideFunction) => Promise; + sendNestedObjectPart: (requestParameters: ApiSendNestedObjectPartRequest, initOverrides?: RequestInit | InitOverrideFunction) => Promise; +} + +export class MultipartApi extends BaseAPI implements MultipartApiInterface { + /** + * Initialize the API client + */ + constructor(configuration?: Configuration) { + super(configuration ?? DefaultConfig); + } + + async sendNestedObjectPartRaw(requestParameters: ApiSendNestedObjectPartRequest, + initOverrides?: RequestInit | InitOverrideFunction): Promise { + if (requestParameters.body === undefined || requestParameters.body === null) { + throw new RequiredError( + 'body', + 'Required parameter "body" was null or undefined when calling sendNestedObjectPartRaw().' + ); + } + // Build path with path parameters + const urlPath = `/multipart/nested-object`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + }; + + // Prepare request body + const requestBody = new FormData(); + requestBody.append('file', requestParameters.body.file); + requestBody.append('item_config', JSON.stringify(requestParameters.body.item_config)); + // Make request + const response = await this.request({ + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: requestBody, + }, initOverrides); + + // Handle responses + if (response.status === 204) { + return new VoidApiResponse(response) as VoidApiResponse & { status: 204 }; + } + else { + return new VoidApiResponse(response) as VoidApiResponse & { status: number }; + } + } + + async sendNestedObjectPart(requestParameters: ApiSendNestedObjectPartRequest, + initOverrides?: RequestInit | InitOverrideFunction): Promise { + const response = await this.sendNestedObjectPartRaw(requestParameters, initOverrides); + return await response.value(); + } +} diff --git a/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/apis/index.ts.golden b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/apis/index.ts.golden new file mode 100644 index 000000000..cc93d072e --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/apis/index.ts.golden @@ -0,0 +1,12 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Nested Object Parts — 1.0.0 + * Covers multipart object parts whose wire names differ from ergonomic names. + */ +export type { + ApiSendNestedObjectPartRequest, + SendNestedObjectPartRawResponse, + MultipartApiInterface, +} from './MultipartApi'; +export { MultipartApi } from './MultipartApi'; diff --git a/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/index.ts.golden b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/index.ts.golden new file mode 100644 index 000000000..82c45cabd --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/index.ts.golden @@ -0,0 +1,9 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Nested Object Parts — 1.0.0 + * Covers multipart object parts whose wire names differ from ergonomic names. + */ +export * from './runtime/runtime'; +export * from './apis'; +export * from './models'; diff --git a/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/models/ItemConfig.ts.golden b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/models/ItemConfig.ts.golden new file mode 100644 index 000000000..ef5c21482 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/models/ItemConfig.ts.golden @@ -0,0 +1,10 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Nested Object Parts — 1.0.0 + * Covers multipart object parts whose wire names differ from ergonomic names. + */ +export interface ItemConfig { + readonly display_name: string; + readonly retention_days?: number; +} diff --git a/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/models/NestedUpload.ts.golden b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/models/NestedUpload.ts.golden new file mode 100644 index 000000000..da288ec0f --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/models/NestedUpload.ts.golden @@ -0,0 +1,12 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Nested Object Parts — 1.0.0 + * Covers multipart object parts whose wire names differ from ergonomic names. + */ +import type { ItemConfig } from './ItemConfig'; + +export interface NestedUpload { + readonly file: Blob | File; + readonly item_config: ItemConfig; +} diff --git a/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/models/index.ts.golden b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/models/index.ts.golden new file mode 100644 index 000000000..5c9c11b59 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/models/index.ts.golden @@ -0,0 +1,8 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Nested Object Parts — 1.0.0 + * Covers multipart object parts whose wire names differ from ergonomic names. + */ +export type { ItemConfig } from './ItemConfig'; +export type { NestedUpload } from './NestedUpload'; diff --git a/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/package.json.golden b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/package.json.golden new file mode 100644 index 000000000..adfcebda6 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/package.json.golden @@ -0,0 +1,26 @@ +{ + "description": "Covers multipart object parts whose wire names differ from ergonomic names.", + "exports": { + ".": { + "default": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "files": [ + "dist" + ], + "keywords": [ + "openapi", + "api-client", + "typescript", + "generated" + ], + "main": "./dist/index.js", + "name": "multipart-nested-object-parts", + "scripts": { + "build": "tsc" + }, + "type": "module", + "types": "./dist/index.d.ts", + "version": "1.0.0" +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/runtime/runtime.ts.golden new file mode 100644 index 000000000..be8df9abe --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/runtime/runtime.ts.golden @@ -0,0 +1,461 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Nested Object Parts — 1.0.0 + * Covers multipart object parts whose wire names differ from ergonomic names. + */ +export const BASE_PATH = "http://localhost".replace(/\/+$/, ""); + +export interface ConfigurationParameters { + basePath?: string; // override base path + fetchApi?: FetchAPI; // override for fetch implementation + middleware?: Middleware[]; // middleware to apply before/after fetch requests + queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings + username?: string | (() => string | Promise); // parameter for basic security + password?: string | (() => string | Promise); // parameter for basic security + apiKey?: string | Promise | ((name: string) => string | Promise); // parameter for apiKey security + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string | Promise); // parameter for oauth2 security + headers?: HTTPHeaders; //header params we want to use on every request + credentials?: RequestCredentials; //value for the credentials param we want to use on each request +} + +export class Configuration { + constructor(private configuration: ConfigurationParameters = {}) {} + + set config(configuration: Configuration) { + this.configuration = configuration; + } + + get basePath(): string { + return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH; + } + + get fetchApi(): FetchAPI | undefined { + return this.configuration.fetchApi; + } + + get middleware(): Middleware[] { + return this.configuration.middleware || []; + } + + get queryParamsStringify(): (params: HTTPQuery) => string { + return this.configuration.queryParamsStringify || querystring; + } + + get username(): (() => string | Promise) | undefined { + const username = this.configuration.username; + if (username) { + return typeof username === 'function' ? username : async () => username; + } + return undefined; + } + + get password(): (() => string | Promise) | undefined { + const password = this.configuration.password; + if (password) { + return typeof password === 'function' ? password : async () => password; + } + return undefined; + } + + get apiKey(): ((name: string) => string | Promise) | undefined { + const apiKey = this.configuration.apiKey; + if (apiKey) { + return typeof apiKey === 'function' ? apiKey : () => apiKey; + } + return undefined; + } + + get accessToken(): ((name?: string, scopes?: string[]) => string | Promise) | undefined { + const accessToken = this.configuration.accessToken; + if (accessToken) { + return typeof accessToken === 'function' ? accessToken : async () => accessToken; + } + return undefined; + } + + get headers(): HTTPHeaders | undefined { + return this.configuration.headers; + } + + get credentials(): RequestCredentials | undefined { + return this.configuration.credentials; + } +} + +export const DefaultConfig = new Configuration(); + +/** + * This is the base class for all generated API classes. + */ +export class BaseAPI { + + private static readonly jsonRegex = new RegExp('^(:?application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(:?;.*)?$', 'i'); + private middleware: Middleware[]; + + constructor(protected configuration = DefaultConfig) { + this.middleware = configuration.middleware; + } + + withMiddleware(this: T, ...middlewares: Middleware[]) { + const next = this.clone(); + next.middleware = next.middleware.concat(...middlewares); + return next; + } + + withPreMiddleware(this: T, ...preMiddlewares: Array) { + const middlewares = preMiddlewares.map((pre) => ({ pre })); + return this.withMiddleware(...middlewares); + } + + withPostMiddleware(this: T, ...postMiddlewares: Array) { + const middlewares = postMiddlewares.map((post) => ({ post })); + return this.withMiddleware(...middlewares); + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + protected isJsonMime(mime: string | null | undefined): boolean { + if (!mime) { + return false; + } + return BaseAPI.jsonRegex.test(mime); + } + + protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise { + const { url, init } = await this.createFetchParams(context, initOverrides); + const response = await this.fetchApi(url, init); + if (response && (response.status >= 200 && response.status < 300)) { + return response; + } + throw new ResponseError(response, 'Response returned an error code'); + } + + private async createFetchParams(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction) { + let url = this.configuration.basePath + context.path; + if (context.query !== undefined && Object.keys(context.query).length !== 0) { + // only add the querystring to the URL if there are query parameters. + // this is done to avoid urls ending with a "?" character which buggy webservers + // do not handle correctly sometimes. + url += '?' + this.configuration.queryParamsStringify(context.query); + } + + const headers = Object.assign({}, this.configuration.headers, context.headers); + Object.keys(headers).forEach(key => headers[key] === undefined ? delete headers[key] : {}); + + const token = await this.configuration.accessToken?.(); + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + if (!headers['Authorization']) { + const username = await this.configuration.username?.(); + const password = await this.configuration.password?.(); + if (username && password) { + headers['Authorization'] = `Basic ${btoa(`${username}:${password}`)}`; + } + } + + const initOverrideFn = + typeof initOverrides === "function" + ? initOverrides + : async () => initOverrides; + + const initParams: HTTPRequestInit = { + method: context.method, + headers, + body: context.body, + credentials: this.configuration.credentials, + signal: context.signal, + }; + + const overriddenInit = { + ...initParams, + ...(await initOverrideFn({ + init: initParams, + context, + })) + }; + + let body: BodyInit | null | undefined; + if (isFormData(overriddenInit.body) + || (overriddenInit.body instanceof URLSearchParams) + || isBlob(overriddenInit.body)) { + body = overriddenInit.body; + } else if (this.isJsonMime(headers['Content-Type'])) { + body = JSON.stringify(overriddenInit.body); + } else { + body = overriddenInit.body as BodyInit | null | undefined; + } + + const init: RequestInit = { + ...overriddenInit, + body + }; + + return { url, init }; + } + + private fetchApi = async (url: string, init: RequestInit) => { + let fetchParams = { url, init }; + for (const middleware of this.middleware) { + if (middleware.pre) { + fetchParams = await middleware.pre({ + fetch: this.fetchApi, + ...fetchParams, + }) || fetchParams; + } + } + let response: Response | undefined = undefined; + try { + response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init); + } catch (e) { + for (const middleware of this.middleware) { + if (middleware.onError) { + response = await middleware.onError({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + error: e, + response: response ? response.clone() : undefined, + }) || response; + } + } + if (response === undefined) { + if (e instanceof Error) { + throw new FetchError(e, 'The request failed and the interceptors did not return an alternative response'); + } else { + throw e; + } + } + } + for (const middleware of this.middleware) { + if (middleware.post) { + response = await middleware.post({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + response: response.clone(), + }) || response; + } + } + return response; + } + + /** + * Create a shallow clone of `this` by constructing a new instance + * and then shallow cloning data members. + */ + private clone(this: T): T { + const constructor = this.constructor as new (configuration: Configuration) => T; + const next = new constructor(this.configuration); + next.middleware = this.middleware.slice(); + return next; + } +} + +function isBlob(value: unknown): value is Blob { + return typeof Blob !== 'undefined' && value instanceof Blob; +} + +function isFormData(value: unknown): value is FormData { + return typeof FormData !== "undefined" && value instanceof FormData; +} + +export class ResponseError extends Error { + override name = "ResponseError" as const; + constructor(public response: Response, msg?: string) { + super(msg); + } +} + +export class FetchError extends Error { + override name = "FetchError" as const; + constructor(public cause: Error, msg?: string) { + super(msg); + } +} + +export class RequiredError extends Error { + override name = "RequiredError" as const; + constructor(public field: string, msg?: string) { + super(msg); + } +} + +export type FetchAPI = WindowOrWorkerGlobalScope['fetch']; + +export type Json = unknown; +export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; +export type HTTPHeaders = { [key: string]: string }; +export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; +export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; + +export type InitOverrideFunction = (requestContext: { init: HTTPRequestInit, context: RequestOpts }) => Promise + +export interface FetchParams { + url: string; + init: RequestInit; +} + +export interface RequestOpts { + path: string; + method: HTTPMethod; + headers: HTTPHeaders; + query?: HTTPQuery; + body?: HTTPBody; + signal?: AbortSignal; +} + +export function querystring(params: HTTPQuery, prefix: string = ''): string { + return Object.keys(params) + .map(key => querystringSingleKey(key, params[key], prefix)) + .filter(part => part.length > 0) + .join('&'); +} + +function querystringSingleKey(key: string, value: string | number | null | undefined | boolean | Array | Set | HTTPQuery, keyPrefix: string = ''): string { + const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key); + if (value instanceof Array) { + const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue))) + .join(`&${encodeURIComponent(fullKey)}=`); + return `${encodeURIComponent(fullKey)}=${multiValue}`; + } + if (value instanceof Set) { + const valueAsArray = Array.from(value); + return querystringSingleKey(key, valueAsArray, keyPrefix); + } + if (value instanceof Date) { + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`; + } + if (value instanceof Object) { + return querystring(value as HTTPQuery, fullKey); + } + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`; +} + +export function exists(json: Record, key: string): boolean { + const value = json[key]; + return value !== null && value !== undefined; +} + +export function mapValues(data: Record, fn: (item: T) => U): Record { + const result: Record = {}; + for (const key of Object.keys(data)) { + result[key] = fn(data[key]); + } + return result; +} + +export function canConsumeForm(consumes: Consume[]): boolean { + for (const consume of consumes) { + if ('multipart/form-data' === consume.contentType) { + return true; + } + } + return false; +} + +export interface Consume { + contentType: string; +} + +export interface RequestContext { + fetch: FetchAPI; + url: string; + init: RequestInit; +} + +export interface ResponseContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + response: Response; +} + +export interface ErrorContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + error: unknown; + response?: Response; +} + +export interface Middleware { + pre?(context: RequestContext): Promise; + post?(context: ResponseContext): Promise; + onError?(context: ErrorContext): Promise; +} + +export interface ApiResponse { + raw: Response; + value(): Promise; +} + +export interface ResponseTransformer { + (json: unknown): T; +} + +class ApiResponseBase { + public readonly status: number; + public readonly ok: boolean; + public readonly statusText: string; + public readonly headers: Headers; + + constructor(public readonly raw: Response) { + this.status = raw.status; + this.ok = raw.ok; + this.statusText = raw.statusText; + this.headers = raw.headers; + } +} + +export class JSONApiResponse extends ApiResponseBase { + constructor(raw: Response, private transformer: ResponseTransformer = (jsonValue: unknown) => jsonValue as T) { + super(raw); + } + + async value(): Promise { + return this.transformer(await this.raw.json()); + } +} + +export class VoidApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return undefined; + } +} + +export class BlobApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return await this.raw.blob(); + }; +} + +export class TextApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return await this.raw.text(); + }; +} diff --git a/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/tsconfig.esm.json.golden b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/tsconfig.esm.json.golden new file mode 100644 index 000000000..bb8350cb6 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/tsconfig.esm.json.golden @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "ES2020", + "outDir": "dist/esm" + }, + "extends": "./tsconfig.json" +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/tsconfig.json.golden b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/tsconfig.json.golden new file mode 100644 index 000000000..4b3a8cf7f --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-nested-object-parts/tsconfig.json.golden @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "lib": [ + "ES2020", + "DOM" + ], + "module": "ES2020", + "moduleResolution": "bundler", + "outDir": "./dist", + "resolveJsonModule": true, + "rootDir": "./", + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "target": "ES2020", + "typeRoots": [ + "node_modules/@types" + ] + }, + "exclude": [ + "dist", + "node_modules" + ], + "include": [ + "**/*.ts" + ] +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/README.md.golden b/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/README.md.golden new file mode 100644 index 000000000..e3a75c849 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/README.md.golden @@ -0,0 +1,248 @@ +# multipart-unsupported-schema + +Covers multipart request bodies that are not object-shaped. + +**Version:** 1.0.0 + +## Overview + +This package provides a TypeScript/JavaScript client for the Multipart Unsupported Schema API. It uses the native [Fetch API](https://fetch.spec.whatwg.org/) for HTTP requests and works in both Node.js and browser environments. + +## Features + +- ✨ **Type-safe** - Full TypeScript support with generated types +- 🚀 **Modern** - Uses native Fetch API, no external HTTP dependencies +- 🔧 **Configurable** - Flexible configuration options +- 🎯 **Middleware** - Support for request/response interceptors +- 📦 **Tree-shakeable** - Import only what you need +- 🌐 **Universal** - Works in Node.js and browsers + +## Installation + +### From npm (published package) + +```bash +npm install multipart-unsupported-schema +``` + +### From local path (development) + +Add the package to your `package.json` using the `file:` protocol: + +```json +{ + "dependencies": { + "multipart-unsupported-schema": "file:../../path/to/generated/package" + } +} +``` + +Then run: + +```bash +npm install +``` + +## Quick Start + +```typescript +import { Configuration, TransferApi } from 'multipart-unsupported-schema'; + +// Create a configuration +const config = new Configuration({ + basePath: 'https://api.example.com', + headers: { + 'Authorization': 'Bearer YOUR_TOKEN' + } +}); + +// Initialize the API client +const api = new TransferApi(config); + +// Make API calls +try { + const result = await api.someMethod(); + console.log(result); +} catch (error) { + console.error('API Error:', error); +} +``` + +## Configuration + +The `Configuration` class accepts the following options: + +```typescript +interface ConfigurationParameters { + /** Base URL for API requests */ + basePath?: string; + + /** Custom fetch implementation */ + fetchApi?: typeof fetch; + + /** Request/response middleware */ + middleware?: Middleware[]; + + /** Custom query string serializer */ + queryParamsStringify?: (params: HTTPQuery) => string; + + /** Default headers for all requests */ + headers?: Record; + + /** Credentials mode for requests */ + credentials?: RequestCredentials; +} +``` + +### Example with custom configuration + +```typescript +const config = new Configuration({ + basePath: 'https://api.example.com', + headers: { + 'X-API-Key': 'your-api-key', + 'Content-Type': 'application/json' + }, + credentials: 'include' +}); +``` + +## Middleware + +Add custom middleware to intercept requests and responses: + +```typescript +import { Configuration, Middleware } from 'multipart-unsupported-schema'; + +const loggingMiddleware: Middleware = { + pre: async (context) => { + console.log('Request:', context.url); + return context; + }, + post: async (context) => { + console.log('Response:', context.response.status); + return context.response; + }, + onError: async (context) => { + console.error('Error:', context.error); + return undefined; + } +}; + +const config = new Configuration({ + basePath: 'https://api.example.com', + middleware: [loggingMiddleware] +}); +``` + +## Error Handling + +The client throws typed errors for different failure scenarios: + +```typescript +import { ResponseError, FetchError, RequiredError } from 'multipart-unsupported-schema'; + +try { + const result = await api.someMethod(); +} catch (error) { + if (error instanceof ResponseError) { + // HTTP error response (4xx, 5xx) + console.error('HTTP Error:', error.response.status); + } else if (error instanceof FetchError) { + // Network or fetch error + console.error('Network Error:', error.cause); + } else if (error instanceof RequiredError) { + // Missing required parameter + console.error('Missing field:', error.field); + } +} +``` + +## API Reference + +This package exports the following: + +- **Configuration** - Client configuration class +- **BaseAPI** - Base class for all API clients +- **API Classes** - Generated API client classes (e.g., `UserApi`, `PostApi`) +- **Models** - Generated TypeScript interfaces for request/response types +- **Errors** - `ResponseError`, `FetchError`, `RequiredError` +- **Types** - TypeScript type definitions + +## Development + +### Building + +To build the package: + +```bash +npm install +npm run build +``` + +This will compile TypeScript to JavaScript in the `dist/` directory. + +### Building for ESM + +To build ES modules: + +```bash +npm run build +``` + +## TypeScript Support + +This package includes TypeScript type definitions. No additional `@types` package is needed. + +### TypeScript Configuration + +This package works with standard TypeScript configurations. If you're using a bundler-based setup, you may want to configure: + +```json +{ + "compilerOptions": { + "moduleResolution": "bundler" + } +} +``` + +### Type Imports + +```typescript +import type { User, CreateUserRequest } from 'multipart-unsupported-schema'; + +const user: User = { + id: 1, + name: 'John Doe', + email: 'john@example.com' +}; +``` + +## Browser Support + +This package uses the native Fetch API, which is supported in: + +- Chrome 42+ +- Firefox 39+ +- Safari 10.1+ +- Edge 14+ +- Node.js 18+ (native fetch) +- Node.js <18 (with `node-fetch` polyfill) + +For older browsers, you may need to include a fetch polyfill. + +## License + +This is an auto-generated API client. Please refer to your API documentation for license information. + +## Support + +For issues related to the API itself, please contact the API provider. + +For issues with this generated client, please check the OpenAPI specification used to generate it. + +--- + +**Generated by OpenAPI Generator** + +API Version: 1.0.0 diff --git a/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/apis/TransferApi.ts.golden b/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/apis/TransferApi.ts.golden new file mode 100644 index 000000000..12472b94f --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/apis/TransferApi.ts.golden @@ -0,0 +1,73 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Unsupported Schema — 1.0.0 + * Covers multipart request bodies that are not object-shaped. + */ +import type { Configuration, HTTPQuery, InitOverrideFunction } from '../runtime/runtime'; +import { BaseAPI, DefaultConfig, RequiredError, VoidApiResponse } from '../runtime/runtime'; + +export interface ApiUploadRawMultipartRequest { + body: Blob | File; +} + +export type UploadRawMultipartRawResponse = + | VoidApiResponse & { status: 204 } + | VoidApiResponse & { status: number }; + + +export interface TransferApiInterface { + uploadRawMultipartRaw: (requestParameters: ApiUploadRawMultipartRequest, initOverrides?: RequestInit | InitOverrideFunction) => Promise; + uploadRawMultipart: (requestParameters: ApiUploadRawMultipartRequest, initOverrides?: RequestInit | InitOverrideFunction) => Promise; +} + +export class TransferApi extends BaseAPI implements TransferApiInterface { + /** + * Initialize the API client + */ + constructor(configuration?: Configuration) { + super(configuration ?? DefaultConfig); + } + + async uploadRawMultipartRaw(requestParameters: ApiUploadRawMultipartRequest, + initOverrides?: RequestInit | InitOverrideFunction): Promise { + if (requestParameters.body === undefined || requestParameters.body === null) { + throw new RequiredError( + 'body', + 'Required parameter "body" was null or undefined when calling uploadRawMultipartRaw().' + ); + } + // Build path with path parameters + const urlPath = `/uploads/raw`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + }; + + // Prepare request body + const requestBody = (() => {throw new Error('unsupported multipart request body: schema must be object-shaped');})(); + // Make request + const response = await this.request({ + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: requestBody, + }, initOverrides); + + // Handle responses + if (response.status === 204) { + return new VoidApiResponse(response) as VoidApiResponse & { status: 204 }; + } + else { + return new VoidApiResponse(response) as VoidApiResponse & { status: number }; + } + } + + async uploadRawMultipart(requestParameters: ApiUploadRawMultipartRequest, + initOverrides?: RequestInit | InitOverrideFunction): Promise { + const response = await this.uploadRawMultipartRaw(requestParameters, initOverrides); + return await response.value(); + } +} diff --git a/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/apis/index.ts.golden b/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/apis/index.ts.golden new file mode 100644 index 000000000..ba2f50a93 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/apis/index.ts.golden @@ -0,0 +1,12 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Unsupported Schema — 1.0.0 + * Covers multipart request bodies that are not object-shaped. + */ +export type { + ApiUploadRawMultipartRequest, + UploadRawMultipartRawResponse, + TransferApiInterface, +} from './TransferApi'; +export { TransferApi } from './TransferApi'; diff --git a/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/index.ts.golden b/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/index.ts.golden new file mode 100644 index 000000000..dc6e1b1e5 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/index.ts.golden @@ -0,0 +1,8 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Unsupported Schema — 1.0.0 + * Covers multipart request bodies that are not object-shaped. + */ +export * from './runtime/runtime'; +export * from './apis'; diff --git a/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/package.json.golden b/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/package.json.golden new file mode 100644 index 000000000..82e0f3267 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/package.json.golden @@ -0,0 +1,26 @@ +{ + "description": "Covers multipart request bodies that are not object-shaped.", + "exports": { + ".": { + "default": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "files": [ + "dist" + ], + "keywords": [ + "openapi", + "api-client", + "typescript", + "generated" + ], + "main": "./dist/index.js", + "name": "multipart-unsupported-schema", + "scripts": { + "build": "tsc" + }, + "type": "module", + "types": "./dist/index.d.ts", + "version": "1.0.0" +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/runtime/runtime.ts.golden new file mode 100644 index 000000000..fd1f19018 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/runtime/runtime.ts.golden @@ -0,0 +1,461 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Unsupported Schema — 1.0.0 + * Covers multipart request bodies that are not object-shaped. + */ +export const BASE_PATH = "http://localhost".replace(/\/+$/, ""); + +export interface ConfigurationParameters { + basePath?: string; // override base path + fetchApi?: FetchAPI; // override for fetch implementation + middleware?: Middleware[]; // middleware to apply before/after fetch requests + queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings + username?: string | (() => string | Promise); // parameter for basic security + password?: string | (() => string | Promise); // parameter for basic security + apiKey?: string | Promise | ((name: string) => string | Promise); // parameter for apiKey security + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string | Promise); // parameter for oauth2 security + headers?: HTTPHeaders; //header params we want to use on every request + credentials?: RequestCredentials; //value for the credentials param we want to use on each request +} + +export class Configuration { + constructor(private configuration: ConfigurationParameters = {}) {} + + set config(configuration: Configuration) { + this.configuration = configuration; + } + + get basePath(): string { + return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH; + } + + get fetchApi(): FetchAPI | undefined { + return this.configuration.fetchApi; + } + + get middleware(): Middleware[] { + return this.configuration.middleware || []; + } + + get queryParamsStringify(): (params: HTTPQuery) => string { + return this.configuration.queryParamsStringify || querystring; + } + + get username(): (() => string | Promise) | undefined { + const username = this.configuration.username; + if (username) { + return typeof username === 'function' ? username : async () => username; + } + return undefined; + } + + get password(): (() => string | Promise) | undefined { + const password = this.configuration.password; + if (password) { + return typeof password === 'function' ? password : async () => password; + } + return undefined; + } + + get apiKey(): ((name: string) => string | Promise) | undefined { + const apiKey = this.configuration.apiKey; + if (apiKey) { + return typeof apiKey === 'function' ? apiKey : () => apiKey; + } + return undefined; + } + + get accessToken(): ((name?: string, scopes?: string[]) => string | Promise) | undefined { + const accessToken = this.configuration.accessToken; + if (accessToken) { + return typeof accessToken === 'function' ? accessToken : async () => accessToken; + } + return undefined; + } + + get headers(): HTTPHeaders | undefined { + return this.configuration.headers; + } + + get credentials(): RequestCredentials | undefined { + return this.configuration.credentials; + } +} + +export const DefaultConfig = new Configuration(); + +/** + * This is the base class for all generated API classes. + */ +export class BaseAPI { + + private static readonly jsonRegex = new RegExp('^(:?application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(:?;.*)?$', 'i'); + private middleware: Middleware[]; + + constructor(protected configuration = DefaultConfig) { + this.middleware = configuration.middleware; + } + + withMiddleware(this: T, ...middlewares: Middleware[]) { + const next = this.clone(); + next.middleware = next.middleware.concat(...middlewares); + return next; + } + + withPreMiddleware(this: T, ...preMiddlewares: Array) { + const middlewares = preMiddlewares.map((pre) => ({ pre })); + return this.withMiddleware(...middlewares); + } + + withPostMiddleware(this: T, ...postMiddlewares: Array) { + const middlewares = postMiddlewares.map((post) => ({ post })); + return this.withMiddleware(...middlewares); + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + protected isJsonMime(mime: string | null | undefined): boolean { + if (!mime) { + return false; + } + return BaseAPI.jsonRegex.test(mime); + } + + protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise { + const { url, init } = await this.createFetchParams(context, initOverrides); + const response = await this.fetchApi(url, init); + if (response && (response.status >= 200 && response.status < 300)) { + return response; + } + throw new ResponseError(response, 'Response returned an error code'); + } + + private async createFetchParams(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction) { + let url = this.configuration.basePath + context.path; + if (context.query !== undefined && Object.keys(context.query).length !== 0) { + // only add the querystring to the URL if there are query parameters. + // this is done to avoid urls ending with a "?" character which buggy webservers + // do not handle correctly sometimes. + url += '?' + this.configuration.queryParamsStringify(context.query); + } + + const headers = Object.assign({}, this.configuration.headers, context.headers); + Object.keys(headers).forEach(key => headers[key] === undefined ? delete headers[key] : {}); + + const token = await this.configuration.accessToken?.(); + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + if (!headers['Authorization']) { + const username = await this.configuration.username?.(); + const password = await this.configuration.password?.(); + if (username && password) { + headers['Authorization'] = `Basic ${btoa(`${username}:${password}`)}`; + } + } + + const initOverrideFn = + typeof initOverrides === "function" + ? initOverrides + : async () => initOverrides; + + const initParams: HTTPRequestInit = { + method: context.method, + headers, + body: context.body, + credentials: this.configuration.credentials, + signal: context.signal, + }; + + const overriddenInit = { + ...initParams, + ...(await initOverrideFn({ + init: initParams, + context, + })) + }; + + let body: BodyInit | null | undefined; + if (isFormData(overriddenInit.body) + || (overriddenInit.body instanceof URLSearchParams) + || isBlob(overriddenInit.body)) { + body = overriddenInit.body; + } else if (this.isJsonMime(headers['Content-Type'])) { + body = JSON.stringify(overriddenInit.body); + } else { + body = overriddenInit.body as BodyInit | null | undefined; + } + + const init: RequestInit = { + ...overriddenInit, + body + }; + + return { url, init }; + } + + private fetchApi = async (url: string, init: RequestInit) => { + let fetchParams = { url, init }; + for (const middleware of this.middleware) { + if (middleware.pre) { + fetchParams = await middleware.pre({ + fetch: this.fetchApi, + ...fetchParams, + }) || fetchParams; + } + } + let response: Response | undefined = undefined; + try { + response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init); + } catch (e) { + for (const middleware of this.middleware) { + if (middleware.onError) { + response = await middleware.onError({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + error: e, + response: response ? response.clone() : undefined, + }) || response; + } + } + if (response === undefined) { + if (e instanceof Error) { + throw new FetchError(e, 'The request failed and the interceptors did not return an alternative response'); + } else { + throw e; + } + } + } + for (const middleware of this.middleware) { + if (middleware.post) { + response = await middleware.post({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + response: response.clone(), + }) || response; + } + } + return response; + } + + /** + * Create a shallow clone of `this` by constructing a new instance + * and then shallow cloning data members. + */ + private clone(this: T): T { + const constructor = this.constructor as new (configuration: Configuration) => T; + const next = new constructor(this.configuration); + next.middleware = this.middleware.slice(); + return next; + } +} + +function isBlob(value: unknown): value is Blob { + return typeof Blob !== 'undefined' && value instanceof Blob; +} + +function isFormData(value: unknown): value is FormData { + return typeof FormData !== "undefined" && value instanceof FormData; +} + +export class ResponseError extends Error { + override name = "ResponseError" as const; + constructor(public response: Response, msg?: string) { + super(msg); + } +} + +export class FetchError extends Error { + override name = "FetchError" as const; + constructor(public cause: Error, msg?: string) { + super(msg); + } +} + +export class RequiredError extends Error { + override name = "RequiredError" as const; + constructor(public field: string, msg?: string) { + super(msg); + } +} + +export type FetchAPI = WindowOrWorkerGlobalScope['fetch']; + +export type Json = unknown; +export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; +export type HTTPHeaders = { [key: string]: string }; +export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; +export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; + +export type InitOverrideFunction = (requestContext: { init: HTTPRequestInit, context: RequestOpts }) => Promise + +export interface FetchParams { + url: string; + init: RequestInit; +} + +export interface RequestOpts { + path: string; + method: HTTPMethod; + headers: HTTPHeaders; + query?: HTTPQuery; + body?: HTTPBody; + signal?: AbortSignal; +} + +export function querystring(params: HTTPQuery, prefix: string = ''): string { + return Object.keys(params) + .map(key => querystringSingleKey(key, params[key], prefix)) + .filter(part => part.length > 0) + .join('&'); +} + +function querystringSingleKey(key: string, value: string | number | null | undefined | boolean | Array | Set | HTTPQuery, keyPrefix: string = ''): string { + const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key); + if (value instanceof Array) { + const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue))) + .join(`&${encodeURIComponent(fullKey)}=`); + return `${encodeURIComponent(fullKey)}=${multiValue}`; + } + if (value instanceof Set) { + const valueAsArray = Array.from(value); + return querystringSingleKey(key, valueAsArray, keyPrefix); + } + if (value instanceof Date) { + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`; + } + if (value instanceof Object) { + return querystring(value as HTTPQuery, fullKey); + } + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`; +} + +export function exists(json: Record, key: string): boolean { + const value = json[key]; + return value !== null && value !== undefined; +} + +export function mapValues(data: Record, fn: (item: T) => U): Record { + const result: Record = {}; + for (const key of Object.keys(data)) { + result[key] = fn(data[key]); + } + return result; +} + +export function canConsumeForm(consumes: Consume[]): boolean { + for (const consume of consumes) { + if ('multipart/form-data' === consume.contentType) { + return true; + } + } + return false; +} + +export interface Consume { + contentType: string; +} + +export interface RequestContext { + fetch: FetchAPI; + url: string; + init: RequestInit; +} + +export interface ResponseContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + response: Response; +} + +export interface ErrorContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + error: unknown; + response?: Response; +} + +export interface Middleware { + pre?(context: RequestContext): Promise; + post?(context: ResponseContext): Promise; + onError?(context: ErrorContext): Promise; +} + +export interface ApiResponse { + raw: Response; + value(): Promise; +} + +export interface ResponseTransformer { + (json: unknown): T; +} + +class ApiResponseBase { + public readonly status: number; + public readonly ok: boolean; + public readonly statusText: string; + public readonly headers: Headers; + + constructor(public readonly raw: Response) { + this.status = raw.status; + this.ok = raw.ok; + this.statusText = raw.statusText; + this.headers = raw.headers; + } +} + +export class JSONApiResponse extends ApiResponseBase { + constructor(raw: Response, private transformer: ResponseTransformer = (jsonValue: unknown) => jsonValue as T) { + super(raw); + } + + async value(): Promise { + return this.transformer(await this.raw.json()); + } +} + +export class VoidApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return undefined; + } +} + +export class BlobApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return await this.raw.blob(); + }; +} + +export class TextApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return await this.raw.text(); + }; +} diff --git a/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/tsconfig.esm.json.golden b/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/tsconfig.esm.json.golden new file mode 100644 index 000000000..bb8350cb6 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/tsconfig.esm.json.golden @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "ES2020", + "outDir": "dist/esm" + }, + "extends": "./tsconfig.json" +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/tsconfig.json.golden b/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/tsconfig.json.golden new file mode 100644 index 000000000..4b3a8cf7f --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/multipart-unsupported-schema/tsconfig.json.golden @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "lib": [ + "ES2020", + "DOM" + ], + "module": "ES2020", + "moduleResolution": "bundler", + "outDir": "./dist", + "resolveJsonModule": true, + "rootDir": "./", + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "target": "ES2020", + "typeRoots": [ + "node_modules/@types" + ] + }, + "exclude": [ + "dist", + "node_modules" + ], + "include": [ + "**/*.ts" + ] +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/multiple-similar-request-schemas/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/multiple-similar-request-schemas/runtime/runtime.ts.golden index bc6e24342..14a167b8b 100644 --- a/tests/golden/typescript/typescript-fetch/multiple-similar-request-schemas/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/multiple-similar-request-schemas/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/naming-conventions/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/naming-conventions/runtime/runtime.ts.golden index 73a38f705..649999855 100644 --- a/tests/golden/typescript/typescript-fetch/naming-conventions/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/naming-conventions/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/petstore/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/petstore/runtime/runtime.ts.golden index 6da4cd16f..1f9dffc9c 100644 --- a/tests/golden/typescript/typescript-fetch/petstore/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/petstore/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/query-param-enum/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/query-param-enum/runtime/runtime.ts.golden index 692a66df9..dbc1f7349 100644 --- a/tests/golden/typescript/typescript-fetch/query-param-enum/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/query-param-enum/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/recursive-json-all-optional-properties/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/recursive-json-all-optional-properties/runtime/runtime.ts.golden index 566581bc4..93140f7fb 100644 --- a/tests/golden/typescript/typescript-fetch/recursive-json-all-optional-properties/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/recursive-json-all-optional-properties/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/recursive-json-array-of-inline-objects/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/recursive-json-array-of-inline-objects/runtime/runtime.ts.golden index eefa3fa04..7b722e56a 100644 --- a/tests/golden/typescript/typescript-fetch/recursive-json-array-of-inline-objects/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/recursive-json-array-of-inline-objects/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/recursive-json-array-of-referenced-types/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/recursive-json-array-of-referenced-types/runtime/runtime.ts.golden index 72354008f..6ed1c8d61 100644 --- a/tests/golden/typescript/typescript-fetch/recursive-json-array-of-referenced-types/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/recursive-json-array-of-referenced-types/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/recursive-json-array-with-reference-property/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/recursive-json-array-with-reference-property/runtime/runtime.ts.golden index c037280d9..669907a1f 100644 --- a/tests/golden/typescript/typescript-fetch/recursive-json-array-with-reference-property/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/recursive-json-array-with-reference-property/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/recursive-json-complex-array-structure/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/recursive-json-complex-array-structure/runtime/runtime.ts.golden index 98528e322..c361c3614 100644 --- a/tests/golden/typescript/typescript-fetch/recursive-json-complex-array-structure/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/recursive-json-complex-array-structure/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/recursive-json-deeply-nested-inline/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/recursive-json-deeply-nested-inline/runtime/runtime.ts.golden index 03d10b88c..eaaa0add8 100644 --- a/tests/golden/typescript/typescript-fetch/recursive-json-deeply-nested-inline/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/recursive-json-deeply-nested-inline/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/recursive-json-empty-array/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/recursive-json-empty-array/runtime/runtime.ts.golden index 02b0731d6..2ad3a6caf 100644 --- a/tests/golden/typescript/typescript-fetch/recursive-json-empty-array/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/recursive-json-empty-array/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/recursive-json-inline-object-with-array/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/recursive-json-inline-object-with-array/runtime/runtime.ts.golden index a7e97a574..5c69f794d 100644 --- a/tests/golden/typescript/typescript-fetch/recursive-json-inline-object-with-array/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/recursive-json-inline-object-with-array/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/recursive-json-inline-object/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/recursive-json-inline-object/runtime/runtime.ts.golden index 3915b4250..e789fd432 100644 --- a/tests/golden/typescript/typescript-fetch/recursive-json-inline-object/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/recursive-json-inline-object/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/recursive-json-mixed-property-types/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/recursive-json-mixed-property-types/runtime/runtime.ts.golden index 2ff898df0..2795ece7b 100644 --- a/tests/golden/typescript/typescript-fetch/recursive-json-mixed-property-types/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/recursive-json-mixed-property-types/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/recursive-json-nested-object-reference/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/recursive-json-nested-object-reference/runtime/runtime.ts.golden index c00a077da..286c06c71 100644 --- a/tests/golden/typescript/typescript-fetch/recursive-json-nested-object-reference/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/recursive-json-nested-object-reference/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/recursive-json-optional-array-of-inline-objects/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/recursive-json-optional-array-of-inline-objects/runtime/runtime.ts.golden index 79b54ab30..11f8cc3a2 100644 --- a/tests/golden/typescript/typescript-fetch/recursive-json-optional-array-of-inline-objects/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/recursive-json-optional-array-of-inline-objects/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/recursive-json-optional-array-of-referenced-types/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/recursive-json-optional-array-of-referenced-types/runtime/runtime.ts.golden index 740c36ca7..17e569c95 100644 --- a/tests/golden/typescript/typescript-fetch/recursive-json-optional-array-of-referenced-types/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/recursive-json-optional-array-of-referenced-types/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/recursive-json-optional-inline-object/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/recursive-json-optional-inline-object/runtime/runtime.ts.golden index 9a9c7400b..02833e530 100644 --- a/tests/golden/typescript/typescript-fetch/recursive-json-optional-inline-object/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/recursive-json-optional-inline-object/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/recursive-json-optional-nested-object-reference/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/recursive-json-optional-nested-object-reference/runtime/runtime.ts.golden index 403886bfd..e1737c209 100644 --- a/tests/golden/typescript/typescript-fetch/recursive-json-optional-nested-object-reference/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/recursive-json-optional-nested-object-reference/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/recursive-json-primitive-array/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/recursive-json-primitive-array/runtime/runtime.ts.golden index a1568c437..0fa6f4a44 100644 --- a/tests/golden/typescript/typescript-fetch/recursive-json-primitive-array/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/recursive-json-primitive-array/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/request-body-content-types/apis/DefaultApi.ts.golden b/tests/golden/typescript/typescript-fetch/request-body-content-types/apis/DefaultApi.ts.golden index d7561c3d3..4dac7db10 100644 --- a/tests/golden/typescript/typescript-fetch/request-body-content-types/apis/DefaultApi.ts.golden +++ b/tests/golden/typescript/typescript-fetch/request-body-content-types/apis/DefaultApi.ts.golden @@ -9,7 +9,7 @@ import type { Configuration, HTTPQuery, InitOverrideFunction } from '../runtime/ import { BaseAPI, DefaultConfig, JSONApiResponse, RequiredError, TextApiResponse, VoidApiResponse } from '../runtime/runtime'; export interface ApiPostBinaryRequest { - body: string; + body: Blob | File; } export interface ApiPostFormRequest { @@ -157,7 +157,7 @@ export class DefaultApi extends BaseAPI implements DefaultApiInterface { }; // Prepare request body - const requestBody = requestParameters.body; + const requestBody = (() => {throw new Error('unsupported request body media type: application/x-www-form-urlencoded');})(); // Make request const response = await this.request({ path: urlPath, @@ -372,7 +372,7 @@ export class DefaultApi extends BaseAPI implements DefaultApiInterface { }; // Prepare request body - const requestBody = requestParameters.body; + const requestBody = (() => {throw new Error('unsupported request body media type: application/xml');})(); // Make request const response = await this.request({ path: urlPath, diff --git a/tests/golden/typescript/typescript-fetch/request-body-content-types/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/request-body-content-types/runtime/runtime.ts.golden index 9c912f5e4..c2ae91d32 100644 --- a/tests/golden/typescript/typescript-fetch/request-body-content-types/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/request-body-content-types/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/response-body-default-and-exact/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/response-body-default-and-exact/runtime/runtime.ts.golden index 4b9682d88..c7b7b0213 100644 --- a/tests/golden/typescript/typescript-fetch/response-body-default-and-exact/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/response-body-default-and-exact/runtime/runtime.ts.golden @@ -296,7 +296,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/response-body-fallback/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/response-body-fallback/runtime/runtime.ts.golden index a3cfb5d18..87a7d6f4e 100644 --- a/tests/golden/typescript/typescript-fetch/response-body-fallback/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/response-body-fallback/runtime/runtime.ts.golden @@ -296,7 +296,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/response-body-multi-status-responses/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/response-body-multi-status-responses/runtime/runtime.ts.golden index debac6e28..4f58994f4 100644 --- a/tests/golden/typescript/typescript-fetch/response-body-multi-status-responses/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/response-body-multi-status-responses/runtime/runtime.ts.golden @@ -296,7 +296,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/response-body-no-response-body/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/response-body-no-response-body/runtime/runtime.ts.golden index d28de9f3d..0e1c6c312 100644 --- a/tests/golden/typescript/typescript-fetch/response-body-no-response-body/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/response-body-no-response-body/runtime/runtime.ts.golden @@ -296,7 +296,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/server-object/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/server-object/runtime/runtime.ts.golden index 1d2b1b35e..f2907d41f 100644 --- a/tests/golden/typescript/typescript-fetch/server-object/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/server-object/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/server-path-prefix/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/server-path-prefix/runtime/runtime.ts.golden index 3100eee15..211e5edce 100644 --- a/tests/golden/typescript/typescript-fetch/server-path-prefix/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/server-path-prefix/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/ts-enum-const/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/ts-enum-const/runtime/runtime.ts.golden index 15ea19181..b1fb60468 100644 --- a/tests/golden/typescript/typescript-fetch/ts-enum-const/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/ts-enum-const/runtime/runtime.ts.golden @@ -296,7 +296,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-enum-ref/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-enum-ref/runtime/runtime.ts.golden index 1c8d117f9..025c8a969 100644 --- a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-enum-ref/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-enum-ref/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-externally-tagged/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-externally-tagged/runtime/runtime.ts.golden index 764c0eabf..f382a0821 100644 --- a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-externally-tagged/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-externally-tagged/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-intersection-enum-ref/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-intersection-enum-ref/runtime/runtime.ts.golden index aad01b7a0..5900a9872 100644 --- a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-intersection-enum-ref/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-intersection-enum-ref/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-intersection/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-intersection/runtime/runtime.ts.golden index 98f1acf32..d96978125 100644 --- a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-intersection/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-intersection/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/README.md.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/README.md.golden new file mode 100644 index 000000000..cde8d2ef6 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/README.md.golden @@ -0,0 +1,248 @@ +# multipart-nested-object-parts + +Covers multipart object parts whose wire names differ from ergonomic names. + +**Version:** 1.0.0 + +## Overview + +This package provides a TypeScript/JavaScript client for the Multipart Nested Object Parts API. It uses the native [Fetch API](https://fetch.spec.whatwg.org/) for HTTP requests and works in both Node.js and browser environments. + +## Features + +- ✨ **Type-safe** - Full TypeScript support with generated types +- 🚀 **Modern** - Uses native Fetch API, no external HTTP dependencies +- 🔧 **Configurable** - Flexible configuration options +- 🎯 **Middleware** - Support for request/response interceptors +- 📦 **Tree-shakeable** - Import only what you need +- 🌐 **Universal** - Works in Node.js and browsers + +## Installation + +### From npm (published package) + +```bash +npm install multipart-nested-object-parts +``` + +### From local path (development) + +Add the package to your `package.json` using the `file:` protocol: + +```json +{ + "dependencies": { + "multipart-nested-object-parts": "file:../../path/to/generated/package" + } +} +``` + +Then run: + +```bash +npm install +``` + +## Quick Start + +```typescript +import { Configuration, MultipartApi } from 'multipart-nested-object-parts'; + +// Create a configuration +const config = new Configuration({ + basePath: 'https://api.example.com', + headers: { + 'Authorization': 'Bearer YOUR_TOKEN' + } +}); + +// Initialize the API client +const api = new MultipartApi(config); + +// Make API calls +try { + const result = await api.someMethod(); + console.log(result); +} catch (error) { + console.error('API Error:', error); +} +``` + +## Configuration + +The `Configuration` class accepts the following options: + +```typescript +interface ConfigurationParameters { + /** Base URL for API requests */ + basePath?: string; + + /** Custom fetch implementation */ + fetchApi?: typeof fetch; + + /** Request/response middleware */ + middleware?: Middleware[]; + + /** Custom query string serializer */ + queryParamsStringify?: (params: HTTPQuery) => string; + + /** Default headers for all requests */ + headers?: Record; + + /** Credentials mode for requests */ + credentials?: RequestCredentials; +} +``` + +### Example with custom configuration + +```typescript +const config = new Configuration({ + basePath: 'https://api.example.com', + headers: { + 'X-API-Key': 'your-api-key', + 'Content-Type': 'application/json' + }, + credentials: 'include' +}); +``` + +## Middleware + +Add custom middleware to intercept requests and responses: + +```typescript +import { Configuration, Middleware } from 'multipart-nested-object-parts'; + +const loggingMiddleware: Middleware = { + pre: async (context) => { + console.log('Request:', context.url); + return context; + }, + post: async (context) => { + console.log('Response:', context.response.status); + return context.response; + }, + onError: async (context) => { + console.error('Error:', context.error); + return undefined; + } +}; + +const config = new Configuration({ + basePath: 'https://api.example.com', + middleware: [loggingMiddleware] +}); +``` + +## Error Handling + +The client throws typed errors for different failure scenarios: + +```typescript +import { ResponseError, FetchError, RequiredError } from 'multipart-nested-object-parts'; + +try { + const result = await api.someMethod(); +} catch (error) { + if (error instanceof ResponseError) { + // HTTP error response (4xx, 5xx) + console.error('HTTP Error:', error.response.status); + } else if (error instanceof FetchError) { + // Network or fetch error + console.error('Network Error:', error.cause); + } else if (error instanceof RequiredError) { + // Missing required parameter + console.error('Missing field:', error.field); + } +} +``` + +## API Reference + +This package exports the following: + +- **Configuration** - Client configuration class +- **BaseAPI** - Base class for all API clients +- **API Classes** - Generated API client classes (e.g., `UserApi`, `PostApi`) +- **Models** - Generated TypeScript interfaces for request/response types +- **Errors** - `ResponseError`, `FetchError`, `RequiredError` +- **Types** - TypeScript type definitions + +## Development + +### Building + +To build the package: + +```bash +npm install +npm run build +``` + +This will compile TypeScript to JavaScript in the `dist/` directory. + +### Building for ESM + +To build ES modules: + +```bash +npm run build +``` + +## TypeScript Support + +This package includes TypeScript type definitions. No additional `@types` package is needed. + +### TypeScript Configuration + +This package works with standard TypeScript configurations. If you're using a bundler-based setup, you may want to configure: + +```json +{ + "compilerOptions": { + "moduleResolution": "bundler" + } +} +``` + +### Type Imports + +```typescript +import type { User, CreateUserRequest } from 'multipart-nested-object-parts'; + +const user: User = { + id: 1, + name: 'John Doe', + email: 'john@example.com' +}; +``` + +## Browser Support + +This package uses the native Fetch API, which is supported in: + +- Chrome 42+ +- Firefox 39+ +- Safari 10.1+ +- Edge 14+ +- Node.js 18+ (native fetch) +- Node.js <18 (with `node-fetch` polyfill) + +For older browsers, you may need to include a fetch polyfill. + +## License + +This is an auto-generated API client. Please refer to your API documentation for license information. + +## Support + +For issues related to the API itself, please contact the API provider. + +For issues with this generated client, please check the OpenAPI specification used to generate it. + +--- + +**Generated by OpenAPI Generator** + +API Version: 1.0.0 diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/apis/MultipartApi.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/apis/MultipartApi.ts.golden new file mode 100644 index 000000000..aa517a178 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/apis/MultipartApi.ts.golden @@ -0,0 +1,77 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Nested Object Parts — 1.0.0 + * Covers multipart object parts whose wire names differ from ergonomic names. + */ +import { itemConfigToJSON } from '../models/ItemConfig'; +import type { NestedUpload } from '../models/NestedUpload'; +import type { Configuration, HTTPQuery, InitOverrideFunction } from '../runtime/runtime'; +import { BaseAPI, DefaultConfig, RequiredError, VoidApiResponse } from '../runtime/runtime'; + +export interface ApiSendNestedObjectPartRequest { + body: NestedUpload; +} + +export type SendNestedObjectPartRawResponse = + | VoidApiResponse & { status: 204 } + | VoidApiResponse & { status: number }; + + +export interface MultipartApiInterface { + sendNestedObjectPartRaw: (requestParameters: ApiSendNestedObjectPartRequest, initOverrides?: RequestInit | InitOverrideFunction) => Promise; + sendNestedObjectPart: (requestParameters: ApiSendNestedObjectPartRequest, initOverrides?: RequestInit | InitOverrideFunction) => Promise; +} + +export class MultipartApi extends BaseAPI implements MultipartApiInterface { + /** + * Initialize the API client + */ + constructor(configuration?: Configuration) { + super(configuration ?? DefaultConfig); + } + + async sendNestedObjectPartRaw(requestParameters: ApiSendNestedObjectPartRequest, + initOverrides?: RequestInit | InitOverrideFunction): Promise { + if (requestParameters.body === undefined || requestParameters.body === null) { + throw new RequiredError( + 'body', + 'Required parameter "body" was null or undefined when calling sendNestedObjectPartRaw().' + ); + } + // Build path with path parameters + const urlPath = `/multipart/nested-object`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + }; + + // Prepare request body + const requestBody = new FormData(); + requestBody.append('file', requestParameters.body.file); + requestBody.append('item_config', JSON.stringify(itemConfigToJSON(requestParameters.body.itemConfig))); + // Make request + const response = await this.request({ + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: requestBody, + }, initOverrides); + + // Handle responses + if (response.status === 204) { + return new VoidApiResponse(response) as VoidApiResponse & { status: 204 }; + } + else { + return new VoidApiResponse(response) as VoidApiResponse & { status: number }; + } + } + + async sendNestedObjectPart(requestParameters: ApiSendNestedObjectPartRequest, + initOverrides?: RequestInit | InitOverrideFunction): Promise { + const response = await this.sendNestedObjectPartRaw(requestParameters, initOverrides); + return await response.value(); + } +} diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/apis/index.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/apis/index.ts.golden new file mode 100644 index 000000000..cc93d072e --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/apis/index.ts.golden @@ -0,0 +1,12 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Nested Object Parts — 1.0.0 + * Covers multipart object parts whose wire names differ from ergonomic names. + */ +export type { + ApiSendNestedObjectPartRequest, + SendNestedObjectPartRawResponse, + MultipartApiInterface, +} from './MultipartApi'; +export { MultipartApi } from './MultipartApi'; diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/index.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/index.ts.golden new file mode 100644 index 000000000..82c45cabd --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/index.ts.golden @@ -0,0 +1,9 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Nested Object Parts — 1.0.0 + * Covers multipart object parts whose wire names differ from ergonomic names. + */ +export * from './runtime/runtime'; +export * from './apis'; +export * from './models'; diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/models/ItemConfig.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/models/ItemConfig.ts.golden new file mode 100644 index 000000000..09c4c23b0 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/models/ItemConfig.ts.golden @@ -0,0 +1,29 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Nested Object Parts — 1.0.0 + * Covers multipart object parts whose wire names differ from ergonomic names. + */ +export interface ItemConfig$Wire { + readonly display_name: string; + readonly retention_days?: number; +} + +export interface ItemConfig { + readonly displayName: string; + readonly retentionDays?: number; +} + +export function itemConfigFromJSON(json: ItemConfig$Wire): ItemConfig { + return { + displayName: json.display_name, + retentionDays: json.retention_days, + }; +} + +export function itemConfigToJSON(value: ItemConfig): ItemConfig$Wire { + return { + display_name: value.displayName, + retention_days: value.retentionDays, + }; +} diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/models/NestedUpload.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/models/NestedUpload.ts.golden new file mode 100644 index 000000000..99c00af59 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/models/NestedUpload.ts.golden @@ -0,0 +1,32 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Nested Object Parts — 1.0.0 + * Covers multipart object parts whose wire names differ from ergonomic names. + */ +import type { ItemConfig, ItemConfig$Wire } from './ItemConfig'; +import { itemConfigFromJSON, itemConfigToJSON } from './ItemConfig'; + +export interface NestedUpload$Wire { + readonly file: Blob | File; + readonly item_config: ItemConfig$Wire; +} + +export interface NestedUpload { + readonly file: Blob | File; + readonly itemConfig: ItemConfig; +} + +export function nestedUploadFromJSON(json: NestedUpload$Wire): NestedUpload { + return { + file: json.file, + itemConfig: itemConfigFromJSON(json.item_config), + }; +} + +export function nestedUploadToJSON(value: NestedUpload): NestedUpload$Wire { + return { + file: value.file, + item_config: itemConfigToJSON(value.itemConfig), + }; +} diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/models/index.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/models/index.ts.golden new file mode 100644 index 000000000..0720dc467 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/models/index.ts.golden @@ -0,0 +1,10 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Nested Object Parts — 1.0.0 + * Covers multipart object parts whose wire names differ from ergonomic names. + */ +export type { ItemConfig, ItemConfig$Wire } from './ItemConfig'; +export { itemConfigFromJSON, itemConfigToJSON } from './ItemConfig'; +export type { NestedUpload, NestedUpload$Wire } from './NestedUpload'; +export { nestedUploadFromJSON, nestedUploadToJSON } from './NestedUpload'; diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/package.json.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/package.json.golden new file mode 100644 index 000000000..adfcebda6 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/package.json.golden @@ -0,0 +1,26 @@ +{ + "description": "Covers multipart object parts whose wire names differ from ergonomic names.", + "exports": { + ".": { + "default": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "files": [ + "dist" + ], + "keywords": [ + "openapi", + "api-client", + "typescript", + "generated" + ], + "main": "./dist/index.js", + "name": "multipart-nested-object-parts", + "scripts": { + "build": "tsc" + }, + "type": "module", + "types": "./dist/index.d.ts", + "version": "1.0.0" +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/runtime/runtime.ts.golden new file mode 100644 index 000000000..be8df9abe --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/runtime/runtime.ts.golden @@ -0,0 +1,461 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * Multipart Nested Object Parts — 1.0.0 + * Covers multipart object parts whose wire names differ from ergonomic names. + */ +export const BASE_PATH = "http://localhost".replace(/\/+$/, ""); + +export interface ConfigurationParameters { + basePath?: string; // override base path + fetchApi?: FetchAPI; // override for fetch implementation + middleware?: Middleware[]; // middleware to apply before/after fetch requests + queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings + username?: string | (() => string | Promise); // parameter for basic security + password?: string | (() => string | Promise); // parameter for basic security + apiKey?: string | Promise | ((name: string) => string | Promise); // parameter for apiKey security + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string | Promise); // parameter for oauth2 security + headers?: HTTPHeaders; //header params we want to use on every request + credentials?: RequestCredentials; //value for the credentials param we want to use on each request +} + +export class Configuration { + constructor(private configuration: ConfigurationParameters = {}) {} + + set config(configuration: Configuration) { + this.configuration = configuration; + } + + get basePath(): string { + return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH; + } + + get fetchApi(): FetchAPI | undefined { + return this.configuration.fetchApi; + } + + get middleware(): Middleware[] { + return this.configuration.middleware || []; + } + + get queryParamsStringify(): (params: HTTPQuery) => string { + return this.configuration.queryParamsStringify || querystring; + } + + get username(): (() => string | Promise) | undefined { + const username = this.configuration.username; + if (username) { + return typeof username === 'function' ? username : async () => username; + } + return undefined; + } + + get password(): (() => string | Promise) | undefined { + const password = this.configuration.password; + if (password) { + return typeof password === 'function' ? password : async () => password; + } + return undefined; + } + + get apiKey(): ((name: string) => string | Promise) | undefined { + const apiKey = this.configuration.apiKey; + if (apiKey) { + return typeof apiKey === 'function' ? apiKey : () => apiKey; + } + return undefined; + } + + get accessToken(): ((name?: string, scopes?: string[]) => string | Promise) | undefined { + const accessToken = this.configuration.accessToken; + if (accessToken) { + return typeof accessToken === 'function' ? accessToken : async () => accessToken; + } + return undefined; + } + + get headers(): HTTPHeaders | undefined { + return this.configuration.headers; + } + + get credentials(): RequestCredentials | undefined { + return this.configuration.credentials; + } +} + +export const DefaultConfig = new Configuration(); + +/** + * This is the base class for all generated API classes. + */ +export class BaseAPI { + + private static readonly jsonRegex = new RegExp('^(:?application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(:?;.*)?$', 'i'); + private middleware: Middleware[]; + + constructor(protected configuration = DefaultConfig) { + this.middleware = configuration.middleware; + } + + withMiddleware(this: T, ...middlewares: Middleware[]) { + const next = this.clone(); + next.middleware = next.middleware.concat(...middlewares); + return next; + } + + withPreMiddleware(this: T, ...preMiddlewares: Array) { + const middlewares = preMiddlewares.map((pre) => ({ pre })); + return this.withMiddleware(...middlewares); + } + + withPostMiddleware(this: T, ...postMiddlewares: Array) { + const middlewares = postMiddlewares.map((post) => ({ post })); + return this.withMiddleware(...middlewares); + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + protected isJsonMime(mime: string | null | undefined): boolean { + if (!mime) { + return false; + } + return BaseAPI.jsonRegex.test(mime); + } + + protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise { + const { url, init } = await this.createFetchParams(context, initOverrides); + const response = await this.fetchApi(url, init); + if (response && (response.status >= 200 && response.status < 300)) { + return response; + } + throw new ResponseError(response, 'Response returned an error code'); + } + + private async createFetchParams(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction) { + let url = this.configuration.basePath + context.path; + if (context.query !== undefined && Object.keys(context.query).length !== 0) { + // only add the querystring to the URL if there are query parameters. + // this is done to avoid urls ending with a "?" character which buggy webservers + // do not handle correctly sometimes. + url += '?' + this.configuration.queryParamsStringify(context.query); + } + + const headers = Object.assign({}, this.configuration.headers, context.headers); + Object.keys(headers).forEach(key => headers[key] === undefined ? delete headers[key] : {}); + + const token = await this.configuration.accessToken?.(); + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + if (!headers['Authorization']) { + const username = await this.configuration.username?.(); + const password = await this.configuration.password?.(); + if (username && password) { + headers['Authorization'] = `Basic ${btoa(`${username}:${password}`)}`; + } + } + + const initOverrideFn = + typeof initOverrides === "function" + ? initOverrides + : async () => initOverrides; + + const initParams: HTTPRequestInit = { + method: context.method, + headers, + body: context.body, + credentials: this.configuration.credentials, + signal: context.signal, + }; + + const overriddenInit = { + ...initParams, + ...(await initOverrideFn({ + init: initParams, + context, + })) + }; + + let body: BodyInit | null | undefined; + if (isFormData(overriddenInit.body) + || (overriddenInit.body instanceof URLSearchParams) + || isBlob(overriddenInit.body)) { + body = overriddenInit.body; + } else if (this.isJsonMime(headers['Content-Type'])) { + body = JSON.stringify(overriddenInit.body); + } else { + body = overriddenInit.body as BodyInit | null | undefined; + } + + const init: RequestInit = { + ...overriddenInit, + body + }; + + return { url, init }; + } + + private fetchApi = async (url: string, init: RequestInit) => { + let fetchParams = { url, init }; + for (const middleware of this.middleware) { + if (middleware.pre) { + fetchParams = await middleware.pre({ + fetch: this.fetchApi, + ...fetchParams, + }) || fetchParams; + } + } + let response: Response | undefined = undefined; + try { + response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init); + } catch (e) { + for (const middleware of this.middleware) { + if (middleware.onError) { + response = await middleware.onError({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + error: e, + response: response ? response.clone() : undefined, + }) || response; + } + } + if (response === undefined) { + if (e instanceof Error) { + throw new FetchError(e, 'The request failed and the interceptors did not return an alternative response'); + } else { + throw e; + } + } + } + for (const middleware of this.middleware) { + if (middleware.post) { + response = await middleware.post({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + response: response.clone(), + }) || response; + } + } + return response; + } + + /** + * Create a shallow clone of `this` by constructing a new instance + * and then shallow cloning data members. + */ + private clone(this: T): T { + const constructor = this.constructor as new (configuration: Configuration) => T; + const next = new constructor(this.configuration); + next.middleware = this.middleware.slice(); + return next; + } +} + +function isBlob(value: unknown): value is Blob { + return typeof Blob !== 'undefined' && value instanceof Blob; +} + +function isFormData(value: unknown): value is FormData { + return typeof FormData !== "undefined" && value instanceof FormData; +} + +export class ResponseError extends Error { + override name = "ResponseError" as const; + constructor(public response: Response, msg?: string) { + super(msg); + } +} + +export class FetchError extends Error { + override name = "FetchError" as const; + constructor(public cause: Error, msg?: string) { + super(msg); + } +} + +export class RequiredError extends Error { + override name = "RequiredError" as const; + constructor(public field: string, msg?: string) { + super(msg); + } +} + +export type FetchAPI = WindowOrWorkerGlobalScope['fetch']; + +export type Json = unknown; +export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; +export type HTTPHeaders = { [key: string]: string }; +export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; +export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; + +export type InitOverrideFunction = (requestContext: { init: HTTPRequestInit, context: RequestOpts }) => Promise + +export interface FetchParams { + url: string; + init: RequestInit; +} + +export interface RequestOpts { + path: string; + method: HTTPMethod; + headers: HTTPHeaders; + query?: HTTPQuery; + body?: HTTPBody; + signal?: AbortSignal; +} + +export function querystring(params: HTTPQuery, prefix: string = ''): string { + return Object.keys(params) + .map(key => querystringSingleKey(key, params[key], prefix)) + .filter(part => part.length > 0) + .join('&'); +} + +function querystringSingleKey(key: string, value: string | number | null | undefined | boolean | Array | Set | HTTPQuery, keyPrefix: string = ''): string { + const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key); + if (value instanceof Array) { + const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue))) + .join(`&${encodeURIComponent(fullKey)}=`); + return `${encodeURIComponent(fullKey)}=${multiValue}`; + } + if (value instanceof Set) { + const valueAsArray = Array.from(value); + return querystringSingleKey(key, valueAsArray, keyPrefix); + } + if (value instanceof Date) { + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`; + } + if (value instanceof Object) { + return querystring(value as HTTPQuery, fullKey); + } + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`; +} + +export function exists(json: Record, key: string): boolean { + const value = json[key]; + return value !== null && value !== undefined; +} + +export function mapValues(data: Record, fn: (item: T) => U): Record { + const result: Record = {}; + for (const key of Object.keys(data)) { + result[key] = fn(data[key]); + } + return result; +} + +export function canConsumeForm(consumes: Consume[]): boolean { + for (const consume of consumes) { + if ('multipart/form-data' === consume.contentType) { + return true; + } + } + return false; +} + +export interface Consume { + contentType: string; +} + +export interface RequestContext { + fetch: FetchAPI; + url: string; + init: RequestInit; +} + +export interface ResponseContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + response: Response; +} + +export interface ErrorContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + error: unknown; + response?: Response; +} + +export interface Middleware { + pre?(context: RequestContext): Promise; + post?(context: ResponseContext): Promise; + onError?(context: ErrorContext): Promise; +} + +export interface ApiResponse { + raw: Response; + value(): Promise; +} + +export interface ResponseTransformer { + (json: unknown): T; +} + +class ApiResponseBase { + public readonly status: number; + public readonly ok: boolean; + public readonly statusText: string; + public readonly headers: Headers; + + constructor(public readonly raw: Response) { + this.status = raw.status; + this.ok = raw.ok; + this.statusText = raw.statusText; + this.headers = raw.headers; + } +} + +export class JSONApiResponse extends ApiResponseBase { + constructor(raw: Response, private transformer: ResponseTransformer = (jsonValue: unknown) => jsonValue as T) { + super(raw); + } + + async value(): Promise { + return this.transformer(await this.raw.json()); + } +} + +export class VoidApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return undefined; + } +} + +export class BlobApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return await this.raw.blob(); + }; +} + +export class TextApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return await this.raw.text(); + }; +} diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/tsconfig.esm.json.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/tsconfig.esm.json.golden new file mode 100644 index 000000000..bb8350cb6 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/tsconfig.esm.json.golden @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "ES2020", + "outDir": "dist/esm" + }, + "extends": "./tsconfig.json" +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/tsconfig.json.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/tsconfig.json.golden new file mode 100644 index 000000000..4b3a8cf7f --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-multipart-parts/tsconfig.json.golden @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "lib": [ + "ES2020", + "DOM" + ], + "module": "ES2020", + "moduleResolution": "bundler", + "outDir": "./dist", + "resolveJsonModule": true, + "rootDir": "./", + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "target": "ES2020", + "typeRoots": [ + "node_modules/@types" + ] + }, + "exclude": [ + "dist", + "node_modules" + ], + "include": [ + "**/*.ts" + ] +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-only-types/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-only-types/runtime/runtime.ts.golden index 0c2aae43d..e76d1b637 100644 --- a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-only-types/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-only-types/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-tagged-union-variant-with-discriminator/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-tagged-union-variant-with-discriminator/runtime/runtime.ts.golden index a08727241..b604773d8 100644 --- a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-tagged-union-variant-with-discriminator/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-tagged-union-variant-with-discriminator/runtime/runtime.ts.golden @@ -300,7 +300,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-tagged-union/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-tagged-union/runtime/runtime.ts.golden index 93f49b916..314484e23 100644 --- a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-tagged-union/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-tagged-union/runtime/runtime.ts.golden @@ -298,7 +298,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-union/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-union/runtime/runtime.ts.golden index 58543bc7a..45c7a6b77 100644 --- a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-union/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-union/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case/runtime/runtime.ts.golden index 73a38f705..649999855 100644 --- a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-comprehensive/models/BinaryString.ts.golden b/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-comprehensive/models/BinaryString.ts.golden index 866fc78f1..458ee098e 100644 --- a/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-comprehensive/models/BinaryString.ts.golden +++ b/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-comprehensive/models/BinaryString.ts.golden @@ -6,4 +6,4 @@ */ /** String with binary format */ -export type BinaryString = string; +export type BinaryString = Blob | File; diff --git a/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-comprehensive/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-comprehensive/runtime/runtime.ts.golden index 7d42b3934..f5de55d55 100644 --- a/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-comprehensive/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-comprehensive/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-delete-response/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-delete-response/runtime/runtime.ts.golden index 0c2aae43d..e76d1b637 100644 --- a/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-delete-response/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-delete-response/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-enum-repr/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-enum-repr/runtime/runtime.ts.golden index 4c76003a4..df88c849e 100644 --- a/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-enum-repr/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-enum-repr/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-petstore/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-petstore/runtime/runtime.ts.golden index 6da4cd16f..1f9dffc9c 100644 --- a/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-petstore/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-petstore/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/ts-toolchain-vp/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/ts-toolchain-vp/runtime/runtime.ts.golden index 93f49b916..314484e23 100644 --- a/tests/golden/typescript/typescript-fetch/ts-toolchain-vp/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/ts-toolchain-vp/runtime/runtime.ts.golden @@ -298,7 +298,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/ts-type-guards/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/ts-type-guards/runtime/runtime.ts.golden index e9cd29682..32bad89a7 100644 --- a/tests/golden/typescript/typescript-fetch/ts-type-guards/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/ts-type-guards/runtime/runtime.ts.golden @@ -296,7 +296,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/type-aliases-complex-union/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/type-aliases-complex-union/runtime/runtime.ts.golden index 7a0f15c81..99ca4347c 100644 --- a/tests/golden/typescript/typescript-fetch/type-aliases-complex-union/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/type-aliases-complex-union/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-inline-discriminator-only/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-inline-discriminator-only/runtime/runtime.ts.golden index 4c1eea065..6cef61ab3 100644 --- a/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-inline-discriminator-only/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-inline-discriminator-only/runtime/runtime.ts.golden @@ -306,7 +306,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-internally-tagged/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-internally-tagged/runtime/runtime.ts.golden index 6c459747f..c18687604 100644 --- a/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-internally-tagged/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-internally-tagged/runtime/runtime.ts.golden @@ -299,7 +299,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-long-names/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-long-names/runtime/runtime.ts.golden index b42b971f7..8d3b74f06 100644 --- a/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-long-names/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-long-names/runtime/runtime.ts.golden @@ -299,7 +299,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-mixed-unit-and-allof/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-mixed-unit-and-allof/runtime/runtime.ts.golden index 7f892fda2..b6f7ede4c 100644 --- a/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-mixed-unit-and-allof/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-mixed-unit-and-allof/runtime/runtime.ts.golden @@ -302,7 +302,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-multiple/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-multiple/runtime/runtime.ts.golden index 08f042082..2f9220ab6 100644 --- a/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-multiple/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-multiple/runtime/runtime.ts.golden @@ -300,7 +300,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-with-refs/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-with-refs/runtime/runtime.ts.golden index 93f49b916..314484e23 100644 --- a/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-with-refs/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/type-aliases-discriminated-union-with-refs/runtime/runtime.ts.golden @@ -298,7 +298,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/type-aliases-intersection-allof/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/type-aliases-intersection-allof/runtime/runtime.ts.golden index 98f1acf32..d96978125 100644 --- a/tests/golden/typescript/typescript-fetch/type-aliases-intersection-allof/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/type-aliases-intersection-allof/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/type-aliases-intersection-with-nullable-reference/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/type-aliases-intersection-with-nullable-reference/runtime/runtime.ts.golden index 802b045f2..38cd91ba6 100644 --- a/tests/golden/typescript/typescript-fetch/type-aliases-intersection-with-nullable-reference/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/type-aliases-intersection-with-nullable-reference/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/type-aliases-nested-union/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/type-aliases-nested-union/runtime/runtime.ts.golden index 236cbb15f..f28530504 100644 --- a/tests/golden/typescript/typescript-fetch/type-aliases-nested-union/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/type-aliases-nested-union/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/type-aliases-simple-type-alias/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/type-aliases-simple-type-alias/runtime/runtime.ts.golden index d7947accf..966b422cc 100644 --- a/tests/golden/typescript/typescript-fetch/type-aliases-simple-type-alias/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/type-aliases-simple-type-alias/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/type-aliases-union-mixed/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/type-aliases-union-mixed/runtime/runtime.ts.golden index d31ae9fd4..4a290fa0c 100644 --- a/tests/golden/typescript/typescript-fetch/type-aliases-union-mixed/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/type-aliases-union-mixed/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/type-aliases-union-with-any/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/type-aliases-union-with-any/runtime/runtime.ts.golden index 0444cb86f..285a13054 100644 --- a/tests/golden/typescript/typescript-fetch/type-aliases-union-with-any/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/type-aliases-union-with-any/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/type-aliases-union-with-inline-objects/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/type-aliases-union-with-inline-objects/runtime/runtime.ts.golden index bc2231ffd..a611817b7 100644 --- a/tests/golden/typescript/typescript-fetch/type-aliases-union-with-inline-objects/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/type-aliases-union-with-inline-objects/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/type-aliases-union-with-interfaces/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/type-aliases-union-with-interfaces/runtime/runtime.ts.golden index 58543bc7a..45c7a6b77 100644 --- a/tests/golden/typescript/typescript-fetch/type-aliases-union-with-interfaces/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/type-aliases-union-with-interfaces/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden/typescript/typescript-fetch/type-aliases-union-with-primitives/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/type-aliases-union-with-primitives/runtime/runtime.ts.golden index c75cc0b62..04eb88c6e 100644 --- a/tests/golden/typescript/typescript-fetch/type-aliases-union-with-primitives/runtime/runtime.ts.golden +++ b/tests/golden/typescript/typescript-fetch/type-aliases-union-with-primitives/runtime/runtime.ts.golden @@ -297,7 +297,7 @@ export type Json = unknown; export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; export type HTTPHeaders = { [key: string]: string }; export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; diff --git a/tests/golden_tests_go_http.rs b/tests/golden_tests_go_http.rs index c9a5dcc2a..536148a8b 100644 --- a/tests/golden_tests_go_http.rs +++ b/tests/golden_tests_go_http.rs @@ -32,6 +32,10 @@ fn get_golden_test_cases() -> HashMap<&'static str, &'static str> { ("naming-conventions", "valid/naming-conventions.yaml"), ("request-body-content-types", "valid/request-body-content-types.yaml"), ("binary-transfer-media-types", "valid/binary-transfer-media-types.yaml"), + ("media-type-selection", "valid/media-type-selection.yaml"), + ("multipart-edge-cases", "valid/multipart-edge-cases.yaml"), + ("multipart-nested-object-parts", "valid/multipart-nested-object-parts.yaml"), + ("multipart-unsupported-schema", "valid/multipart-unsupported-schema.yaml"), ("server-object", "valid/server-object.yaml"), ("recursive-json-all-optional-properties", "valid/recursive-json/all-optional-properties.yaml"), @@ -112,6 +116,10 @@ generate_golden_tests! { test_naming_conventions_golden: "naming-conventions", test_request_body_content_types_golden: "request-body-content-types", test_binary_transfer_media_types_golden: "binary-transfer-media-types", + test_media_type_selection_golden: "media-type-selection", + test_multipart_edge_cases_golden: "multipart-edge-cases", + test_multipart_nested_object_parts_golden: "multipart-nested-object-parts", + test_multipart_unsupported_schema_golden: "multipart-unsupported-schema", test_server_object_golden: "server-object", test_recursive_json_all_optional_properties_golden: "recursive-json-all-optional-properties", diff --git a/tests/golden_tests_java_okhttp.rs b/tests/golden_tests_java_okhttp.rs index e81f96b35..caa7ffb6c 100644 --- a/tests/golden_tests_java_okhttp.rs +++ b/tests/golden_tests_java_okhttp.rs @@ -32,6 +32,10 @@ fn get_golden_test_cases() -> HashMap<&'static str, &'static str> { ("naming-conventions", "valid/naming-conventions.yaml"), ("request-body-content-types", "valid/request-body-content-types.yaml"), ("binary-transfer-media-types", "valid/binary-transfer-media-types.yaml"), + ("media-type-selection", "valid/media-type-selection.yaml"), + ("multipart-edge-cases", "valid/multipart-edge-cases.yaml"), + ("multipart-nested-object-parts", "valid/multipart-nested-object-parts.yaml"), + ("multipart-unsupported-schema", "valid/multipart-unsupported-schema.yaml"), ("server-object", "valid/server-object.yaml"), ("recursive-json-all-optional-properties", "valid/recursive-json/all-optional-properties.yaml"), @@ -113,6 +117,10 @@ generate_golden_tests! { test_naming_conventions_golden: "naming-conventions", test_request_body_content_types_golden: "request-body-content-types", test_binary_transfer_media_types_golden: "binary-transfer-media-types", + test_media_type_selection_golden: "media-type-selection", + test_multipart_edge_cases_golden: "multipart-edge-cases", + test_multipart_nested_object_parts_golden: "multipart-nested-object-parts", + test_multipart_unsupported_schema_golden: "multipart-unsupported-schema", test_server_object_golden: "server-object", test_recursive_json_all_optional_properties_golden: "recursive-json-all-optional-properties", diff --git a/tests/golden_tests_kotlin_okhttp.rs b/tests/golden_tests_kotlin_okhttp.rs index 9f075d6dd..4c06ca017 100644 --- a/tests/golden_tests_kotlin_okhttp.rs +++ b/tests/golden_tests_kotlin_okhttp.rs @@ -32,6 +32,10 @@ fn get_golden_test_cases() -> HashMap<&'static str, &'static str> { ("naming-conventions", "valid/naming-conventions.yaml"), ("request-body-content-types", "valid/request-body-content-types.yaml"), ("binary-transfer-media-types", "valid/binary-transfer-media-types.yaml"), + ("media-type-selection", "valid/media-type-selection.yaml"), + ("multipart-edge-cases", "valid/multipart-edge-cases.yaml"), + ("multipart-nested-object-parts", "valid/multipart-nested-object-parts.yaml"), + ("multipart-unsupported-schema", "valid/multipart-unsupported-schema.yaml"), ("server-object", "valid/server-object.yaml"), ("recursive-json-all-optional-properties", "valid/recursive-json/all-optional-properties.yaml"), @@ -113,6 +117,10 @@ generate_golden_tests! { test_naming_conventions_golden: "naming-conventions", test_request_body_content_types_golden: "request-body-content-types", test_binary_transfer_media_types_golden: "binary-transfer-media-types", + test_media_type_selection_golden: "media-type-selection", + test_multipart_edge_cases_golden: "multipart-edge-cases", + test_multipart_nested_object_parts_golden: "multipart-nested-object-parts", + test_multipart_unsupported_schema_golden: "multipart-unsupported-schema", test_server_object_golden: "server-object", test_recursive_json_all_optional_properties_golden: "recursive-json-all-optional-properties", diff --git a/tests/golden_tests_python_httpx.rs b/tests/golden_tests_python_httpx.rs index 7e1ab580b..397325050 100644 --- a/tests/golden_tests_python_httpx.rs +++ b/tests/golden_tests_python_httpx.rs @@ -32,6 +32,10 @@ fn get_golden_test_cases() -> HashMap<&'static str, &'static str> { ("naming-conventions", "valid/naming-conventions.yaml"), ("request-body-content-types", "valid/request-body-content-types.yaml"), ("binary-transfer-media-types", "valid/binary-transfer-media-types.yaml"), + ("media-type-selection", "valid/media-type-selection.yaml"), + ("multipart-edge-cases", "valid/multipart-edge-cases.yaml"), + ("multipart-nested-object-parts", "valid/multipart-nested-object-parts.yaml"), + ("multipart-unsupported-schema", "valid/multipart-unsupported-schema.yaml"), ("server-object", "valid/server-object.yaml"), ("recursive-json-all-optional-properties", "valid/recursive-json/all-optional-properties.yaml"), @@ -112,6 +116,10 @@ generate_golden_tests! { test_naming_conventions_golden: "naming-conventions", test_request_body_content_types_golden: "request-body-content-types", test_binary_transfer_media_types_golden: "binary-transfer-media-types", + test_media_type_selection_golden: "media-type-selection", + test_multipart_edge_cases_golden: "multipart-edge-cases", + test_multipart_nested_object_parts_golden: "multipart-nested-object-parts", + test_multipart_unsupported_schema_golden: "multipart-unsupported-schema", test_server_object_golden: "server-object", test_recursive_json_all_optional_properties_golden: "recursive-json-all-optional-properties", diff --git a/tests/golden_tests_python_requests.rs b/tests/golden_tests_python_requests.rs index c11c7f90b..3aa850c0c 100644 --- a/tests/golden_tests_python_requests.rs +++ b/tests/golden_tests_python_requests.rs @@ -32,6 +32,10 @@ fn get_golden_test_cases() -> HashMap<&'static str, &'static str> { ("naming-conventions", "valid/naming-conventions.yaml"), ("request-body-content-types", "valid/request-body-content-types.yaml"), ("binary-transfer-media-types", "valid/binary-transfer-media-types.yaml"), + ("media-type-selection", "valid/media-type-selection.yaml"), + ("multipart-edge-cases", "valid/multipart-edge-cases.yaml"), + ("multipart-nested-object-parts", "valid/multipart-nested-object-parts.yaml"), + ("multipart-unsupported-schema", "valid/multipart-unsupported-schema.yaml"), ("server-object", "valid/server-object.yaml"), ("recursive-json-all-optional-properties", "valid/recursive-json/all-optional-properties.yaml"), @@ -112,6 +116,10 @@ generate_golden_tests! { test_naming_conventions_golden: "naming-conventions", test_request_body_content_types_golden: "request-body-content-types", test_binary_transfer_media_types_golden: "binary-transfer-media-types", + test_media_type_selection_golden: "media-type-selection", + test_multipart_edge_cases_golden: "multipart-edge-cases", + test_multipart_nested_object_parts_golden: "multipart-nested-object-parts", + test_multipart_unsupported_schema_golden: "multipart-unsupported-schema", test_server_object_golden: "server-object", test_recursive_json_all_optional_properties_golden: "recursive-json-all-optional-properties", diff --git a/tests/golden_tests_rust_aioduct.rs b/tests/golden_tests_rust_aioduct.rs index b513c7ef0..1006e4bdb 100644 --- a/tests/golden_tests_rust_aioduct.rs +++ b/tests/golden_tests_rust_aioduct.rs @@ -34,6 +34,9 @@ fn get_golden_test_cases() -> HashMap<&'static str, &'static str> { ("multiple-similar-request-schemas", "valid/multiple-similar-request-schemas.yaml"), ("request-body-content-types", "valid/request-body-content-types.yaml"), ("binary-transfer-media-types", "valid/binary-transfer-media-types.yaml"), + ("media-type-selection", "valid/media-type-selection.yaml"), + ("multipart-edge-cases", "valid/multipart-edge-cases.yaml"), + ("multipart-nested-object-parts", "valid/multipart-nested-object-parts.yaml"), ("multipart-unsupported-schema", "valid/multipart-unsupported-schema.yaml"), ("type-aliases-simple-type-alias", "valid/type-aliases/simple-type-alias.yaml"), @@ -115,6 +118,9 @@ generate_golden_tests! { test_multiple_similar_request_schemas_golden: "multiple-similar-request-schemas", test_request_body_content_types_golden: "request-body-content-types", test_binary_transfer_media_types_golden: "binary-transfer-media-types", + test_media_type_selection_golden: "media-type-selection", + test_multipart_edge_cases_golden: "multipart-edge-cases", + test_multipart_nested_object_parts_golden: "multipart-nested-object-parts", test_multipart_unsupported_schema_golden: "multipart-unsupported-schema", test_type_aliases_simple_type_alias_golden: "type-aliases-simple-type-alias", diff --git a/tests/golden_tests_rust_reqwest.rs b/tests/golden_tests_rust_reqwest.rs index 72cc8b1aa..1b6c1d319 100644 --- a/tests/golden_tests_rust_reqwest.rs +++ b/tests/golden_tests_rust_reqwest.rs @@ -34,6 +34,9 @@ fn get_golden_test_cases() -> HashMap<&'static str, &'static str> { ("multiple-similar-request-schemas", "valid/multiple-similar-request-schemas.yaml"), ("request-body-content-types", "valid/request-body-content-types.yaml"), ("binary-transfer-media-types", "valid/binary-transfer-media-types.yaml"), + ("media-type-selection", "valid/media-type-selection.yaml"), + ("multipart-edge-cases", "valid/multipart-edge-cases.yaml"), + ("multipart-nested-object-parts", "valid/multipart-nested-object-parts.yaml"), ("multipart-unsupported-schema", "valid/multipart-unsupported-schema.yaml"), ("type-aliases-simple-type-alias", "valid/type-aliases/simple-type-alias.yaml"), @@ -115,6 +118,9 @@ generate_golden_tests! { test_multiple_similar_request_schemas_golden: "multiple-similar-request-schemas", test_request_body_content_types_golden: "request-body-content-types", test_binary_transfer_media_types_golden: "binary-transfer-media-types", + test_media_type_selection_golden: "media-type-selection", + test_multipart_edge_cases_golden: "multipart-edge-cases", + test_multipart_nested_object_parts_golden: "multipart-nested-object-parts", test_multipart_unsupported_schema_golden: "multipart-unsupported-schema", test_type_aliases_simple_type_alias_golden: "type-aliases-simple-type-alias", diff --git a/tests/golden_tests_rust_ureq.rs b/tests/golden_tests_rust_ureq.rs index 92aeff2f3..b4c73c4ab 100644 --- a/tests/golden_tests_rust_ureq.rs +++ b/tests/golden_tests_rust_ureq.rs @@ -34,6 +34,9 @@ fn get_golden_test_cases() -> HashMap<&'static str, &'static str> { ("multiple-similar-request-schemas", "valid/multiple-similar-request-schemas.yaml"), ("request-body-content-types", "valid/request-body-content-types.yaml"), ("binary-transfer-media-types", "valid/binary-transfer-media-types.yaml"), + ("media-type-selection", "valid/media-type-selection.yaml"), + ("multipart-edge-cases", "valid/multipart-edge-cases.yaml"), + ("multipart-nested-object-parts", "valid/multipart-nested-object-parts.yaml"), ("multipart-unsupported-schema", "valid/multipart-unsupported-schema.yaml"), ("type-aliases-simple-type-alias", "valid/type-aliases/simple-type-alias.yaml"), @@ -115,6 +118,9 @@ generate_golden_tests! { test_multiple_similar_request_schemas_golden: "multiple-similar-request-schemas", test_request_body_content_types_golden: "request-body-content-types", test_binary_transfer_media_types_golden: "binary-transfer-media-types", + test_media_type_selection_golden: "media-type-selection", + test_multipart_edge_cases_golden: "multipart-edge-cases", + test_multipart_nested_object_parts_golden: "multipart-nested-object-parts", test_multipart_unsupported_schema_golden: "multipart-unsupported-schema", test_type_aliases_simple_type_alias_golden: "type-aliases-simple-type-alias", diff --git a/tests/golden_tests_typescript_fetch.rs b/tests/golden_tests_typescript_fetch.rs index 54892ffb3..a9af92752 100644 --- a/tests/golden_tests_typescript_fetch.rs +++ b/tests/golden_tests_typescript_fetch.rs @@ -32,6 +32,10 @@ fn get_golden_test_cases() -> HashMap<&'static str, &'static str> { ("naming-conventions", "valid/naming-conventions.yaml"), ("request-body-content-types", "valid/request-body-content-types.yaml"), ("binary-transfer-media-types", "valid/binary-transfer-media-types.yaml"), + ("media-type-selection", "valid/media-type-selection.yaml"), + ("multipart-edge-cases", "valid/multipart-edge-cases.yaml"), + ("multipart-nested-object-parts", "valid/multipart-nested-object-parts.yaml"), + ("multipart-unsupported-schema", "valid/multipart-unsupported-schema.yaml"), ("server-object", "valid/server-object.yaml"), ("recursive-json-all-optional-properties", "valid/recursive-json/all-optional-properties.yaml"), @@ -112,6 +116,10 @@ generate_golden_tests! { test_naming_conventions_golden: "naming-conventions", test_request_body_content_types_golden: "request-body-content-types", test_binary_transfer_media_types_golden: "binary-transfer-media-types", + test_media_type_selection_golden: "media-type-selection", + test_multipart_edge_cases_golden: "multipart-edge-cases", + test_multipart_nested_object_parts_golden: "multipart-nested-object-parts", + test_multipart_unsupported_schema_golden: "multipart-unsupported-schema", test_server_object_golden: "server-object", test_recursive_json_all_optional_properties_golden: "recursive-json-all-optional-properties", @@ -222,6 +230,25 @@ property_naming = "camelCase" ); } +#[test] +#[traced_test] +fn test_property_naming_camel_case_multipart_parts_golden() { + let config: toml::value::Table = toml::from_str( + r#" +property_naming = "camelCase" +"#, + ) + .unwrap(); + let generator = TypeScriptFetchCodeGenerator::new(config); + run_golden_test( + &generator, + golden_dir(), + "ts-property-naming-camel-case-multipart-parts", + "valid/multipart-nested-object-parts.yaml", + UPDATE_HINT, + ); +} + #[test] #[traced_test] fn test_property_naming_camel_case_response_only_types_golden() { diff --git a/tests/multipart_runtime_smoke.rs b/tests/multipart_runtime_smoke.rs new file mode 100644 index 000000000..b753253ed --- /dev/null +++ b/tests/multipart_runtime_smoke.rs @@ -0,0 +1,256 @@ +use std::collections::HashMap; + +use openapi_nexus::generators::go::http::GoHttpCodeGenerator; +use openapi_nexus::generators::java::okhttp::JavaOkhttpCodeGenerator; +use openapi_nexus::generators::kotlin::okhttp::KotlinOkhttpCodeGenerator; +use openapi_nexus::generators::python::httpx::PythonHttpxCodeGenerator; +use openapi_nexus::generators::python::requests::PythonRequestsCodeGenerator; +use openapi_nexus::generators::typescript::fetch::TypeScriptFetchCodeGenerator; +use openapi_nexus::test_utils::{generate_files, read_fixture}; + +fn empty_config() -> toml::value::Table { + toml::value::Table::new() +} + +fn generated_file<'a>(files: &'a HashMap, suffix: &str) -> &'a str { + files + .iter() + .find(|(name, _)| name.ends_with(suffix)) + .map(|(_, content)| content.as_str()) + .unwrap_or_else(|| { + panic!( + "expected generated file ending in {suffix}; got {:?}", + files.keys().collect::>() + ) + }) +} + +#[test] +fn multipart_wire_construction_is_pinned_across_non_rust_clients() { + let fixture = read_fixture("valid/multipart-edge-cases.yaml"); + + let go_files = generate_files(&GoHttpCodeGenerator::new(empty_config()), &fixture).unwrap(); + let go_api = generated_file(&go_files, "apis/multipart.go"); + assert!(go_api.contains("multipart.NewWriter(buf)")); + assert!(go_api.contains("strconv.FormatInt(int64(*body.RetryCount), 10)")); + assert!(go_api.contains("strconv.FormatBool(*body.Enabled)")); + + let java_files = + generate_files(&JavaOkhttpCodeGenerator::new(empty_config()), &fixture).unwrap(); + let java_api = generated_file(&java_files, "apis/MultipartApi.java"); + assert!(java_api.contains("client.newRequestWithBody(\"POST\", path, null, multipartBody)")); + assert!(java_api.contains("RequestBody multipartBody = RequestBody.create(new byte[0], null)")); + assert!(java_api.contains("if (body != null)")); + assert!(java_api.contains("if (body.getFile() != null)")); + + let kotlin_files = + generate_files(&KotlinOkhttpCodeGenerator::new(empty_config()), &fixture).unwrap(); + let kotlin_api = generated_file(&kotlin_files, "apis/MultipartApi.kt"); + assert!(kotlin_api.contains("client.newRequestWithBody(\"POST\", path, null, multipartBody)")); + assert!(kotlin_api.contains("body: OptionalUpload?")); + assert!(kotlin_api.contains("var multipartBody = ByteArray(0).toRequestBody(null)")); + assert!(kotlin_api.contains("if (body != null)")); + assert!(kotlin_api.contains("if (body.file != null)")); + + let httpx_files = + generate_files(&PythonHttpxCodeGenerator::new(empty_config()), &fixture).unwrap(); + let httpx_api = generated_file(&httpx_files, "apis/multipart_api.py"); + assert!(httpx_api.contains("files: dict[str, object] = {}")); + assert!(httpx_api.contains("files[\"note\"] = (None, str(body.note))")); + assert!(httpx_api.contains("files=files if files else None")); + assert!(!httpx_api.contains("data=data")); + + let requests_files = + generate_files(&PythonRequestsCodeGenerator::new(empty_config()), &fixture).unwrap(); + let requests_api = generated_file(&requests_files, "apis/multipart_api.py"); + assert!(requests_api.contains("files: dict[str, object] = {}")); + assert!(requests_api.contains("files[\"note\"] = (None, str(body.note))")); + assert!(requests_api.contains("files=files if files else None")); + assert!(!requests_api.contains("data=data")); + + let ts_files = + generate_files(&TypeScriptFetchCodeGenerator::new(empty_config()), &fixture).unwrap(); + let ts_api = generated_file(&ts_files, "apis/MultipartApi.ts"); + assert!(ts_api.contains("let requestBody: FormData | undefined = undefined;")); + assert!( + ts_api.contains( + "if (requestParameters.body !== undefined && requestParameters.body !== null)" + ) + ); + assert!(ts_api.contains("const requestBody = new FormData();")); + assert!(ts_api.contains("requestBody.append('note', String(requestParameters.body.note));")); +} + +#[test] +fn selected_media_types_drive_request_and_response_wire_code() { + let media_fixture = read_fixture("valid/media-type-selection.yaml"); + + let go_files = + generate_files(&GoHttpCodeGenerator::new(empty_config()), &media_fixture).unwrap(); + let go_api = generated_file(&go_files, "apis/media.go"); + assert!(go_api.contains( + "req.Header.Set(\"Content-Type\", \"application/vnd.example+json; charset=utf-8\")" + )); + assert!(go_api.contains("payload, err := io.ReadAll(httpResp.Body)")); + assert!(go_api.contains("bodyBytes, err := io.ReadAll(httpResp.Body)")); + + let java_files = generate_files( + &JavaOkhttpCodeGenerator::new(empty_config()), + &media_fixture, + ) + .unwrap(); + let java_api = generated_file(&java_files, "apis/MediaApi.java"); + assert!(java_api.contains("RequestBody.create(jsonBody, MediaType.get(\"application/vnd.example+json; charset=utf-8\"))")); + assert!(java_api.contains( + "byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]" + )); + assert!(java_api.contains("status200 = responseBytes")); + assert!(java_api.contains("status200 = responseText")); + + let kotlin_files = generate_files( + &KotlinOkhttpCodeGenerator::new(empty_config()), + &media_fixture, + ) + .unwrap(); + let kotlin_api = generated_file(&kotlin_files, "apis/MediaApi.kt"); + assert!(kotlin_api.contains( + "jsonBody.toRequestBody(\"application/vnd.example+json; charset=utf-8\".toMediaType())" + )); + assert!(kotlin_api.contains("val responseBytes = response.body?.bytes() ?: ByteArray(0)")); + assert!(kotlin_api.contains("if (response.code == 200) responseBytes else null")); + assert!(kotlin_api.contains("if (response.code == 200) responseText else null")); + + let httpx_files = generate_files( + &PythonHttpxCodeGenerator::new(empty_config()), + &media_fixture, + ) + .unwrap(); + let httpx_api = generated_file(&httpx_files, "apis/media_api.py"); + assert!( + httpx_api.contains( + "headers[\"Content-Type\"] = \"application/vnd.example+json; charset=utf-8\"" + ) + ); + assert!(httpx_api.contains("return response.content")); + assert!(httpx_api.contains("return response.text")); + + let requests_files = generate_files( + &PythonRequestsCodeGenerator::new(empty_config()), + &media_fixture, + ) + .unwrap(); + let requests_api = generated_file(&requests_files, "apis/media_api.py"); + assert!( + requests_api.contains( + "headers[\"Content-Type\"] = \"application/vnd.example+json; charset=utf-8\"" + ) + ); + assert!(requests_api.contains("return response.content")); + assert!(requests_api.contains("return response.text")); +} + +#[test] +fn non_json_request_bodies_do_not_use_json_serialization() { + let fixture = read_fixture("valid/request-body-content-types.yaml"); + + let go_files = generate_files(&GoHttpCodeGenerator::new(empty_config()), &fixture).unwrap(); + let go_api = generated_file(&go_files, "apis/default.go"); + assert!(go_api.contains("bodyReader = strings.NewReader(*body)")); + assert!(go_api.contains("bodyReader = bytes.NewReader(body)")); + assert!( + go_api.contains("unsupported request body media type: application/x-www-form-urlencoded") + ); + assert!(go_api.contains("unsupported request body media type: application/xml")); + + let java_files = + generate_files(&JavaOkhttpCodeGenerator::new(empty_config()), &fixture).unwrap(); + let java_api = generated_file(&java_files, "apis/DefaultApi.java"); + assert!(java_api.contains("RequestBody.create(body, MediaType.get(\"text/plain\"))")); + assert!( + java_api.contains("RequestBody.create(body, MediaType.get(\"application/octet-stream\"))") + ); + assert!( + java_api.contains("unsupported request body media type: application/x-www-form-urlencoded") + ); + assert!(java_api.contains("unsupported request body media type: application/xml")); + + let kotlin_files = + generate_files(&KotlinOkhttpCodeGenerator::new(empty_config()), &fixture).unwrap(); + let kotlin_api = generated_file(&kotlin_files, "apis/DefaultApi.kt"); + assert!(kotlin_api.contains("body.toRequestBody(\"text/plain\".toMediaType())")); + assert!(kotlin_api.contains("body.toRequestBody(\"application/octet-stream\".toMediaType())")); + assert!( + kotlin_api + .contains("unsupported request body media type: application/x-www-form-urlencoded") + ); + assert!(kotlin_api.contains("unsupported request body media type: application/xml")); + + let httpx_files = + generate_files(&PythonHttpxCodeGenerator::new(empty_config()), &fixture).unwrap(); + let httpx_api = generated_file(&httpx_files, "apis/default_api.py"); + assert!(httpx_api.contains("data=body.to_dict()")); + assert!(httpx_api.contains("content=body")); + assert!(httpx_api.contains("unsupported request body media type: application/xml")); + + let requests_files = + generate_files(&PythonRequestsCodeGenerator::new(empty_config()), &fixture).unwrap(); + let requests_api = generated_file(&requests_files, "apis/default_api.py"); + assert!(requests_api.contains("data=body.to_dict()")); + assert!(requests_api.contains("data=body")); + assert!(requests_api.contains("unsupported request body media type: application/xml")); + + let ts_files = + generate_files(&TypeScriptFetchCodeGenerator::new(empty_config()), &fixture).unwrap(); + let ts_api = generated_file(&ts_files, "apis/DefaultApi.ts"); + assert!( + ts_api.contains("unsupported request body media type: application/x-www-form-urlencoded") + ); + assert!(ts_api.contains("unsupported request body media type: application/xml")); +} + +#[test] +fn unsupported_multipart_schema_fails_explicitly_in_non_rust_clients() { + let fixture = read_fixture("valid/multipart-unsupported-schema.yaml"); + let expected = "unsupported multipart request body: schema must be object-shaped"; + + let go_files = generate_files(&GoHttpCodeGenerator::new(empty_config()), &fixture).unwrap(); + assert!(generated_file(&go_files, "apis/transfer.go").contains(expected)); + + let java_files = + generate_files(&JavaOkhttpCodeGenerator::new(empty_config()), &fixture).unwrap(); + assert!(generated_file(&java_files, "apis/TransferApi.java").contains(expected)); + + let kotlin_files = + generate_files(&KotlinOkhttpCodeGenerator::new(empty_config()), &fixture).unwrap(); + assert!(generated_file(&kotlin_files, "apis/TransferApi.kt").contains(expected)); + + let httpx_files = + generate_files(&PythonHttpxCodeGenerator::new(empty_config()), &fixture).unwrap(); + assert!(generated_file(&httpx_files, "apis/transfer_api.py").contains(expected)); + + let requests_files = + generate_files(&PythonRequestsCodeGenerator::new(empty_config()), &fixture).unwrap(); + assert!(generated_file(&requests_files, "apis/transfer_api.py").contains(expected)); + + let ts_files = + generate_files(&TypeScriptFetchCodeGenerator::new(empty_config()), &fixture).unwrap(); + assert!(generated_file(&ts_files, "apis/TransferApi.ts").contains(expected)); +} + +#[test] +fn typescript_camel_case_multipart_object_parts_use_wire_json_conversion() { + let fixture = read_fixture("valid/multipart-nested-object-parts.yaml"); + let config = toml::from_str( + r#" +property_naming = "camelCase" +"#, + ) + .unwrap(); + let files = generate_files(&TypeScriptFetchCodeGenerator::new(config), &fixture).unwrap(); + let api = generated_file(&files, "apis/MultipartApi.ts"); + + assert!(api.contains("itemConfigToJSON")); + assert!(api.contains( + "requestBody.append('item_config', JSON.stringify(itemConfigToJSON(requestParameters.body.itemConfig)));" + )); +}