From e056fb448e8621a6be85c7f18697731e704d12c7 Mon Sep 17 00:00:00 2001 From: eden wang <64514273+eyw520@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:48:04 -0500 Subject: [PATCH] fix(python): multipart file upload parameter fixes, python core utility bugfix. (#5201) * fix(python): omitted body parameters should not be encoded during files construction. * fix(python): with_content_type should resolve to specified file content type when provided. * Update `test_file.py` and improve `with_content_type` definition. * Add docstring to with_content_type. * Update python sdk seed snapshots. * Minor fix; update `file-upload` seed test snapshot to include new changes. * Snapshot updates. * Snapshot updates. * Update snapshots. * Update ts and java snapshots. * update --- .../python/core_utilities/shared/file.py | 21 +- .../file_upload_request_body_parameters.py | 39 ++- generators/python/tests/utils/test_file.py | 36 ++- .../test-definitions/file-upload.json | 32 +++ .../test-definitions/file-upload.json | 102 +++++++ .../__test__/__snapshots__/file-upload.json | 12 + .../file-upload/.mock/definition/service.yml | 3 + .../resources/service/service/service.py | 9 +- .../.mock/generators.yml | 11 +- .../.mock/openapi/openapi.yml | 33 --- .../fastapi/grpc-proto-exhaustive/__init__.py | 2 - .../resources/dataservice/service/service.py | 58 +--- .../fastapi/grpc-proto-exhaustive/security.py | 9 - .../file-upload/.mock/definition/service.yml | 3 + seed/go-fiber/file-upload/service.go | 5 +- .../grpc-proto-exhaustive/dataservice.go | 66 ++--- .../internal}/extra_properties.go | 2 +- .../internal}/extra_properties_test.go | 2 +- .../internal}/stringer.go | 2 +- .../{core => internal}/time.go | 2 +- .../{core => internal}/extra_properties.go | 2 +- .../extra_properties_test.go | 2 +- .../grpc-proto/internal}/stringer.go | 2 +- .../grpc-proto/internal}/time.go | 2 +- seed/go-fiber/grpc-proto/userservice.go | 10 +- .../.mock/definition/service.yml | 3 + .../inline-file-properties/service.go | 7 +- .../inline-file-properties/service/client.go | 5 + .../.mock/definition/service.yml | 3 + .../file-upload/no-custom-config/service.go | 5 +- .../no-custom-config/service/client.go | 5 + .../grpc-proto-exhaustive/client/client.go | 7 +- .../grpc-proto-exhaustive/core/api_error.go | 42 +++ .../go-sdk/grpc-proto-exhaustive/core/http.go | 8 + .../grpc-proto-exhaustive/core/multipart.go | 195 -------------- .../core/multipart_test.go | 251 ------------------ .../grpc-proto-exhaustive/dataservice.go | 98 +++---- .../dataservice/client.go | 39 +-- .../{core/core.go => internal/caller.go} | 99 +------ .../internal/caller_test.go} | 15 +- .../internal}/extra_properties.go | 2 +- .../internal}/extra_properties_test.go | 2 +- .../grpc-proto-exhaustive/internal/http.go | 37 +++ .../internal}/query.go | 2 +- .../{core => internal}/query_test.go | 2 +- .../{core => internal}/retrier.go | 3 +- .../internal}/retrier_test.go | 15 +- .../internal}/stringer.go | 2 +- .../internal}/time.go | 2 +- seed/go-sdk/grpc-proto/client/client.go | 7 +- seed/go-sdk/grpc-proto/core/api_error.go | 42 +++ seed/go-sdk/grpc-proto/core/http.go | 8 + seed/go-sdk/grpc-proto/core/multipart.go | 195 -------------- seed/go-sdk/grpc-proto/core/multipart_test.go | 251 ------------------ .../{core/core.go => internal/caller.go} | 99 +------ .../internal/caller_test.go} | 15 +- .../{core => internal}/extra_properties.go | 2 +- .../internal}/extra_properties_test.go | 2 +- seed/go-sdk/grpc-proto/internal/http.go | 37 +++ .../core => grpc-proto/internal}/query.go | 2 +- .../{core => internal}/query_test.go | 2 +- .../grpc-proto/{core => internal}/retrier.go | 3 +- .../internal}/retrier_test.go | 15 +- .../grpc-proto/internal}/stringer.go | 2 +- .../grpc-proto/internal}/time.go | 2 +- seed/go-sdk/grpc-proto/userservice.go | 14 +- seed/go-sdk/grpc-proto/userservice/client.go | 11 +- .../file-upload/.mock/definition/service.yml | 3 + .../resources/service/ServiceClient.java | 3 + .../requests/WithContentTypeRequest.java | 39 ++- .../definition/endpoints/content-type.yml | 19 ++ .../file-upload/.mock/definition/service.yml | 3 + seed/openapi/file-upload/openapi.yml | 3 + .../license/.mock/definition/__package__.yml | 13 + seed/openapi/license/.mock/definition/api.yml | 1 + seed/openapi/license/.mock/fern.config.json | 1 + seed/openapi/license/.mock/generators.yml | 1 + seed/openapi/license/openapi.yml | 26 ++ seed/openapi/license/snippet-templates.json | 0 seed/openapi/license/snippet.json | 0 .../definition/endpoints/content-type.yml | 19 ++ .../file-upload/.mock/definition/service.yml | 3 + .../.mock/generators.yml | 11 +- .../.mock/openapi/openapi.yml | 33 --- .../license/.github/workflows/ci.yml | 48 ++++ seed/php-model/license/.gitignore | 4 + .../license/.mock/definition/__package__.yml | 13 + .../license/.mock/definition/api.yml | 1 + seed/php-model/license/.mock/fern.config.json | 1 + seed/php-model/license/.mock/generators.yml | 1 + seed/php-model/license/composer.json | 40 +++ seed/php-model/license/phpstan.neon | 5 + seed/php-model/license/phpunit.xml | 7 + seed/php-model/license/snippet-templates.json | 0 seed/php-model/license/snippet.json | 0 .../license/src/Core/Json/JsonDecoder.php | 161 +++++++++++ .../src/Core/Json/JsonDeserializer.php | 204 ++++++++++++++ .../license/src/Core/Json/JsonEncoder.php | 20 ++ .../license/src/Core/Json/JsonProperty.php | 13 + .../src/Core/Json/JsonSerializableType.php | 182 +++++++++++++ .../license/src/Core/Json/JsonSerializer.php | 192 ++++++++++++++ .../php-model/license/src/Core/Json/Utils.php | 61 +++++ .../license/src/Core/Types/ArrayType.php | 16 ++ .../license/src/Core/Types/Constant.php | 12 + .../php-model/license/src/Core/Types/Date.php | 16 ++ .../license/src/Core/Types/Union.php | 62 +++++ seed/php-model/license/src/Type.php | 29 ++ .../tests/Seed/Core/Json/DateArrayTest.php | 54 ++++ .../tests/Seed/Core/Json/EmptyArrayTest.php | 71 +++++ .../license/tests/Seed/Core/Json/EnumTest.php | 76 ++++++ .../tests/Seed/Core/Json/ExhaustiveTest.php | 197 ++++++++++++++ .../tests/Seed/Core/Json/InvalidTest.php | 42 +++ .../Seed/Core/Json/NestedUnionArrayTest.php | 89 +++++++ .../tests/Seed/Core/Json/NullPropertyTest.php | 53 ++++ .../Seed/Core/Json/NullableArrayTest.php | 49 ++++ .../tests/Seed/Core/Json/ScalarTest.php | 116 ++++++++ .../tests/Seed/Core/Json/TraitTest.php | 60 +++++ .../tests/Seed/Core/Json/UnionArrayTest.php | 57 ++++ .../Seed/Core/Json/UnionPropertyTest.php | 115 ++++++++ .../file-upload/.mock/definition/service.yml | 3 + .../Requests/WithContentTypeRequest.php | 8 + .../file-upload/src/Service/ServiceClient.php | 7 + .../alias-extends/src/seed/core/file.py | 21 +- seed/python-sdk/alias/src/seed/core/file.py | 21 +- .../python-sdk/any-auth/src/seed/core/file.py | 21 +- .../api-wide-base-path/src/seed/core/file.py | 21 +- .../audiences/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../basic-auth/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- seed/python-sdk/bytes/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../circular-references/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../custom-auth/src/seed/core/file.py | 21 +- .../no-custom-config/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../enum/real-enum/src/seed/core/file.py | 21 +- .../enum/strenum/src/seed/core/file.py | 21 +- .../error-property/src/seed/core/file.py | 21 +- .../client-filename/src/seed/core/file.py | 21 +- .../no-custom-config/src/seed/core/file.py | 21 +- .../examples/readme/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../extra_dependencies/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../five-second-timeout/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../improved_imports/src/seed/core/file.py | 21 +- .../infinite-timeout/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../no-custom-config/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../pydantic-v1-wrapped/src/seed/core/file.py | 21 +- .../pydantic-v1/src/seed/core/file.py | 21 +- .../pyproject_extras/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../union-utils/src/seed/core/file.py | 21 +- seed/python-sdk/extends/src/seed/core/file.py | 21 +- .../extra-properties/src/seed/core/file.py | 21 +- .../default-chunk-size/src/seed/core/file.py | 21 +- .../no-custom-config/src/seed/core/file.py | 21 +- .../file-upload/.mock/definition/service.yml | 3 + .../file-upload/snippet-templates.json | 60 +++++ .../file-upload/src/seed/core/file.py | 21 +- .../file-upload/src/seed/service/client.py | 34 ++- seed/python-sdk/folders/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../grpc-proto/src/seed/core/file.py | 21 +- .../idempotency-headers/src/seed/core/file.py | 21 +- seed/python-sdk/imdb/src/seed/core/file.py | 21 +- seed/python-sdk/license/src/seed/core/file.py | 21 +- .../no-custom-config/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../mixed-case/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../multi-line-docs/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../no-environment/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- seed/python-sdk/object/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../python-sdk/optional/src/seed/core/file.py | 21 +- .../package-yml/src/seed/core/file.py | 21 +- .../pagination/src/seed/core/file.py | 21 +- .../plain-text/src/seed/core/file.py | 21 +- .../query-parameters/src/seed/core/file.py | 21 +- .../reserved-keywords/src/seed/core/file.py | 21 +- .../response-property/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../server-sent-events/src/seed/core/file.py | 21 +- .../simple-fhir/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../streaming-parameter/src/seed/core/file.py | 21 +- .../no-custom-config/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- seed/python-sdk/trace/src/seed/core/file.py | 21 +- .../src/seed/core/file.py | 21 +- .../no-custom-config/src/seed/core/file.py | 21 +- .../union-naming-v1/src/seed/core/file.py | 21 +- .../unions/union-utils/src/seed/core/file.py | 21 +- seed/python-sdk/unknown/src/seed/core/file.py | 21 +- .../no-custom-config/src/seed/core/file.py | 21 +- .../with-defaults/src/seed/core/file.py | 21 +- .../variables/src/seed/core/file.py | 21 +- .../version-no-default/src/seed/core/file.py | 21 +- seed/python-sdk/version/src/seed/core/file.py | 21 +- .../websocket/src/seed/core/file.py | 21 +- .../file-upload/.mock/definition/service.yml | 3 + .../lib/fern_file_upload/service/client.rb | 14 +- .../.mock/definition/service.yml | 3 + .../no-custom-config/snippet-templates.json | 28 ++ .../api/resources/service/client/Client.ts | 4 + .../client/requests/WithContentTypeRequest.ts | 1 + .../.mock/definition/service.yml | 3 + .../snippet-templates.json | 28 ++ .../api/resources/service/client/Client.ts | 4 + .../client/requests/WithContentTypeRequest.ts | 1 + .../apis/file-upload/definition/service.yml | 3 + 228 files changed, 4231 insertions(+), 2178 deletions(-) delete mode 100644 seed/fastapi/grpc-proto-exhaustive/.mock/openapi/openapi.yml delete mode 100644 seed/fastapi/grpc-proto-exhaustive/security.py rename seed/{go-sdk/grpc-proto-exhaustive/core => go-fiber/grpc-proto-exhaustive/internal}/extra_properties.go (99%) rename seed/{go-sdk/grpc-proto-exhaustive/core => go-fiber/grpc-proto-exhaustive/internal}/extra_properties_test.go (99%) rename seed/{go-sdk/grpc-proto-exhaustive/core => go-fiber/grpc-proto-exhaustive/internal}/stringer.go (94%) rename seed/go-fiber/grpc-proto-exhaustive/{core => internal}/time.go (99%) rename seed/go-fiber/grpc-proto/{core => internal}/extra_properties.go (99%) rename seed/go-fiber/grpc-proto/{core => internal}/extra_properties_test.go (99%) rename seed/{go-sdk/grpc-proto/core => go-fiber/grpc-proto/internal}/stringer.go (94%) rename seed/{go-sdk/grpc-proto-exhaustive/core => go-fiber/grpc-proto/internal}/time.go (99%) create mode 100644 seed/go-sdk/grpc-proto-exhaustive/core/api_error.go create mode 100644 seed/go-sdk/grpc-proto-exhaustive/core/http.go delete mode 100644 seed/go-sdk/grpc-proto-exhaustive/core/multipart.go delete mode 100644 seed/go-sdk/grpc-proto-exhaustive/core/multipart_test.go rename seed/go-sdk/grpc-proto-exhaustive/{core/core.go => internal/caller.go} (70%) rename seed/go-sdk/{grpc-proto/core/core_test.go => grpc-proto-exhaustive/internal/caller_test.go} (97%) rename seed/{go-fiber/grpc-proto-exhaustive/core => go-sdk/grpc-proto-exhaustive/internal}/extra_properties.go (99%) rename seed/go-sdk/{grpc-proto/core => grpc-proto-exhaustive/internal}/extra_properties_test.go (99%) create mode 100644 seed/go-sdk/grpc-proto-exhaustive/internal/http.go rename seed/go-sdk/{grpc-proto/core => grpc-proto-exhaustive/internal}/query.go (99%) rename seed/go-sdk/grpc-proto-exhaustive/{core => internal}/query_test.go (99%) rename seed/go-sdk/grpc-proto-exhaustive/{core => internal}/retrier.go (98%) rename seed/go-sdk/{grpc-proto/core => grpc-proto-exhaustive/internal}/retrier_test.go (95%) rename seed/{go-fiber/grpc-proto-exhaustive/core => go-sdk/grpc-proto-exhaustive/internal}/stringer.go (94%) rename seed/go-sdk/{grpc-proto/core => grpc-proto-exhaustive/internal}/time.go (99%) create mode 100644 seed/go-sdk/grpc-proto/core/api_error.go create mode 100644 seed/go-sdk/grpc-proto/core/http.go delete mode 100644 seed/go-sdk/grpc-proto/core/multipart.go delete mode 100644 seed/go-sdk/grpc-proto/core/multipart_test.go rename seed/go-sdk/grpc-proto/{core/core.go => internal/caller.go} (70%) rename seed/go-sdk/{grpc-proto-exhaustive/core/core_test.go => grpc-proto/internal/caller_test.go} (97%) rename seed/go-sdk/grpc-proto/{core => internal}/extra_properties.go (99%) rename seed/{go-fiber/grpc-proto-exhaustive/core => go-sdk/grpc-proto/internal}/extra_properties_test.go (99%) create mode 100644 seed/go-sdk/grpc-proto/internal/http.go rename seed/go-sdk/{grpc-proto-exhaustive/core => grpc-proto/internal}/query.go (99%) rename seed/go-sdk/grpc-proto/{core => internal}/query_test.go (99%) rename seed/go-sdk/grpc-proto/{core => internal}/retrier.go (98%) rename seed/go-sdk/{grpc-proto-exhaustive/core => grpc-proto/internal}/retrier_test.go (95%) rename seed/{go-fiber/grpc-proto/core => go-sdk/grpc-proto/internal}/stringer.go (94%) rename seed/{go-fiber/grpc-proto/core => go-sdk/grpc-proto/internal}/time.go (99%) create mode 100644 seed/openapi/exhaustive/.mock/definition/endpoints/content-type.yml create mode 100644 seed/openapi/license/.mock/definition/__package__.yml create mode 100644 seed/openapi/license/.mock/definition/api.yml create mode 100644 seed/openapi/license/.mock/fern.config.json create mode 100644 seed/openapi/license/.mock/generators.yml create mode 100644 seed/openapi/license/openapi.yml create mode 100644 seed/openapi/license/snippet-templates.json create mode 100644 seed/openapi/license/snippet.json create mode 100644 seed/php-model/exhaustive/.mock/definition/endpoints/content-type.yml delete mode 100644 seed/php-model/grpc-proto-exhaustive/.mock/openapi/openapi.yml create mode 100644 seed/php-model/license/.github/workflows/ci.yml create mode 100644 seed/php-model/license/.gitignore create mode 100644 seed/php-model/license/.mock/definition/__package__.yml create mode 100644 seed/php-model/license/.mock/definition/api.yml create mode 100644 seed/php-model/license/.mock/fern.config.json create mode 100644 seed/php-model/license/.mock/generators.yml create mode 100644 seed/php-model/license/composer.json create mode 100644 seed/php-model/license/phpstan.neon create mode 100644 seed/php-model/license/phpunit.xml create mode 100644 seed/php-model/license/snippet-templates.json create mode 100644 seed/php-model/license/snippet.json create mode 100644 seed/php-model/license/src/Core/Json/JsonDecoder.php create mode 100644 seed/php-model/license/src/Core/Json/JsonDeserializer.php create mode 100644 seed/php-model/license/src/Core/Json/JsonEncoder.php create mode 100644 seed/php-model/license/src/Core/Json/JsonProperty.php create mode 100644 seed/php-model/license/src/Core/Json/JsonSerializableType.php create mode 100644 seed/php-model/license/src/Core/Json/JsonSerializer.php create mode 100644 seed/php-model/license/src/Core/Json/Utils.php create mode 100644 seed/php-model/license/src/Core/Types/ArrayType.php create mode 100644 seed/php-model/license/src/Core/Types/Constant.php create mode 100644 seed/php-model/license/src/Core/Types/Date.php create mode 100644 seed/php-model/license/src/Core/Types/Union.php create mode 100644 seed/php-model/license/src/Type.php create mode 100644 seed/php-model/license/tests/Seed/Core/Json/DateArrayTest.php create mode 100644 seed/php-model/license/tests/Seed/Core/Json/EmptyArrayTest.php create mode 100644 seed/php-model/license/tests/Seed/Core/Json/EnumTest.php create mode 100644 seed/php-model/license/tests/Seed/Core/Json/ExhaustiveTest.php create mode 100644 seed/php-model/license/tests/Seed/Core/Json/InvalidTest.php create mode 100644 seed/php-model/license/tests/Seed/Core/Json/NestedUnionArrayTest.php create mode 100644 seed/php-model/license/tests/Seed/Core/Json/NullPropertyTest.php create mode 100644 seed/php-model/license/tests/Seed/Core/Json/NullableArrayTest.php create mode 100644 seed/php-model/license/tests/Seed/Core/Json/ScalarTest.php create mode 100644 seed/php-model/license/tests/Seed/Core/Json/TraitTest.php create mode 100644 seed/php-model/license/tests/Seed/Core/Json/UnionArrayTest.php create mode 100644 seed/php-model/license/tests/Seed/Core/Json/UnionPropertyTest.php diff --git a/generators/python/core_utilities/shared/file.py b/generators/python/core_utilities/shared/file.py index c7341b7ffad..d99e2515390 100644 --- a/generators/python/core_utilities/shared/file.py +++ b/generators/python/core_utilities/shared/file.py @@ -41,20 +41,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/generators/python/src/fern_python/generators/sdk/client_generator/request_body_parameters/file_upload_request_body_parameters.py b/generators/python/src/fern_python/generators/sdk/client_generator/request_body_parameters/file_upload_request_body_parameters.py index b04b4802c22..6e0b5b1e938 100644 --- a/generators/python/src/fern_python/generators/sdk/client_generator/request_body_parameters/file_upload_request_body_parameters.py +++ b/generators/python/src/fern_python/generators/sdk/client_generator/request_body_parameters/file_upload_request_body_parameters.py @@ -132,21 +132,42 @@ def write(writer: AST.NodeWriter) -> None: writer.write_line("{") with writer.indent(): for property in self._request.properties: + type_hint = self._get_property_type(property) property_as_union = property.get_as_union() if property_as_union.type == "bodyProperty" and property_as_union.content_type is not None: - writer.write(f'"{property_as_union.name.wire_value}": (None, ') - writer.write_node( - AST.Expression( - Json.dumps( + if type_hint.is_optional: + writer.write_line("**(") + with writer.indent(): + writer.write(f'{{"{property_as_union.name.wire_value}": (None, ') + writer.write_node( AST.Expression( - self._context.core_utilities.jsonable_encoder( - AST.Expression(property_as_union.name.wire_value) + Json.dumps( + AST.Expression( + self._context.core_utilities.jsonable_encoder( + AST.Expression(property_as_union.name.wire_value) + ) + ) + ) + ) + ) + writer.write_line(f', "{property_as_union.content_type}")}}') + writer.write_line(f"if {property_as_union.name.wire_value} is not OMIT ") + writer.write_line("else {}") + writer.write_line("),") + else: + writer.write(f'"{property_as_union.name.wire_value}": (None, ') + writer.write_node( + AST.Expression( + Json.dumps( + AST.Expression( + self._context.core_utilities.jsonable_encoder( + AST.Expression(property_as_union.name.wire_value) + ) ) ) ) ) - ) - writer.write_line(f', "{property_as_union.content_type}"),') + writer.write_line(f', "{property_as_union.content_type}"),') elif property_as_union.type == "file": file_property_as_union = property_as_union.value.get_as_union() if file_property_as_union.content_type is not None: @@ -154,7 +175,7 @@ def write(writer: AST.NodeWriter) -> None: writer.write_node( self._context.core_utilities.with_content_type( AST.Expression( - f'file={file_property_as_union.key.wire_value}, content_type="{file_property_as_union.content_type}"' + f'file={file_property_as_union.key.wire_value}, default_content_type="{file_property_as_union.content_type}"' ) ) ) diff --git a/generators/python/tests/utils/test_file.py b/generators/python/tests/utils/test_file.py index 5935ade3d19..0e2b778ea56 100644 --- a/generators/python/tests/utils/test_file.py +++ b/generators/python/tests/utils/test_file.py @@ -6,40 +6,52 @@ def test_file_content_bytes() -> None: - result = with_content_type(file=b"file content", content_type="text/plain") + result = with_content_type(file=b"file content", default_content_type="text/plain") assert result == (None, b"file content", "text/plain") + def test_file_content_str() -> None: - result = with_content_type(file="file content", content_type="text/plain") + result = with_content_type(file="file content", default_content_type="text/plain") assert result == (None, "file content", "text/plain") + def test_file_content_io() -> None: file_like = BytesIO(b"file content") - result = with_content_type(file=file_like, content_type="text/plain") + result = with_content_type(file=file_like, default_content_type="text/plain") filename, content, contentType = cast(Tuple[Optional[str], FileContent, Optional[str]], result) assert filename is None assert content == file_like assert contentType == "text/plain" + def test_tuple_2() -> None: - result = with_content_type(file=("example.txt", b"file content"), content_type="text/plain") + result = with_content_type(file=("example.txt", b"file content"), default_content_type="text/plain") assert result == ("example.txt", b"file content", "text/plain") + def test_tuple_3() -> None: - result = with_content_type(file=("example.txt", b"file content", "application/octet-stream"), content_type="text/plain") - assert result == ("example.txt", b"file content", "text/plain") + result = with_content_type( + file=("example.txt", b"file content", "application/octet-stream"), default_content_type="text/plain" + ) + assert result == ("example.txt", b"file content", "application/octet-stream") + def test_tuple_4() -> None: - result = with_content_type(file=("example.txt", b"file content", "application/octet-stream", {"X-Custom": "value"}), content_type="text/plain") - assert result == ("example.txt", b"file content", "text/plain", {"X-Custom": "value"}) + result = with_content_type( + file=("example.txt", b"file content", "application/octet-stream", {"X-Custom": "value"}), + default_content_type="text/plain", + ) + assert result == ("example.txt", b"file content", "application/octet-stream", {"X-Custom": "value"}) + def test_none_filename() -> None: - result = with_content_type(file=(None, b"file content"), content_type="text/plain") + result = with_content_type(file=(None, b"file content"), default_content_type="text/plain") assert result == (None, b"file content", "text/plain") + def test_invalid_tuple_length() -> None: with pytest.raises(ValueError): with_content_type( - file=("example.txt", b"file content", "text/plain", {}, "extra"), # type: ignore - content_type="application/json" - ) \ No newline at end of file + file=("example.txt", b"file content", "text/plain", {}, "extra"), # type: ignore + default_content_type="application/json", + ) diff --git a/packages/cli/dynamic-snippets/src/__test__/test-definitions/file-upload.json b/packages/cli/dynamic-snippets/src/__test__/test-definitions/file-upload.json index d2b99cd4ad2..5cedc153b8a 100644 --- a/packages/cli/dynamic-snippets/src/__test__/test-definitions/file-upload.json +++ b/packages/cli/dynamic-snippets/src/__test__/test-definitions/file-upload.json @@ -1503,6 +1503,38 @@ "_type": "named", "value": "type_service:MyObject" } + }, + { + "type": "bodyProperty", + "name": { + "name": { + "originalName": "foobar", + "camelCase": { + "unsafeName": "foobar", + "safeName": "foobar" + }, + "snakeCase": { + "unsafeName": "foobar", + "safeName": "foobar" + }, + "screamingSnakeCase": { + "unsafeName": "FOOBAR", + "safeName": "FOOBAR" + }, + "pascalCase": { + "unsafeName": "Foobar", + "safeName": "Foobar" + } + }, + "wireValue": "foobar" + }, + "typeReference": { + "_type": "optional", + "value": { + "_type": "named", + "value": "type_service:MyObject" + } + } } ] } diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/file-upload.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/file-upload.json index ccb2ecfd776..2ceb983dfe9 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/file-upload.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/file-upload.json @@ -2011,6 +2011,108 @@ }, "availability": null, "docs": null + }, + { + "type": "bodyProperty", + "contentType": "application/json", + "name": { + "name": { + "originalName": "foobar", + "camelCase": { + "unsafeName": "foobar", + "safeName": "foobar" + }, + "snakeCase": { + "unsafeName": "foobar", + "safeName": "foobar" + }, + "screamingSnakeCase": { + "unsafeName": "FOOBAR", + "safeName": "FOOBAR" + }, + "pascalCase": { + "unsafeName": "Foobar", + "safeName": "Foobar" + } + }, + "wireValue": "foobar" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": { + "originalName": "MyObject", + "camelCase": { + "unsafeName": "myObject", + "safeName": "myObject" + }, + "snakeCase": { + "unsafeName": "my_object", + "safeName": "my_object" + }, + "screamingSnakeCase": { + "unsafeName": "MY_OBJECT", + "safeName": "MY_OBJECT" + }, + "pascalCase": { + "unsafeName": "MyObject", + "safeName": "MyObject" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "service", + "camelCase": { + "unsafeName": "service", + "safeName": "service" + }, + "snakeCase": { + "unsafeName": "service", + "safeName": "service" + }, + "screamingSnakeCase": { + "unsafeName": "SERVICE", + "safeName": "SERVICE" + }, + "pascalCase": { + "unsafeName": "Service", + "safeName": "Service" + } + } + ], + "packagePath": [], + "file": { + "originalName": "service", + "camelCase": { + "unsafeName": "service", + "safeName": "service" + }, + "snakeCase": { + "unsafeName": "service", + "safeName": "service" + }, + "screamingSnakeCase": { + "unsafeName": "SERVICE", + "safeName": "SERVICE" + }, + "pascalCase": { + "unsafeName": "Service", + "safeName": "Service" + } + } + }, + "typeId": "type_service:MyObject", + "default": null, + "inline": null + } + } + }, + "availability": null, + "docs": null } ], "docs": null diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/file-upload.json b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/file-upload.json index 2dca7342452..30c84afacd1 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/file-upload.json +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/file-upload.json @@ -407,6 +407,18 @@ "value": "type_service:MyObject" }, "contentType": "application/json" + }, + { + "type": "bodyProperty", + "key": "foobar", + "valueType": { + "type": "optional", + "itemType": { + "type": "id", + "value": "type_service:MyObject" + } + }, + "contentType": "application/json" } ] } diff --git a/seed/fastapi/file-upload/.mock/definition/service.yml b/seed/fastapi/file-upload/.mock/definition/service.yml index 5ae95021c2d..956e6ba73ba 100644 --- a/seed/fastapi/file-upload/.mock/definition/service.yml +++ b/seed/fastapi/file-upload/.mock/definition/service.yml @@ -64,6 +64,9 @@ service: bar: type: MyObject content-type: application/json + foobar: + type: optional + content-type: application/json types: Id: string diff --git a/seed/fastapi/file-upload/resources/service/service/service.py b/seed/fastapi/file-upload/resources/service/service/service.py index 33b9974efd5..23bc4dbca14 100644 --- a/seed/fastapi/file-upload/resources/service/service/service.py +++ b/seed/fastapi/file-upload/resources/service/service/service.py @@ -60,7 +60,12 @@ def just_file_with_query_params( @abc.abstractmethod def with_content_type( - self, *, file: fastapi.UploadFile, foo: str, bar: MyObject + self, + *, + file: fastapi.UploadFile, + foo: str, + bar: MyObject, + foobar: typing.Optional[MyObject] = None, ) -> None: ... """ @@ -308,6 +313,8 @@ def __init_with_content_type(cls, router: fastapi.APIRouter) -> None: new_parameters.append(parameter.replace(default=fastapi.Body(...))) elif parameter_name == "bar": new_parameters.append(parameter.replace(default=fastapi.Body(...))) + elif parameter_name == "foobar": + new_parameters.append(parameter.replace(default=fastapi.Body(...))) else: new_parameters.append(parameter) setattr( diff --git a/seed/fastapi/grpc-proto-exhaustive/.mock/generators.yml b/seed/fastapi/grpc-proto-exhaustive/.mock/generators.yml index c23323621f2..972ed6d7b73 100644 --- a/seed/fastapi/grpc-proto-exhaustive/.mock/generators.yml +++ b/seed/fastapi/grpc-proto-exhaustive/.mock/generators.yml @@ -1,7 +1,6 @@ api: - - path: openapi/openapi.yml - - proto: - root: proto - target: proto/data/v1/data.proto - overrides: overrides.yml - local-generation: true + - proto: + root: proto + target: proto/data/v1/data.proto + overrides: overrides.yml + local-generation: true \ No newline at end of file diff --git a/seed/fastapi/grpc-proto-exhaustive/.mock/openapi/openapi.yml b/seed/fastapi/grpc-proto-exhaustive/.mock/openapi/openapi.yml deleted file mode 100644 index ebc23143df3..00000000000 --- a/seed/fastapi/grpc-proto-exhaustive/.mock/openapi/openapi.yml +++ /dev/null @@ -1,33 +0,0 @@ -openapi: 3.0.3 -info: - title: Test API - version: 1.0.0 -servers: - - url: https://localhost -tags: - - name: dataservice -paths: - /foo: - post: - tag: dataservice - x-fern-sdk-group-name: - - dataservice - x-fern-sdk-method-name: foo - security: - - ApiKeyAuth: [] - operationId: foo - responses: - "200": - content: - application/json: - schema: - type: object - -security: - - ApiKeyAuth: [] -components: - securitySchemes: - ApiKeyAuth: - type: apiKey - in: header - name: X-API-Key diff --git a/seed/fastapi/grpc-proto-exhaustive/__init__.py b/seed/fastapi/grpc-proto-exhaustive/__init__.py index 5d12cddf903..f6880eeb568 100644 --- a/seed/fastapi/grpc-proto-exhaustive/__init__.py +++ b/seed/fastapi/grpc-proto-exhaustive/__init__.py @@ -8,7 +8,6 @@ UploadRequest, dataservice, ) -from .security import ApiAuth from .types import ( Column, DeleteResponse, @@ -31,7 +30,6 @@ ) __all__ = [ - "ApiAuth", "Column", "DeleteRequest", "DeleteResponse", diff --git a/seed/fastapi/grpc-proto-exhaustive/resources/dataservice/service/service.py b/seed/fastapi/grpc-proto-exhaustive/resources/dataservice/service/service.py index c4ee4269b48..ad37342c366 100644 --- a/seed/fastapi/grpc-proto-exhaustive/resources/dataservice/service/service.py +++ b/seed/fastapi/grpc-proto-exhaustive/resources/dataservice/service/service.py @@ -1,15 +1,14 @@ # This file was auto-generated by Fern from our API Definition. from ....core.abstract_fern_service import AbstractFernService -from ....security import ApiAuth -import typing -import abc from .upload_request import UploadRequest from ....types.upload_response import UploadResponse +import abc from .delete_request import DeleteRequest from ....types.delete_response import DeleteResponse from .describe_request import DescribeRequest from ....types.describe_response import DescribeResponse +import typing from ....types.fetch_response import FetchResponse from ....types.list_response import ListResponse from .query_request import QueryRequest @@ -18,7 +17,6 @@ from ....types.update_response import UpdateResponse import fastapi import inspect -from ....security import FernAuth from ....core.exceptions.fern_http_exception import FernHTTPException import logging import functools @@ -34,11 +32,6 @@ class AbstractDataserviceService(AbstractFernService): function. """ - @abc.abstractmethod - def foo( - self, *, auth: ApiAuth - ) -> typing.Dict[str, typing.Optional[typing.Any]]: ... - @abc.abstractmethod def upload(self, *, body: UploadRequest) -> UploadResponse: ... @@ -79,7 +72,6 @@ def update(self, *, body: UpdateRequest) -> UpdateResponse: ... @classmethod def _init_fern(cls, router: fastapi.APIRouter) -> None: - cls.__init_foo(router=router) cls.__init_upload(router=router) cls.__init_delete(router=router) cls.__init_describe(router=router) @@ -88,52 +80,6 @@ def _init_fern(cls, router: fastapi.APIRouter) -> None: cls.__init_query(router=router) cls.__init_update(router=router) - @classmethod - def __init_foo(cls, router: fastapi.APIRouter) -> None: - endpoint_function = inspect.signature(cls.foo) - new_parameters: typing.List[inspect.Parameter] = [] - for index, (parameter_name, parameter) in enumerate( - endpoint_function.parameters.items() - ): - if index == 0: - new_parameters.append(parameter.replace(default=fastapi.Depends(cls))) - elif parameter_name == "auth": - new_parameters.append( - parameter.replace(default=fastapi.Depends(FernAuth)) - ) - else: - new_parameters.append(parameter) - setattr( - cls.foo, - "__signature__", - endpoint_function.replace(parameters=new_parameters), - ) - - @functools.wraps(cls.foo) - def wrapper( - *args: typing.Any, **kwargs: typing.Any - ) -> typing.Dict[str, typing.Optional[typing.Any]]: - try: - return cls.foo(*args, **kwargs) - except FernHTTPException as e: - logging.getLogger(f"{cls.__module__}.{cls.__name__}").warn( - f"Endpoint 'foo' unexpectedly threw {e.__class__.__name__}. " - + f"If this was intentional, please add {e.__class__.__name__} to " - + "the endpoint's errors list in your Fern Definition." - ) - raise e - - # this is necessary for FastAPI to find forward-ref'ed type hints. - # https://github.com/tiangolo/fastapi/pull/5077 - wrapper.__globals__.update(cls.foo.__globals__) - - router.post( - path="/foo", - response_model=typing.Dict[str, typing.Optional[typing.Any]], - description=AbstractDataserviceService.foo.__doc__, - **get_route_args(cls.foo, default_tag="dataservice"), - )(wrapper) - @classmethod def __init_upload(cls, router: fastapi.APIRouter) -> None: endpoint_function = inspect.signature(cls.upload) diff --git a/seed/fastapi/grpc-proto-exhaustive/security.py b/seed/fastapi/grpc-proto-exhaustive/security.py deleted file mode 100644 index bbf34e45c13..00000000000 --- a/seed/fastapi/grpc-proto-exhaustive/security.py +++ /dev/null @@ -1,9 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import fastapi - -ApiAuth = str - - -def FernAuth(auth: str = fastapi.Header(alias="X-API-Key")) -> str: - return auth diff --git a/seed/go-fiber/file-upload/.mock/definition/service.yml b/seed/go-fiber/file-upload/.mock/definition/service.yml index 5ae95021c2d..956e6ba73ba 100644 --- a/seed/go-fiber/file-upload/.mock/definition/service.yml +++ b/seed/go-fiber/file-upload/.mock/definition/service.yml @@ -64,6 +64,9 @@ service: bar: type: MyObject content-type: application/json + foobar: + type: optional + content-type: application/json types: Id: string diff --git a/seed/go-fiber/file-upload/service.go b/seed/go-fiber/file-upload/service.go index b4a34d13bed..872da7d7f83 100644 --- a/seed/go-fiber/file-upload/service.go +++ b/seed/go-fiber/file-upload/service.go @@ -93,6 +93,7 @@ func (o ObjectType) Ptr() *ObjectType { } type WithContentTypeRequest struct { - Foo string `json:"foo" url:"-"` - Bar *MyObject `json:"bar,omitempty" url:"-"` + Foo string `json:"foo" url:"-"` + Bar *MyObject `json:"bar,omitempty" url:"-"` + Foobar *MyObject `json:"foobar,omitempty" url:"-"` } diff --git a/seed/go-fiber/grpc-proto-exhaustive/dataservice.go b/seed/go-fiber/grpc-proto-exhaustive/dataservice.go index 5561334298d..f467e2c8996 100644 --- a/seed/go-fiber/grpc-proto-exhaustive/dataservice.go +++ b/seed/go-fiber/grpc-proto-exhaustive/dataservice.go @@ -5,7 +5,7 @@ package api import ( json "encoding/json" fmt "fmt" - core "github.com/grpc-proto-exhaustive/fern/core" + internal "github.com/grpc-proto-exhaustive/fern/internal" ) type DeleteRequest struct { @@ -92,7 +92,7 @@ func (c *Column) UnmarshalJSON(data []byte) error { } *c = Column(value) - extraProperties, err := core.ExtractExtraProperties(data, *c) + extraProperties, err := internal.ExtractExtraProperties(data, *c) if err != nil { return err } @@ -102,7 +102,7 @@ func (c *Column) UnmarshalJSON(data []byte) error { } func (c *Column) String() string { - if value, err := core.StringifyJSON(c); err == nil { + if value, err := internal.StringifyJSON(c); err == nil { return value } return fmt.Sprintf("%#v", c) @@ -124,7 +124,7 @@ func (d *DeleteResponse) UnmarshalJSON(data []byte) error { } *d = DeleteResponse(value) - extraProperties, err := core.ExtractExtraProperties(data, *d) + extraProperties, err := internal.ExtractExtraProperties(data, *d) if err != nil { return err } @@ -134,7 +134,7 @@ func (d *DeleteResponse) UnmarshalJSON(data []byte) error { } func (d *DeleteResponse) String() string { - if value, err := core.StringifyJSON(d); err == nil { + if value, err := internal.StringifyJSON(d); err == nil { return value } return fmt.Sprintf("%#v", d) @@ -189,7 +189,7 @@ func (d *DescribeResponse) UnmarshalJSON(data []byte) error { } *d = DescribeResponse(value) - extraProperties, err := core.ExtractExtraProperties(data, *d) + extraProperties, err := internal.ExtractExtraProperties(data, *d) if err != nil { return err } @@ -199,7 +199,7 @@ func (d *DescribeResponse) UnmarshalJSON(data []byte) error { } func (d *DescribeResponse) String() string { - if value, err := core.StringifyJSON(d); err == nil { + if value, err := internal.StringifyJSON(d); err == nil { return value } return fmt.Sprintf("%#v", d) @@ -246,7 +246,7 @@ func (f *FetchResponse) UnmarshalJSON(data []byte) error { } *f = FetchResponse(value) - extraProperties, err := core.ExtractExtraProperties(data, *f) + extraProperties, err := internal.ExtractExtraProperties(data, *f) if err != nil { return err } @@ -256,7 +256,7 @@ func (f *FetchResponse) UnmarshalJSON(data []byte) error { } func (f *FetchResponse) String() string { - if value, err := core.StringifyJSON(f); err == nil { + if value, err := internal.StringifyJSON(f); err == nil { return value } return fmt.Sprintf("%#v", f) @@ -295,7 +295,7 @@ func (i *IndexedData) UnmarshalJSON(data []byte) error { } *i = IndexedData(value) - extraProperties, err := core.ExtractExtraProperties(data, *i) + extraProperties, err := internal.ExtractExtraProperties(data, *i) if err != nil { return err } @@ -305,7 +305,7 @@ func (i *IndexedData) UnmarshalJSON(data []byte) error { } func (i *IndexedData) String() string { - if value, err := core.StringifyJSON(i); err == nil { + if value, err := internal.StringifyJSON(i); err == nil { return value } return fmt.Sprintf("%#v", i) @@ -336,7 +336,7 @@ func (l *ListElement) UnmarshalJSON(data []byte) error { } *l = ListElement(value) - extraProperties, err := core.ExtractExtraProperties(data, *l) + extraProperties, err := internal.ExtractExtraProperties(data, *l) if err != nil { return err } @@ -346,7 +346,7 @@ func (l *ListElement) UnmarshalJSON(data []byte) error { } func (l *ListElement) String() string { - if value, err := core.StringifyJSON(l); err == nil { + if value, err := internal.StringifyJSON(l); err == nil { return value } return fmt.Sprintf("%#v", l) @@ -401,7 +401,7 @@ func (l *ListResponse) UnmarshalJSON(data []byte) error { } *l = ListResponse(value) - extraProperties, err := core.ExtractExtraProperties(data, *l) + extraProperties, err := internal.ExtractExtraProperties(data, *l) if err != nil { return err } @@ -411,7 +411,7 @@ func (l *ListResponse) UnmarshalJSON(data []byte) error { } func (l *ListResponse) String() string { - if value, err := core.StringifyJSON(l); err == nil { + if value, err := internal.StringifyJSON(l); err == nil { return value } return fmt.Sprintf("%#v", l) @@ -607,7 +607,7 @@ func (n *NamespaceSummary) UnmarshalJSON(data []byte) error { } *n = NamespaceSummary(value) - extraProperties, err := core.ExtractExtraProperties(data, *n) + extraProperties, err := internal.ExtractExtraProperties(data, *n) if err != nil { return err } @@ -617,7 +617,7 @@ func (n *NamespaceSummary) UnmarshalJSON(data []byte) error { } func (n *NamespaceSummary) String() string { - if value, err := core.StringifyJSON(n); err == nil { + if value, err := internal.StringifyJSON(n); err == nil { return value } return fmt.Sprintf("%#v", n) @@ -648,7 +648,7 @@ func (p *Pagination) UnmarshalJSON(data []byte) error { } *p = Pagination(value) - extraProperties, err := core.ExtractExtraProperties(data, *p) + extraProperties, err := internal.ExtractExtraProperties(data, *p) if err != nil { return err } @@ -658,7 +658,7 @@ func (p *Pagination) UnmarshalJSON(data []byte) error { } func (p *Pagination) String() string { - if value, err := core.StringifyJSON(p); err == nil { + if value, err := internal.StringifyJSON(p); err == nil { return value } return fmt.Sprintf("%#v", p) @@ -721,7 +721,7 @@ func (q *QueryColumn) UnmarshalJSON(data []byte) error { } *q = QueryColumn(value) - extraProperties, err := core.ExtractExtraProperties(data, *q) + extraProperties, err := internal.ExtractExtraProperties(data, *q) if err != nil { return err } @@ -731,7 +731,7 @@ func (q *QueryColumn) UnmarshalJSON(data []byte) error { } func (q *QueryColumn) String() string { - if value, err := core.StringifyJSON(q); err == nil { + if value, err := internal.StringifyJSON(q); err == nil { return value } return fmt.Sprintf("%#v", q) @@ -786,7 +786,7 @@ func (q *QueryResponse) UnmarshalJSON(data []byte) error { } *q = QueryResponse(value) - extraProperties, err := core.ExtractExtraProperties(data, *q) + extraProperties, err := internal.ExtractExtraProperties(data, *q) if err != nil { return err } @@ -796,7 +796,7 @@ func (q *QueryResponse) UnmarshalJSON(data []byte) error { } func (q *QueryResponse) String() string { - if value, err := core.StringifyJSON(q); err == nil { + if value, err := internal.StringifyJSON(q); err == nil { return value } return fmt.Sprintf("%#v", q) @@ -835,7 +835,7 @@ func (q *QueryResult) UnmarshalJSON(data []byte) error { } *q = QueryResult(value) - extraProperties, err := core.ExtractExtraProperties(data, *q) + extraProperties, err := internal.ExtractExtraProperties(data, *q) if err != nil { return err } @@ -845,7 +845,7 @@ func (q *QueryResult) UnmarshalJSON(data []byte) error { } func (q *QueryResult) String() string { - if value, err := core.StringifyJSON(q); err == nil { + if value, err := internal.StringifyJSON(q); err == nil { return value } return fmt.Sprintf("%#v", q) @@ -908,7 +908,7 @@ func (s *ScoredColumn) UnmarshalJSON(data []byte) error { } *s = ScoredColumn(value) - extraProperties, err := core.ExtractExtraProperties(data, *s) + extraProperties, err := internal.ExtractExtraProperties(data, *s) if err != nil { return err } @@ -918,7 +918,7 @@ func (s *ScoredColumn) UnmarshalJSON(data []byte) error { } func (s *ScoredColumn) String() string { - if value, err := core.StringifyJSON(s); err == nil { + if value, err := internal.StringifyJSON(s); err == nil { return value } return fmt.Sprintf("%#v", s) @@ -940,7 +940,7 @@ func (u *UpdateResponse) UnmarshalJSON(data []byte) error { } *u = UpdateResponse(value) - extraProperties, err := core.ExtractExtraProperties(data, *u) + extraProperties, err := internal.ExtractExtraProperties(data, *u) if err != nil { return err } @@ -950,7 +950,7 @@ func (u *UpdateResponse) UnmarshalJSON(data []byte) error { } func (u *UpdateResponse) String() string { - if value, err := core.StringifyJSON(u); err == nil { + if value, err := internal.StringifyJSON(u); err == nil { return value } return fmt.Sprintf("%#v", u) @@ -981,7 +981,7 @@ func (u *UploadResponse) UnmarshalJSON(data []byte) error { } *u = UploadResponse(value) - extraProperties, err := core.ExtractExtraProperties(data, *u) + extraProperties, err := internal.ExtractExtraProperties(data, *u) if err != nil { return err } @@ -991,7 +991,7 @@ func (u *UploadResponse) UnmarshalJSON(data []byte) error { } func (u *UploadResponse) String() string { - if value, err := core.StringifyJSON(u); err == nil { + if value, err := internal.StringifyJSON(u); err == nil { return value } return fmt.Sprintf("%#v", u) @@ -1022,7 +1022,7 @@ func (u *Usage) UnmarshalJSON(data []byte) error { } *u = Usage(value) - extraProperties, err := core.ExtractExtraProperties(data, *u) + extraProperties, err := internal.ExtractExtraProperties(data, *u) if err != nil { return err } @@ -1032,7 +1032,7 @@ func (u *Usage) UnmarshalJSON(data []byte) error { } func (u *Usage) String() string { - if value, err := core.StringifyJSON(u); err == nil { + if value, err := internal.StringifyJSON(u); err == nil { return value } return fmt.Sprintf("%#v", u) diff --git a/seed/go-sdk/grpc-proto-exhaustive/core/extra_properties.go b/seed/go-fiber/grpc-proto-exhaustive/internal/extra_properties.go similarity index 99% rename from seed/go-sdk/grpc-proto-exhaustive/core/extra_properties.go rename to seed/go-fiber/grpc-proto-exhaustive/internal/extra_properties.go index a6af3e12410..540c3fd89ee 100644 --- a/seed/go-sdk/grpc-proto-exhaustive/core/extra_properties.go +++ b/seed/go-fiber/grpc-proto-exhaustive/internal/extra_properties.go @@ -1,4 +1,4 @@ -package core +package internal import ( "bytes" diff --git a/seed/go-sdk/grpc-proto-exhaustive/core/extra_properties_test.go b/seed/go-fiber/grpc-proto-exhaustive/internal/extra_properties_test.go similarity index 99% rename from seed/go-sdk/grpc-proto-exhaustive/core/extra_properties_test.go rename to seed/go-fiber/grpc-proto-exhaustive/internal/extra_properties_test.go index dc66fccd7f1..aa2510ee512 100644 --- a/seed/go-sdk/grpc-proto-exhaustive/core/extra_properties_test.go +++ b/seed/go-fiber/grpc-proto-exhaustive/internal/extra_properties_test.go @@ -1,4 +1,4 @@ -package core +package internal import ( "encoding/json" diff --git a/seed/go-sdk/grpc-proto-exhaustive/core/stringer.go b/seed/go-fiber/grpc-proto-exhaustive/internal/stringer.go similarity index 94% rename from seed/go-sdk/grpc-proto-exhaustive/core/stringer.go rename to seed/go-fiber/grpc-proto-exhaustive/internal/stringer.go index 000cf448641..312801851e0 100644 --- a/seed/go-sdk/grpc-proto-exhaustive/core/stringer.go +++ b/seed/go-fiber/grpc-proto-exhaustive/internal/stringer.go @@ -1,4 +1,4 @@ -package core +package internal import "encoding/json" diff --git a/seed/go-fiber/grpc-proto-exhaustive/core/time.go b/seed/go-fiber/grpc-proto-exhaustive/internal/time.go similarity index 99% rename from seed/go-fiber/grpc-proto-exhaustive/core/time.go rename to seed/go-fiber/grpc-proto-exhaustive/internal/time.go index d009ab30c90..ab0e269fade 100644 --- a/seed/go-fiber/grpc-proto-exhaustive/core/time.go +++ b/seed/go-fiber/grpc-proto-exhaustive/internal/time.go @@ -1,4 +1,4 @@ -package core +package internal import ( "encoding/json" diff --git a/seed/go-fiber/grpc-proto/core/extra_properties.go b/seed/go-fiber/grpc-proto/internal/extra_properties.go similarity index 99% rename from seed/go-fiber/grpc-proto/core/extra_properties.go rename to seed/go-fiber/grpc-proto/internal/extra_properties.go index a6af3e12410..540c3fd89ee 100644 --- a/seed/go-fiber/grpc-proto/core/extra_properties.go +++ b/seed/go-fiber/grpc-proto/internal/extra_properties.go @@ -1,4 +1,4 @@ -package core +package internal import ( "bytes" diff --git a/seed/go-fiber/grpc-proto/core/extra_properties_test.go b/seed/go-fiber/grpc-proto/internal/extra_properties_test.go similarity index 99% rename from seed/go-fiber/grpc-proto/core/extra_properties_test.go rename to seed/go-fiber/grpc-proto/internal/extra_properties_test.go index dc66fccd7f1..aa2510ee512 100644 --- a/seed/go-fiber/grpc-proto/core/extra_properties_test.go +++ b/seed/go-fiber/grpc-proto/internal/extra_properties_test.go @@ -1,4 +1,4 @@ -package core +package internal import ( "encoding/json" diff --git a/seed/go-sdk/grpc-proto/core/stringer.go b/seed/go-fiber/grpc-proto/internal/stringer.go similarity index 94% rename from seed/go-sdk/grpc-proto/core/stringer.go rename to seed/go-fiber/grpc-proto/internal/stringer.go index 000cf448641..312801851e0 100644 --- a/seed/go-sdk/grpc-proto/core/stringer.go +++ b/seed/go-fiber/grpc-proto/internal/stringer.go @@ -1,4 +1,4 @@ -package core +package internal import "encoding/json" diff --git a/seed/go-sdk/grpc-proto-exhaustive/core/time.go b/seed/go-fiber/grpc-proto/internal/time.go similarity index 99% rename from seed/go-sdk/grpc-proto-exhaustive/core/time.go rename to seed/go-fiber/grpc-proto/internal/time.go index d009ab30c90..ab0e269fade 100644 --- a/seed/go-sdk/grpc-proto-exhaustive/core/time.go +++ b/seed/go-fiber/grpc-proto/internal/time.go @@ -1,4 +1,4 @@ -package core +package internal import ( "encoding/json" diff --git a/seed/go-fiber/grpc-proto/userservice.go b/seed/go-fiber/grpc-proto/userservice.go index b5edcc6cdb1..17d8924847f 100644 --- a/seed/go-fiber/grpc-proto/userservice.go +++ b/seed/go-fiber/grpc-proto/userservice.go @@ -5,7 +5,7 @@ package api import ( json "encoding/json" fmt "fmt" - core "github.com/grpc-proto/fern/core" + internal "github.com/grpc-proto/fern/internal" ) type CreateRequest struct { @@ -41,7 +41,7 @@ func (c *CreateResponse) UnmarshalJSON(data []byte) error { } *c = CreateResponse(value) - extraProperties, err := core.ExtractExtraProperties(data, *c) + extraProperties, err := internal.ExtractExtraProperties(data, *c) if err != nil { return err } @@ -51,7 +51,7 @@ func (c *CreateResponse) UnmarshalJSON(data []byte) error { } func (c *CreateResponse) String() string { - if value, err := core.StringifyJSON(c); err == nil { + if value, err := internal.StringifyJSON(c); err == nil { return value } return fmt.Sprintf("%#v", c) @@ -279,7 +279,7 @@ func (u *UserModel) UnmarshalJSON(data []byte) error { } *u = UserModel(value) - extraProperties, err := core.ExtractExtraProperties(data, *u) + extraProperties, err := internal.ExtractExtraProperties(data, *u) if err != nil { return err } @@ -289,7 +289,7 @@ func (u *UserModel) UnmarshalJSON(data []byte) error { } func (u *UserModel) String() string { - if value, err := core.StringifyJSON(u); err == nil { + if value, err := internal.StringifyJSON(u); err == nil { return value } return fmt.Sprintf("%#v", u) diff --git a/seed/go-sdk/file-upload/inline-file-properties/.mock/definition/service.yml b/seed/go-sdk/file-upload/inline-file-properties/.mock/definition/service.yml index 5ae95021c2d..956e6ba73ba 100644 --- a/seed/go-sdk/file-upload/inline-file-properties/.mock/definition/service.yml +++ b/seed/go-sdk/file-upload/inline-file-properties/.mock/definition/service.yml @@ -64,6 +64,9 @@ service: bar: type: MyObject content-type: application/json + foobar: + type: optional + content-type: application/json types: Id: string diff --git a/seed/go-sdk/file-upload/inline-file-properties/service.go b/seed/go-sdk/file-upload/inline-file-properties/service.go index 0efd41c250a..83bc08b4574 100644 --- a/seed/go-sdk/file-upload/inline-file-properties/service.go +++ b/seed/go-sdk/file-upload/inline-file-properties/service.go @@ -110,7 +110,8 @@ func (o ObjectType) Ptr() *ObjectType { } type WithContentTypeRequest struct { - File io.Reader `json:"-" url:"-"` - Foo string `json:"foo" url:"-"` - Bar *MyObject `json:"bar,omitempty" url:"-"` + File io.Reader `json:"-" url:"-"` + Foo string `json:"foo" url:"-"` + Bar *MyObject `json:"bar,omitempty" url:"-"` + Foobar *MyObject `json:"foobar,omitempty" url:"-"` } diff --git a/seed/go-sdk/file-upload/inline-file-properties/service/client.go b/seed/go-sdk/file-upload/inline-file-properties/service/client.go index 2a51b655839..a8691e886eb 100644 --- a/seed/go-sdk/file-upload/inline-file-properties/service/client.go +++ b/seed/go-sdk/file-upload/inline-file-properties/service/client.go @@ -256,6 +256,11 @@ func (c *Client) WithContentType( if err := writer.WriteJSON("bar", request.Bar, internal.WithDefaultContentType("application/json")); err != nil { return err } + if request.Foobar != nil { + if err := writer.WriteJSON("foobar", request.Foobar, internal.WithDefaultContentType("application/json")); err != nil { + return err + } + } if err := writer.Close(); err != nil { return err } diff --git a/seed/go-sdk/file-upload/no-custom-config/.mock/definition/service.yml b/seed/go-sdk/file-upload/no-custom-config/.mock/definition/service.yml index 5ae95021c2d..956e6ba73ba 100644 --- a/seed/go-sdk/file-upload/no-custom-config/.mock/definition/service.yml +++ b/seed/go-sdk/file-upload/no-custom-config/.mock/definition/service.yml @@ -64,6 +64,9 @@ service: bar: type: MyObject content-type: application/json + foobar: + type: optional + content-type: application/json types: Id: string diff --git a/seed/go-sdk/file-upload/no-custom-config/service.go b/seed/go-sdk/file-upload/no-custom-config/service.go index 50b08ca5758..a0a2d481d46 100644 --- a/seed/go-sdk/file-upload/no-custom-config/service.go +++ b/seed/go-sdk/file-upload/no-custom-config/service.go @@ -100,6 +100,7 @@ func (o ObjectType) Ptr() *ObjectType { } type WithContentTypeRequest struct { - Foo string `json:"foo" url:"-"` - Bar *MyObject `json:"bar,omitempty" url:"-"` + Foo string `json:"foo" url:"-"` + Bar *MyObject `json:"bar,omitempty" url:"-"` + Foobar *MyObject `json:"foobar,omitempty" url:"-"` } diff --git a/seed/go-sdk/file-upload/no-custom-config/service/client.go b/seed/go-sdk/file-upload/no-custom-config/service/client.go index fb2c35f2abc..dec72252ed8 100644 --- a/seed/go-sdk/file-upload/no-custom-config/service/client.go +++ b/seed/go-sdk/file-upload/no-custom-config/service/client.go @@ -263,6 +263,11 @@ func (c *Client) WithContentType( if err := writer.WriteJSON("bar", request.Bar, internal.WithDefaultContentType("application/json")); err != nil { return err } + if request.Foobar != nil { + if err := writer.WriteJSON("foobar", request.Foobar, internal.WithDefaultContentType("application/json")); err != nil { + return err + } + } if err := writer.Close(); err != nil { return err } diff --git a/seed/go-sdk/grpc-proto-exhaustive/client/client.go b/seed/go-sdk/grpc-proto-exhaustive/client/client.go index 29316fd9cb8..3f7416c0368 100644 --- a/seed/go-sdk/grpc-proto-exhaustive/client/client.go +++ b/seed/go-sdk/grpc-proto-exhaustive/client/client.go @@ -5,13 +5,14 @@ package client import ( core "github.com/grpc-proto-exhaustive/fern/core" dataservice "github.com/grpc-proto-exhaustive/fern/dataservice" + internal "github.com/grpc-proto-exhaustive/fern/internal" option "github.com/grpc-proto-exhaustive/fern/option" http "net/http" ) type Client struct { baseURL string - caller *core.Caller + caller *internal.Caller header http.Header Dataservice *dataservice.Client @@ -21,8 +22,8 @@ func NewClient(opts ...option.RequestOption) *Client { options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller( - &core.CallerParams{ + caller: internal.NewCaller( + &internal.CallerParams{ Client: options.HTTPClient, MaxAttempts: options.MaxAttempts, }, diff --git a/seed/go-sdk/grpc-proto-exhaustive/core/api_error.go b/seed/go-sdk/grpc-proto-exhaustive/core/api_error.go new file mode 100644 index 00000000000..dc4190ca1cd --- /dev/null +++ b/seed/go-sdk/grpc-proto-exhaustive/core/api_error.go @@ -0,0 +1,42 @@ +package core + +import "fmt" + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} diff --git a/seed/go-sdk/grpc-proto-exhaustive/core/http.go b/seed/go-sdk/grpc-proto-exhaustive/core/http.go new file mode 100644 index 00000000000..b553350b84e --- /dev/null +++ b/seed/go-sdk/grpc-proto-exhaustive/core/http.go @@ -0,0 +1,8 @@ +package core + +import "net/http" + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} diff --git a/seed/go-sdk/grpc-proto-exhaustive/core/multipart.go b/seed/go-sdk/grpc-proto-exhaustive/core/multipart.go deleted file mode 100644 index 40f84bcec76..00000000000 --- a/seed/go-sdk/grpc-proto-exhaustive/core/multipart.go +++ /dev/null @@ -1,195 +0,0 @@ -package core - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "mime/multipart" - "net/textproto" - "strings" -) - -// Named is implemented by types that define a name. -type Named interface { - Name() string -} - -// ContentTyped is implemented by types that define a Content-Type. -type ContentTyped interface { - ContentType() string -} - -// WriteMultipartOption adapts the behavior of the multipart writer. -type WriteMultipartOption func(*writeMultipartOptions) - -// WithMultipartContentType sets the Content-Type for the multipart writer. -func WithMultipartContentType(contentType string) WriteMultipartOption { - return func(options *writeMultipartOptions) { - options.contentType = contentType - } -} - -// MultipartWriter writes multipart/form-data requests. -type MultipartWriter struct { - buffer *bytes.Buffer - writer *multipart.Writer -} - -// NewMultipartWriter creates a new multipart writer. -func NewMultipartWriter() *MultipartWriter { - buffer := bytes.NewBuffer(nil) - return &MultipartWriter{ - buffer: buffer, - writer: multipart.NewWriter(buffer), - } -} - -// Buffer returns the underlying buffer. -func (w *MultipartWriter) Buffer() *bytes.Buffer { - return w.buffer -} - -// ContentType returns the Content-Type for an HTTP multipart/form-data. -func (w *MultipartWriter) ContentType() string { - return w.writer.FormDataContentType() -} - -// WriteFile writes the given file part. -func (w *MultipartWriter) WriteFile( - field string, - file io.Reader, - opts ...WriteMultipartOption, -) error { - options := newWriteMultipartOptions(opts...) - return w.writeFile(field, file, options.contentType) -} - -// WriteField writes the given value as a form field. -func (w *MultipartWriter) WriteField( - field string, - value string, - opts ...WriteMultipartOption, -) error { - options := newWriteMultipartOptions(opts...) - return w.writeField(field, value, options.contentType) -} - -// WriteJSON writes the given value as a JSON form field. -func (w *MultipartWriter) WriteJSON( - field string, - value interface{}, - opts ...WriteMultipartOption, -) error { - bytes, err := json.Marshal(value) - if err != nil { - return err - } - return w.WriteField(field, string(bytes), opts...) -} - -// Close closes the writer. -func (w *MultipartWriter) Close() error { - return w.writer.Close() -} - -func (w *MultipartWriter) writeField( - field string, - value string, - contentType string, -) error { - part, err := w.newFormField(field, contentType) - if err != nil { - return err - } - _, err = part.Write([]byte(value)) - return err -} - -func (w *MultipartWriter) writeFile( - field string, - file io.Reader, - contentType string, -) error { - filename := getFilename(file) - if contentType == "" { - contentType = getContentType(file) - } - part, err := w.newFormPart(field, filename, contentType) - if err != nil { - return err - } - _, err = io.Copy(part, file) - return err -} - -// newFormField creates a new form field. -func (w *MultipartWriter) newFormField( - field string, - contentType string, -) (io.Writer, error) { - return w.newFormPart(field, "" /* filename */, contentType) -} - -// newFormPart creates a new form data part. -func (w *MultipartWriter) newFormPart( - field string, - filename string, - contentType string, -) (io.Writer, error) { - h := make(textproto.MIMEHeader) - h.Set("Content-Disposition", getContentDispositionHeaderValue(field, filename)) - if contentType != "" { - h.Set("Content-Type", contentType) - } - return w.writer.CreatePart(h) -} - -// writeMultipartOptions are options used to adapt the behavior of the multipart writer. -type writeMultipartOptions struct { - contentType string -} - -// newWriteMultipartOptions returns a new write multipart options. -func newWriteMultipartOptions(opts ...WriteMultipartOption) *writeMultipartOptions { - options := new(writeMultipartOptions) - for _, opt := range opts { - opt(options) - } - return options -} - -// getContentType returns the Content-Type for the given file, if any. -func getContentType(file io.Reader) string { - if v, ok := file.(ContentTyped); ok { - return v.ContentType() - } - return "" -} - -// getFilename returns the name for the given file, if any. -func getFilename(file io.Reader) string { - if v, ok := file.(Named); ok { - return v.Name() - } - return "" -} - -// getContentDispositionHeaderValue returns the value for the Content-Disposition header. -func getContentDispositionHeaderValue(field string, filename string) string { - contentDisposition := fmt.Sprintf("form-data; name=%q", field) - if filename != "" { - contentDisposition += fmt.Sprintf(`; filename=%q`, escapeQuotes(filename)) - } - return contentDisposition -} - -// https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/mime/multipart/writer.go;l=132 -var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") - -// escapeQuotes is directly referenced from the standard library. -// -// https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/mime/multipart/writer.go;l=134 -func escapeQuotes(s string) string { - return quoteEscaper.Replace(s) -} diff --git a/seed/go-sdk/grpc-proto-exhaustive/core/multipart_test.go b/seed/go-sdk/grpc-proto-exhaustive/core/multipart_test.go deleted file mode 100644 index ba73d413f0c..00000000000 --- a/seed/go-sdk/grpc-proto-exhaustive/core/multipart_test.go +++ /dev/null @@ -1,251 +0,0 @@ -package core - -import ( - "encoding/json" - "io" - "mime/multipart" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const maxFormMemory = 32 << 20 // 32MB - -type mockFile struct { - name string - content string - contentType string - - reader io.Reader -} - -func (f *mockFile) Read(p []byte) (n int, err error) { - if f.reader == nil { - f.reader = strings.NewReader(f.content) - } - return f.reader.Read(p) -} - -func (f *mockFile) Name() string { - return f.name -} - -func (f *mockFile) ContentType() string { - return f.contentType -} - -func TestMultipartWriter(t *testing.T) { - t.Run("empty", func(t *testing.T) { - w := NewMultipartWriter() - assert.NotNil(t, w.Buffer()) - assert.Contains(t, w.ContentType(), "multipart/form-data; boundary=") - require.NoError(t, w.Close()) - }) - - t.Run("write field", func(t *testing.T) { - tests := []struct { - desc string - giveField string - giveValue string - giveContentType string - }{ - { - desc: "empty field", - giveField: "empty", - giveValue: "", - }, - { - desc: "simple field", - giveField: "greeting", - giveValue: "hello world", - }, - { - desc: "field with content type", - giveField: "message", - giveValue: "hello", - giveContentType: "text/plain", - }, - } - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - w := NewMultipartWriter() - - var opts []WriteMultipartOption - if tt.giveContentType != "" { - opts = append(opts, WithMultipartContentType(tt.giveContentType)) - } - - require.NoError(t, w.WriteField(tt.giveField, tt.giveValue, opts...)) - require.NoError(t, w.Close()) - - reader := multipart.NewReader(w.Buffer(), w.writer.Boundary()) - form, err := reader.ReadForm(maxFormMemory) - require.NoError(t, err) - - assert.Equal(t, []string{tt.giveValue}, form.Value[tt.giveField]) - require.NoError(t, form.RemoveAll()) - }) - } - }) - - t.Run("write file", func(t *testing.T) { - tests := []struct { - desc string - giveField string - giveFile *mockFile - giveContentType string - }{ - { - desc: "simple file", - giveField: "file", - giveFile: &mockFile{ - name: "test.txt", - content: "hello world", - contentType: "text/plain", - }, - }, - { - desc: "override content type", - giveField: "file", - giveFile: &mockFile{ - name: "test.txt", - content: "hello world", - contentType: "text/plain", - }, - giveContentType: "application/octet-stream", - }, - } - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - w := NewMultipartWriter() - - var opts []WriteMultipartOption - if tt.giveContentType != "" { - opts = append(opts, WithMultipartContentType(tt.giveContentType)) - } - - require.NoError(t, w.WriteFile(tt.giveField, tt.giveFile, opts...)) - require.NoError(t, w.Close()) - - reader := multipart.NewReader(w.Buffer(), w.writer.Boundary()) - form, err := reader.ReadForm(maxFormMemory) - require.NoError(t, err) - defer func() { - require.NoError(t, form.RemoveAll()) - }() - - files := form.File[tt.giveField] - require.Len(t, files, 1) - - file := files[0] - assert.Equal(t, tt.giveFile.name, file.Filename) - - f, err := file.Open() - require.NoError(t, err) - defer func() { - require.NoError(t, f.Close()) - }() - - content, err := io.ReadAll(f) - require.NoError(t, err) - assert.Equal(t, tt.giveFile.content, string(content)) - - expectedContentType := tt.giveContentType - if expectedContentType == "" { - expectedContentType = tt.giveFile.contentType - } - if expectedContentType != "" { - assert.Equal(t, expectedContentType, file.Header.Get("Content-Type")) - } - }) - } - }) - - t.Run("write JSON", func(t *testing.T) { - type testStruct struct { - Name string `json:"name"` - Value int `json:"value"` - } - - tests := []struct { - desc string - giveField string - giveValue interface{} - }{ - { - desc: "struct", - giveField: "data", - giveValue: testStruct{Name: "test", Value: 123}, - }, - { - desc: "map", - giveField: "data", - giveValue: map[string]string{"key": "value"}, - }, - } - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - w := NewMultipartWriter() - - require.NoError(t, w.WriteJSON(tt.giveField, tt.giveValue)) - require.NoError(t, w.Close()) - - reader := multipart.NewReader(w.Buffer(), w.writer.Boundary()) - form, err := reader.ReadForm(maxFormMemory) - require.NoError(t, err) - defer func() { - require.NoError(t, form.RemoveAll()) - }() - - expected, err := json.Marshal(tt.giveValue) - require.NoError(t, err) - assert.Equal(t, []string{string(expected)}, form.Value[tt.giveField]) - }) - } - }) - - t.Run("complex", func(t *testing.T) { - w := NewMultipartWriter() - - // Add multiple fields and files - require.NoError(t, w.WriteField("foo", "bar")) - require.NoError(t, w.WriteField("baz", "qux")) - - hello := mockFile{name: "file.txt", content: "Hello, world!", contentType: "text/plain"} - require.NoError(t, w.WriteFile("file", &hello)) - require.NoError(t, w.WriteJSON("data", map[string]string{"key": "value"})) - require.NoError(t, w.Close()) - - reader := multipart.NewReader(w.Buffer(), w.writer.Boundary()) - form, err := reader.ReadForm(maxFormMemory) - require.NoError(t, err) - defer func() { - require.NoError(t, form.RemoveAll()) - }() - - assert.Equal(t, []string{"bar"}, form.Value["foo"]) - assert.Equal(t, []string{"qux"}, form.Value["baz"]) - assert.Equal(t, []string{`{"key":"value"}`}, form.Value["data"]) - - files := form.File["file"] - require.Len(t, files, 1) - - file := files[0] - assert.Equal(t, "file.txt", file.Filename) - - f, err := file.Open() - require.NoError(t, err) - defer func() { - require.NoError(t, f.Close()) - }() - - content, err := io.ReadAll(f) - require.NoError(t, err) - assert.Equal(t, "Hello, world!", string(content)) - }) -} diff --git a/seed/go-sdk/grpc-proto-exhaustive/dataservice.go b/seed/go-sdk/grpc-proto-exhaustive/dataservice.go index 6bfbed803d1..84ee94a71bb 100644 --- a/seed/go-sdk/grpc-proto-exhaustive/dataservice.go +++ b/seed/go-sdk/grpc-proto-exhaustive/dataservice.go @@ -5,7 +5,7 @@ package api import ( json "encoding/json" fmt "fmt" - core "github.com/grpc-proto-exhaustive/fern/core" + internal "github.com/grpc-proto-exhaustive/fern/internal" ) type DeleteRequest struct { @@ -93,7 +93,7 @@ func (c *Column) UnmarshalJSON(data []byte) error { } *c = Column(value) - extraProperties, err := core.ExtractExtraProperties(data, *c) + extraProperties, err := internal.ExtractExtraProperties(data, *c) if err != nil { return err } @@ -105,11 +105,11 @@ func (c *Column) UnmarshalJSON(data []byte) error { func (c *Column) String() string { if len(c._rawJSON) > 0 { - if value, err := core.StringifyJSON(c._rawJSON); err == nil { + if value, err := internal.StringifyJSON(c._rawJSON); err == nil { return value } } - if value, err := core.StringifyJSON(c); err == nil { + if value, err := internal.StringifyJSON(c); err == nil { return value } return fmt.Sprintf("%#v", c) @@ -132,7 +132,7 @@ func (d *DeleteResponse) UnmarshalJSON(data []byte) error { } *d = DeleteResponse(value) - extraProperties, err := core.ExtractExtraProperties(data, *d) + extraProperties, err := internal.ExtractExtraProperties(data, *d) if err != nil { return err } @@ -144,11 +144,11 @@ func (d *DeleteResponse) UnmarshalJSON(data []byte) error { func (d *DeleteResponse) String() string { if len(d._rawJSON) > 0 { - if value, err := core.StringifyJSON(d._rawJSON); err == nil { + if value, err := internal.StringifyJSON(d._rawJSON); err == nil { return value } } - if value, err := core.StringifyJSON(d); err == nil { + if value, err := internal.StringifyJSON(d); err == nil { return value } return fmt.Sprintf("%#v", d) @@ -204,7 +204,7 @@ func (d *DescribeResponse) UnmarshalJSON(data []byte) error { } *d = DescribeResponse(value) - extraProperties, err := core.ExtractExtraProperties(data, *d) + extraProperties, err := internal.ExtractExtraProperties(data, *d) if err != nil { return err } @@ -216,11 +216,11 @@ func (d *DescribeResponse) UnmarshalJSON(data []byte) error { func (d *DescribeResponse) String() string { if len(d._rawJSON) > 0 { - if value, err := core.StringifyJSON(d._rawJSON); err == nil { + if value, err := internal.StringifyJSON(d._rawJSON); err == nil { return value } } - if value, err := core.StringifyJSON(d); err == nil { + if value, err := internal.StringifyJSON(d); err == nil { return value } return fmt.Sprintf("%#v", d) @@ -268,7 +268,7 @@ func (f *FetchResponse) UnmarshalJSON(data []byte) error { } *f = FetchResponse(value) - extraProperties, err := core.ExtractExtraProperties(data, *f) + extraProperties, err := internal.ExtractExtraProperties(data, *f) if err != nil { return err } @@ -280,11 +280,11 @@ func (f *FetchResponse) UnmarshalJSON(data []byte) error { func (f *FetchResponse) String() string { if len(f._rawJSON) > 0 { - if value, err := core.StringifyJSON(f._rawJSON); err == nil { + if value, err := internal.StringifyJSON(f._rawJSON); err == nil { return value } } - if value, err := core.StringifyJSON(f); err == nil { + if value, err := internal.StringifyJSON(f); err == nil { return value } return fmt.Sprintf("%#v", f) @@ -324,7 +324,7 @@ func (i *IndexedData) UnmarshalJSON(data []byte) error { } *i = IndexedData(value) - extraProperties, err := core.ExtractExtraProperties(data, *i) + extraProperties, err := internal.ExtractExtraProperties(data, *i) if err != nil { return err } @@ -336,11 +336,11 @@ func (i *IndexedData) UnmarshalJSON(data []byte) error { func (i *IndexedData) String() string { if len(i._rawJSON) > 0 { - if value, err := core.StringifyJSON(i._rawJSON); err == nil { + if value, err := internal.StringifyJSON(i._rawJSON); err == nil { return value } } - if value, err := core.StringifyJSON(i); err == nil { + if value, err := internal.StringifyJSON(i); err == nil { return value } return fmt.Sprintf("%#v", i) @@ -372,7 +372,7 @@ func (l *ListElement) UnmarshalJSON(data []byte) error { } *l = ListElement(value) - extraProperties, err := core.ExtractExtraProperties(data, *l) + extraProperties, err := internal.ExtractExtraProperties(data, *l) if err != nil { return err } @@ -384,11 +384,11 @@ func (l *ListElement) UnmarshalJSON(data []byte) error { func (l *ListElement) String() string { if len(l._rawJSON) > 0 { - if value, err := core.StringifyJSON(l._rawJSON); err == nil { + if value, err := internal.StringifyJSON(l._rawJSON); err == nil { return value } } - if value, err := core.StringifyJSON(l); err == nil { + if value, err := internal.StringifyJSON(l); err == nil { return value } return fmt.Sprintf("%#v", l) @@ -444,7 +444,7 @@ func (l *ListResponse) UnmarshalJSON(data []byte) error { } *l = ListResponse(value) - extraProperties, err := core.ExtractExtraProperties(data, *l) + extraProperties, err := internal.ExtractExtraProperties(data, *l) if err != nil { return err } @@ -456,11 +456,11 @@ func (l *ListResponse) UnmarshalJSON(data []byte) error { func (l *ListResponse) String() string { if len(l._rawJSON) > 0 { - if value, err := core.StringifyJSON(l._rawJSON); err == nil { + if value, err := internal.StringifyJSON(l._rawJSON); err == nil { return value } } - if value, err := core.StringifyJSON(l); err == nil { + if value, err := internal.StringifyJSON(l); err == nil { return value } return fmt.Sprintf("%#v", l) @@ -657,7 +657,7 @@ func (n *NamespaceSummary) UnmarshalJSON(data []byte) error { } *n = NamespaceSummary(value) - extraProperties, err := core.ExtractExtraProperties(data, *n) + extraProperties, err := internal.ExtractExtraProperties(data, *n) if err != nil { return err } @@ -669,11 +669,11 @@ func (n *NamespaceSummary) UnmarshalJSON(data []byte) error { func (n *NamespaceSummary) String() string { if len(n._rawJSON) > 0 { - if value, err := core.StringifyJSON(n._rawJSON); err == nil { + if value, err := internal.StringifyJSON(n._rawJSON); err == nil { return value } } - if value, err := core.StringifyJSON(n); err == nil { + if value, err := internal.StringifyJSON(n); err == nil { return value } return fmt.Sprintf("%#v", n) @@ -705,7 +705,7 @@ func (p *Pagination) UnmarshalJSON(data []byte) error { } *p = Pagination(value) - extraProperties, err := core.ExtractExtraProperties(data, *p) + extraProperties, err := internal.ExtractExtraProperties(data, *p) if err != nil { return err } @@ -717,11 +717,11 @@ func (p *Pagination) UnmarshalJSON(data []byte) error { func (p *Pagination) String() string { if len(p._rawJSON) > 0 { - if value, err := core.StringifyJSON(p._rawJSON); err == nil { + if value, err := internal.StringifyJSON(p._rawJSON); err == nil { return value } } - if value, err := core.StringifyJSON(p); err == nil { + if value, err := internal.StringifyJSON(p); err == nil { return value } return fmt.Sprintf("%#v", p) @@ -785,7 +785,7 @@ func (q *QueryColumn) UnmarshalJSON(data []byte) error { } *q = QueryColumn(value) - extraProperties, err := core.ExtractExtraProperties(data, *q) + extraProperties, err := internal.ExtractExtraProperties(data, *q) if err != nil { return err } @@ -797,11 +797,11 @@ func (q *QueryColumn) UnmarshalJSON(data []byte) error { func (q *QueryColumn) String() string { if len(q._rawJSON) > 0 { - if value, err := core.StringifyJSON(q._rawJSON); err == nil { + if value, err := internal.StringifyJSON(q._rawJSON); err == nil { return value } } - if value, err := core.StringifyJSON(q); err == nil { + if value, err := internal.StringifyJSON(q); err == nil { return value } return fmt.Sprintf("%#v", q) @@ -857,7 +857,7 @@ func (q *QueryResponse) UnmarshalJSON(data []byte) error { } *q = QueryResponse(value) - extraProperties, err := core.ExtractExtraProperties(data, *q) + extraProperties, err := internal.ExtractExtraProperties(data, *q) if err != nil { return err } @@ -869,11 +869,11 @@ func (q *QueryResponse) UnmarshalJSON(data []byte) error { func (q *QueryResponse) String() string { if len(q._rawJSON) > 0 { - if value, err := core.StringifyJSON(q._rawJSON); err == nil { + if value, err := internal.StringifyJSON(q._rawJSON); err == nil { return value } } - if value, err := core.StringifyJSON(q); err == nil { + if value, err := internal.StringifyJSON(q); err == nil { return value } return fmt.Sprintf("%#v", q) @@ -913,7 +913,7 @@ func (q *QueryResult) UnmarshalJSON(data []byte) error { } *q = QueryResult(value) - extraProperties, err := core.ExtractExtraProperties(data, *q) + extraProperties, err := internal.ExtractExtraProperties(data, *q) if err != nil { return err } @@ -925,11 +925,11 @@ func (q *QueryResult) UnmarshalJSON(data []byte) error { func (q *QueryResult) String() string { if len(q._rawJSON) > 0 { - if value, err := core.StringifyJSON(q._rawJSON); err == nil { + if value, err := internal.StringifyJSON(q._rawJSON); err == nil { return value } } - if value, err := core.StringifyJSON(q); err == nil { + if value, err := internal.StringifyJSON(q); err == nil { return value } return fmt.Sprintf("%#v", q) @@ -993,7 +993,7 @@ func (s *ScoredColumn) UnmarshalJSON(data []byte) error { } *s = ScoredColumn(value) - extraProperties, err := core.ExtractExtraProperties(data, *s) + extraProperties, err := internal.ExtractExtraProperties(data, *s) if err != nil { return err } @@ -1005,11 +1005,11 @@ func (s *ScoredColumn) UnmarshalJSON(data []byte) error { func (s *ScoredColumn) String() string { if len(s._rawJSON) > 0 { - if value, err := core.StringifyJSON(s._rawJSON); err == nil { + if value, err := internal.StringifyJSON(s._rawJSON); err == nil { return value } } - if value, err := core.StringifyJSON(s); err == nil { + if value, err := internal.StringifyJSON(s); err == nil { return value } return fmt.Sprintf("%#v", s) @@ -1032,7 +1032,7 @@ func (u *UpdateResponse) UnmarshalJSON(data []byte) error { } *u = UpdateResponse(value) - extraProperties, err := core.ExtractExtraProperties(data, *u) + extraProperties, err := internal.ExtractExtraProperties(data, *u) if err != nil { return err } @@ -1044,11 +1044,11 @@ func (u *UpdateResponse) UnmarshalJSON(data []byte) error { func (u *UpdateResponse) String() string { if len(u._rawJSON) > 0 { - if value, err := core.StringifyJSON(u._rawJSON); err == nil { + if value, err := internal.StringifyJSON(u._rawJSON); err == nil { return value } } - if value, err := core.StringifyJSON(u); err == nil { + if value, err := internal.StringifyJSON(u); err == nil { return value } return fmt.Sprintf("%#v", u) @@ -1080,7 +1080,7 @@ func (u *UploadResponse) UnmarshalJSON(data []byte) error { } *u = UploadResponse(value) - extraProperties, err := core.ExtractExtraProperties(data, *u) + extraProperties, err := internal.ExtractExtraProperties(data, *u) if err != nil { return err } @@ -1092,11 +1092,11 @@ func (u *UploadResponse) UnmarshalJSON(data []byte) error { func (u *UploadResponse) String() string { if len(u._rawJSON) > 0 { - if value, err := core.StringifyJSON(u._rawJSON); err == nil { + if value, err := internal.StringifyJSON(u._rawJSON); err == nil { return value } } - if value, err := core.StringifyJSON(u); err == nil { + if value, err := internal.StringifyJSON(u); err == nil { return value } return fmt.Sprintf("%#v", u) @@ -1128,7 +1128,7 @@ func (u *Usage) UnmarshalJSON(data []byte) error { } *u = Usage(value) - extraProperties, err := core.ExtractExtraProperties(data, *u) + extraProperties, err := internal.ExtractExtraProperties(data, *u) if err != nil { return err } @@ -1140,11 +1140,11 @@ func (u *Usage) UnmarshalJSON(data []byte) error { func (u *Usage) String() string { if len(u._rawJSON) > 0 { - if value, err := core.StringifyJSON(u._rawJSON); err == nil { + if value, err := internal.StringifyJSON(u._rawJSON); err == nil { return value } } - if value, err := core.StringifyJSON(u); err == nil { + if value, err := internal.StringifyJSON(u); err == nil { return value } return fmt.Sprintf("%#v", u) diff --git a/seed/go-sdk/grpc-proto-exhaustive/dataservice/client.go b/seed/go-sdk/grpc-proto-exhaustive/dataservice/client.go index 5c093d0701b..9bfe63485d0 100644 --- a/seed/go-sdk/grpc-proto-exhaustive/dataservice/client.go +++ b/seed/go-sdk/grpc-proto-exhaustive/dataservice/client.go @@ -6,13 +6,14 @@ import ( context "context" fern "github.com/grpc-proto-exhaustive/fern" core "github.com/grpc-proto-exhaustive/fern/core" + internal "github.com/grpc-proto-exhaustive/fern/internal" option "github.com/grpc-proto-exhaustive/fern/option" http "net/http" ) type Client struct { baseURL string - caller *core.Caller + caller *internal.Caller header http.Header } @@ -20,8 +21,8 @@ func NewClient(opts ...option.RequestOption) *Client { options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller( - &core.CallerParams{ + caller: internal.NewCaller( + &internal.CallerParams{ Client: options.HTTPClient, MaxAttempts: options.MaxAttempts, }, @@ -46,13 +47,13 @@ func (c *Client) Upload( } endpointURL := baseURL + "/data" - headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + headers := internal.MergeHeaders(c.header.Clone(), options.ToHeader()) headers.Set("Content-Type", "application/json") var response *fern.UploadResponse if err := c.caller.Call( ctx, - &core.CallParams{ + &internal.CallParams{ URL: endpointURL, Method: http.MethodPost, MaxAttempts: options.MaxAttempts, @@ -85,13 +86,13 @@ func (c *Client) Delete( } endpointURL := baseURL + "/data/delete" - headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + headers := internal.MergeHeaders(c.header.Clone(), options.ToHeader()) headers.Set("Content-Type", "application/json") var response *fern.DeleteResponse if err := c.caller.Call( ctx, - &core.CallParams{ + &internal.CallParams{ URL: endpointURL, Method: http.MethodPost, MaxAttempts: options.MaxAttempts, @@ -124,13 +125,13 @@ func (c *Client) Describe( } endpointURL := baseURL + "/data/describe" - headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + headers := internal.MergeHeaders(c.header.Clone(), options.ToHeader()) headers.Set("Content-Type", "application/json") var response *fern.DescribeResponse if err := c.caller.Call( ctx, - &core.CallParams{ + &internal.CallParams{ URL: endpointURL, Method: http.MethodPost, MaxAttempts: options.MaxAttempts, @@ -163,7 +164,7 @@ func (c *Client) Fetch( } endpointURL := baseURL + "/data/fetch" - queryParams, err := core.QueryValues(request) + queryParams, err := internal.QueryValues(request) if err != nil { return nil, err } @@ -171,12 +172,12 @@ func (c *Client) Fetch( endpointURL += "?" + queryParams.Encode() } - headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + headers := internal.MergeHeaders(c.header.Clone(), options.ToHeader()) var response *fern.FetchResponse if err := c.caller.Call( ctx, - &core.CallParams{ + &internal.CallParams{ URL: endpointURL, Method: http.MethodGet, MaxAttempts: options.MaxAttempts, @@ -208,7 +209,7 @@ func (c *Client) List( } endpointURL := baseURL + "/data/list" - queryParams, err := core.QueryValues(request) + queryParams, err := internal.QueryValues(request) if err != nil { return nil, err } @@ -216,12 +217,12 @@ func (c *Client) List( endpointURL += "?" + queryParams.Encode() } - headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + headers := internal.MergeHeaders(c.header.Clone(), options.ToHeader()) var response *fern.ListResponse if err := c.caller.Call( ctx, - &core.CallParams{ + &internal.CallParams{ URL: endpointURL, Method: http.MethodGet, MaxAttempts: options.MaxAttempts, @@ -253,13 +254,13 @@ func (c *Client) Query( } endpointURL := baseURL + "/data/query" - headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + headers := internal.MergeHeaders(c.header.Clone(), options.ToHeader()) headers.Set("Content-Type", "application/json") var response *fern.QueryResponse if err := c.caller.Call( ctx, - &core.CallParams{ + &internal.CallParams{ URL: endpointURL, Method: http.MethodPost, MaxAttempts: options.MaxAttempts, @@ -292,13 +293,13 @@ func (c *Client) Update( } endpointURL := baseURL + "/data/update" - headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + headers := internal.MergeHeaders(c.header.Clone(), options.ToHeader()) headers.Set("Content-Type", "application/json") var response *fern.UpdateResponse if err := c.caller.Call( ctx, - &core.CallParams{ + &internal.CallParams{ URL: endpointURL, Method: http.MethodPost, MaxAttempts: options.MaxAttempts, diff --git a/seed/go-sdk/grpc-proto-exhaustive/core/core.go b/seed/go-sdk/grpc-proto-exhaustive/internal/caller.go similarity index 70% rename from seed/go-sdk/grpc-proto-exhaustive/core/core.go rename to seed/go-sdk/grpc-proto-exhaustive/internal/caller.go index 6b5a8f3c011..8ebdc9cc493 100644 --- a/seed/go-sdk/grpc-proto-exhaustive/core/core.go +++ b/seed/go-sdk/grpc-proto-exhaustive/internal/caller.go @@ -1,4 +1,4 @@ -package core +package internal import ( "bytes" @@ -7,11 +7,12 @@ import ( "errors" "fmt" "io" - "mime/multipart" "net/http" "net/url" "reflect" "strings" + + "github.com/grpc-proto-exhaustive/fern/core" ) const ( @@ -20,105 +21,25 @@ const ( contentTypeHeader = "Content-Type" ) -// HTTPClient is an interface for a subset of the *http.Client. -type HTTPClient interface { - Do(*http.Request) (*http.Response, error) -} - -// EncodeURL encodes the given arguments into the URL, escaping -// values as needed. -func EncodeURL(urlFormat string, args ...interface{}) string { - escapedArgs := make([]interface{}, 0, len(args)) - for _, arg := range args { - escapedArgs = append(escapedArgs, url.PathEscape(fmt.Sprintf("%v", arg))) - } - return fmt.Sprintf(urlFormat, escapedArgs...) -} - -// MergeHeaders merges the given headers together, where the right -// takes precedence over the left. -func MergeHeaders(left, right http.Header) http.Header { - for key, values := range right { - if len(values) > 1 { - left[key] = values - continue - } - if value := right.Get(key); value != "" { - left.Set(key, value) - } - } - return left -} - -// WriteMultipartJSON writes the given value as a JSON part. -// This is used to serialize non-primitive multipart properties -// (i.e. lists, objects, etc). -func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { - bytes, err := json.Marshal(value) - if err != nil { - return err - } - return writer.WriteField(field, string(bytes)) -} - -// APIError is a lightweight wrapper around the standard error -// interface that preserves the status code from the RPC, if any. -type APIError struct { - err error - - StatusCode int `json:"-"` -} - -// NewAPIError constructs a new API error. -func NewAPIError(statusCode int, err error) *APIError { - return &APIError{ - err: err, - StatusCode: statusCode, - } -} - -// Unwrap returns the underlying error. This also makes the error compatible -// with errors.As and errors.Is. -func (a *APIError) Unwrap() error { - if a == nil { - return nil - } - return a.err -} - -// Error returns the API error's message. -func (a *APIError) Error() string { - if a == nil || (a.err == nil && a.StatusCode == 0) { - return "" - } - if a.err == nil { - return fmt.Sprintf("%d", a.StatusCode) - } - if a.StatusCode == 0 { - return a.err.Error() - } - return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) -} - // ErrorDecoder decodes *http.Response errors and returns a -// typed API error (e.g. *APIError). +// typed API error (e.g. *core.APIError). type ErrorDecoder func(statusCode int, body io.Reader) error // Caller calls APIs and deserializes their response, if any. type Caller struct { - client HTTPClient + client core.HTTPClient retrier *Retrier } // CallerParams represents the parameters used to constrcut a new *Caller. type CallerParams struct { - Client HTTPClient + Client core.HTTPClient MaxAttempts uint } // NewCaller returns a new *Caller backed by the given parameters. func NewCaller(params *CallerParams) *Caller { - var httpClient HTTPClient = http.DefaultClient + var httpClient core.HTTPClient = http.DefaultClient if params.Client != nil { httpClient = params.Client } @@ -140,7 +61,7 @@ type CallParams struct { Headers http.Header BodyProperties map[string]interface{} QueryParameters url.Values - Client HTTPClient + Client core.HTTPClient Request interface{} Response interface{} ResponseIsOptional bool @@ -309,9 +230,9 @@ func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { // The error didn't have a response body, // so all we can do is return an error // with the status code. - return NewAPIError(response.StatusCode, nil) + return core.NewAPIError(response.StatusCode, nil) } - return NewAPIError(response.StatusCode, errors.New(string(bytes))) + return core.NewAPIError(response.StatusCode, errors.New(string(bytes))) } // isNil is used to determine if the request value is equal to nil (i.e. an interface diff --git a/seed/go-sdk/grpc-proto/core/core_test.go b/seed/go-sdk/grpc-proto-exhaustive/internal/caller_test.go similarity index 97% rename from seed/go-sdk/grpc-proto/core/core_test.go rename to seed/go-sdk/grpc-proto-exhaustive/internal/caller_test.go index e6eaef3a86a..dccfa8d7c3a 100644 --- a/seed/go-sdk/grpc-proto/core/core_test.go +++ b/seed/go-sdk/grpc-proto-exhaustive/internal/caller_test.go @@ -1,4 +1,4 @@ -package core +package internal import ( "bytes" @@ -13,6 +13,7 @@ import ( "strconv" "testing" + "github.com/grpc-proto-exhaustive/fern/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -50,7 +51,7 @@ type Response struct { // NotFoundError represents a 404. type NotFoundError struct { - *APIError + *core.APIError Message string `json:"message"` } @@ -98,7 +99,7 @@ func TestCall(t *testing.T) { }, giveErrorDecoder: newTestErrorDecoder(t), wantError: &NotFoundError{ - APIError: NewAPIError( + APIError: core.NewAPIError( http.StatusNotFound, errors.New(`{"message":"ID \"404\" not found"}`), ), @@ -111,7 +112,7 @@ func TestCall(t *testing.T) { "X-API-Status": []string{"fail"}, }, giveRequest: nil, - wantError: NewAPIError( + wantError: core.NewAPIError( http.StatusBadRequest, errors.New("invalid request"), ), @@ -136,7 +137,7 @@ func TestCall(t *testing.T) { giveRequest: &Request{ Id: strconv.Itoa(http.StatusInternalServerError), }, - wantError: NewAPIError( + wantError: core.NewAPIError( http.StatusInternalServerError, errors.New("failed to process request"), ), @@ -324,7 +325,7 @@ func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { switch request.Id { case strconv.Itoa(http.StatusNotFound): notFoundError := &NotFoundError{ - APIError: &APIError{ + APIError: &core.APIError{ StatusCode: http.StatusNotFound, }, Message: fmt.Sprintf("ID %q not found", request.Id), @@ -375,7 +376,7 @@ func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { require.NoError(t, err) var ( - apiError = NewAPIError(statusCode, errors.New(string(raw))) + apiError = core.NewAPIError(statusCode, errors.New(string(raw))) decoder = json.NewDecoder(bytes.NewReader(raw)) ) if statusCode == http.StatusNotFound { diff --git a/seed/go-fiber/grpc-proto-exhaustive/core/extra_properties.go b/seed/go-sdk/grpc-proto-exhaustive/internal/extra_properties.go similarity index 99% rename from seed/go-fiber/grpc-proto-exhaustive/core/extra_properties.go rename to seed/go-sdk/grpc-proto-exhaustive/internal/extra_properties.go index a6af3e12410..540c3fd89ee 100644 --- a/seed/go-fiber/grpc-proto-exhaustive/core/extra_properties.go +++ b/seed/go-sdk/grpc-proto-exhaustive/internal/extra_properties.go @@ -1,4 +1,4 @@ -package core +package internal import ( "bytes" diff --git a/seed/go-sdk/grpc-proto/core/extra_properties_test.go b/seed/go-sdk/grpc-proto-exhaustive/internal/extra_properties_test.go similarity index 99% rename from seed/go-sdk/grpc-proto/core/extra_properties_test.go rename to seed/go-sdk/grpc-proto-exhaustive/internal/extra_properties_test.go index dc66fccd7f1..aa2510ee512 100644 --- a/seed/go-sdk/grpc-proto/core/extra_properties_test.go +++ b/seed/go-sdk/grpc-proto-exhaustive/internal/extra_properties_test.go @@ -1,4 +1,4 @@ -package core +package internal import ( "encoding/json" diff --git a/seed/go-sdk/grpc-proto-exhaustive/internal/http.go b/seed/go-sdk/grpc-proto-exhaustive/internal/http.go new file mode 100644 index 00000000000..2be0805a8be --- /dev/null +++ b/seed/go-sdk/grpc-proto-exhaustive/internal/http.go @@ -0,0 +1,37 @@ +package internal + +import ( + "fmt" + "net/http" + "net/url" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// EncodeURL encodes the given arguments into the URL, escaping +// values as needed. +func EncodeURL(urlFormat string, args ...interface{}) string { + escapedArgs := make([]interface{}, 0, len(args)) + for _, arg := range args { + escapedArgs = append(escapedArgs, url.PathEscape(fmt.Sprintf("%v", arg))) + } + return fmt.Sprintf(urlFormat, escapedArgs...) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} diff --git a/seed/go-sdk/grpc-proto/core/query.go b/seed/go-sdk/grpc-proto-exhaustive/internal/query.go similarity index 99% rename from seed/go-sdk/grpc-proto/core/query.go rename to seed/go-sdk/grpc-proto-exhaustive/internal/query.go index 2670ff7feda..6129e71ffe5 100644 --- a/seed/go-sdk/grpc-proto/core/query.go +++ b/seed/go-sdk/grpc-proto-exhaustive/internal/query.go @@ -1,4 +1,4 @@ -package core +package internal import ( "encoding/base64" diff --git a/seed/go-sdk/grpc-proto-exhaustive/core/query_test.go b/seed/go-sdk/grpc-proto-exhaustive/internal/query_test.go similarity index 99% rename from seed/go-sdk/grpc-proto-exhaustive/core/query_test.go rename to seed/go-sdk/grpc-proto-exhaustive/internal/query_test.go index 5498fa92aa5..2e58ccadde1 100644 --- a/seed/go-sdk/grpc-proto-exhaustive/core/query_test.go +++ b/seed/go-sdk/grpc-proto-exhaustive/internal/query_test.go @@ -1,4 +1,4 @@ -package core +package internal import ( "testing" diff --git a/seed/go-sdk/grpc-proto-exhaustive/core/retrier.go b/seed/go-sdk/grpc-proto-exhaustive/internal/retrier.go similarity index 98% rename from seed/go-sdk/grpc-proto-exhaustive/core/retrier.go rename to seed/go-sdk/grpc-proto-exhaustive/internal/retrier.go index ea24916b786..6040147154b 100644 --- a/seed/go-sdk/grpc-proto-exhaustive/core/retrier.go +++ b/seed/go-sdk/grpc-proto-exhaustive/internal/retrier.go @@ -1,4 +1,4 @@ -package core +package internal import ( "crypto/rand" @@ -130,7 +130,6 @@ func (r *Retrier) run( func (r *Retrier) shouldRetry(response *http.Response) bool { return response.StatusCode == http.StatusTooManyRequests || response.StatusCode == http.StatusRequestTimeout || - response.StatusCode == http.StatusConflict || response.StatusCode >= http.StatusInternalServerError } diff --git a/seed/go-sdk/grpc-proto/core/retrier_test.go b/seed/go-sdk/grpc-proto-exhaustive/internal/retrier_test.go similarity index 95% rename from seed/go-sdk/grpc-proto/core/retrier_test.go rename to seed/go-sdk/grpc-proto-exhaustive/internal/retrier_test.go index 7638274d738..1942442ddbf 100644 --- a/seed/go-sdk/grpc-proto/core/retrier_test.go +++ b/seed/go-sdk/grpc-proto-exhaustive/internal/retrier_test.go @@ -1,16 +1,15 @@ -package core +package internal import ( "context" "encoding/json" "io" - "net/http" "net/http/httptest" - "testing" "time" + "github.com/grpc-proto-exhaustive/fern/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -23,7 +22,7 @@ type RetryTestCase struct { giveResponse *Response wantResponse *Response - wantError *APIError + wantError *core.APIError } func TestRetrier(t *testing.T) { @@ -52,7 +51,7 @@ func TestRetrier(t *testing.T) { http.StatusRequestTimeout, http.StatusOK, }, - wantError: &APIError{ + wantError: &core.APIError{ StatusCode: http.StatusRequestTimeout, }, }, @@ -70,7 +69,7 @@ func TestRetrier(t *testing.T) { description: "retry does not occur on status code 404", giveAttempts: 2, giveStatusCodes: []int{http.StatusNotFound, http.StatusOK}, - wantError: &APIError{ + wantError: &core.APIError{ StatusCode: http.StatusNotFound, }, }, @@ -121,9 +120,9 @@ func TestRetrier(t *testing.T) { ) if test.wantError != nil { - require.IsType(t, err, &APIError{}) + require.IsType(t, err, &core.APIError{}) expectedErrorCode := test.wantError.StatusCode - actualErrorCode := err.(*APIError).StatusCode + actualErrorCode := err.(*core.APIError).StatusCode assert.Equal(t, expectedErrorCode, actualErrorCode) return } diff --git a/seed/go-fiber/grpc-proto-exhaustive/core/stringer.go b/seed/go-sdk/grpc-proto-exhaustive/internal/stringer.go similarity index 94% rename from seed/go-fiber/grpc-proto-exhaustive/core/stringer.go rename to seed/go-sdk/grpc-proto-exhaustive/internal/stringer.go index 000cf448641..312801851e0 100644 --- a/seed/go-fiber/grpc-proto-exhaustive/core/stringer.go +++ b/seed/go-sdk/grpc-proto-exhaustive/internal/stringer.go @@ -1,4 +1,4 @@ -package core +package internal import "encoding/json" diff --git a/seed/go-sdk/grpc-proto/core/time.go b/seed/go-sdk/grpc-proto-exhaustive/internal/time.go similarity index 99% rename from seed/go-sdk/grpc-proto/core/time.go rename to seed/go-sdk/grpc-proto-exhaustive/internal/time.go index d009ab30c90..ab0e269fade 100644 --- a/seed/go-sdk/grpc-proto/core/time.go +++ b/seed/go-sdk/grpc-proto-exhaustive/internal/time.go @@ -1,4 +1,4 @@ -package core +package internal import ( "encoding/json" diff --git a/seed/go-sdk/grpc-proto/client/client.go b/seed/go-sdk/grpc-proto/client/client.go index b879717f084..efa8f81707c 100644 --- a/seed/go-sdk/grpc-proto/client/client.go +++ b/seed/go-sdk/grpc-proto/client/client.go @@ -4,6 +4,7 @@ package client import ( core "github.com/grpc-proto/fern/core" + internal "github.com/grpc-proto/fern/internal" option "github.com/grpc-proto/fern/option" userservice "github.com/grpc-proto/fern/userservice" http "net/http" @@ -11,7 +12,7 @@ import ( type Client struct { baseURL string - caller *core.Caller + caller *internal.Caller header http.Header Userservice *userservice.Client @@ -21,8 +22,8 @@ func NewClient(opts ...option.RequestOption) *Client { options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller( - &core.CallerParams{ + caller: internal.NewCaller( + &internal.CallerParams{ Client: options.HTTPClient, MaxAttempts: options.MaxAttempts, }, diff --git a/seed/go-sdk/grpc-proto/core/api_error.go b/seed/go-sdk/grpc-proto/core/api_error.go new file mode 100644 index 00000000000..dc4190ca1cd --- /dev/null +++ b/seed/go-sdk/grpc-proto/core/api_error.go @@ -0,0 +1,42 @@ +package core + +import "fmt" + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} diff --git a/seed/go-sdk/grpc-proto/core/http.go b/seed/go-sdk/grpc-proto/core/http.go new file mode 100644 index 00000000000..b553350b84e --- /dev/null +++ b/seed/go-sdk/grpc-proto/core/http.go @@ -0,0 +1,8 @@ +package core + +import "net/http" + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} diff --git a/seed/go-sdk/grpc-proto/core/multipart.go b/seed/go-sdk/grpc-proto/core/multipart.go deleted file mode 100644 index 40f84bcec76..00000000000 --- a/seed/go-sdk/grpc-proto/core/multipart.go +++ /dev/null @@ -1,195 +0,0 @@ -package core - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "mime/multipart" - "net/textproto" - "strings" -) - -// Named is implemented by types that define a name. -type Named interface { - Name() string -} - -// ContentTyped is implemented by types that define a Content-Type. -type ContentTyped interface { - ContentType() string -} - -// WriteMultipartOption adapts the behavior of the multipart writer. -type WriteMultipartOption func(*writeMultipartOptions) - -// WithMultipartContentType sets the Content-Type for the multipart writer. -func WithMultipartContentType(contentType string) WriteMultipartOption { - return func(options *writeMultipartOptions) { - options.contentType = contentType - } -} - -// MultipartWriter writes multipart/form-data requests. -type MultipartWriter struct { - buffer *bytes.Buffer - writer *multipart.Writer -} - -// NewMultipartWriter creates a new multipart writer. -func NewMultipartWriter() *MultipartWriter { - buffer := bytes.NewBuffer(nil) - return &MultipartWriter{ - buffer: buffer, - writer: multipart.NewWriter(buffer), - } -} - -// Buffer returns the underlying buffer. -func (w *MultipartWriter) Buffer() *bytes.Buffer { - return w.buffer -} - -// ContentType returns the Content-Type for an HTTP multipart/form-data. -func (w *MultipartWriter) ContentType() string { - return w.writer.FormDataContentType() -} - -// WriteFile writes the given file part. -func (w *MultipartWriter) WriteFile( - field string, - file io.Reader, - opts ...WriteMultipartOption, -) error { - options := newWriteMultipartOptions(opts...) - return w.writeFile(field, file, options.contentType) -} - -// WriteField writes the given value as a form field. -func (w *MultipartWriter) WriteField( - field string, - value string, - opts ...WriteMultipartOption, -) error { - options := newWriteMultipartOptions(opts...) - return w.writeField(field, value, options.contentType) -} - -// WriteJSON writes the given value as a JSON form field. -func (w *MultipartWriter) WriteJSON( - field string, - value interface{}, - opts ...WriteMultipartOption, -) error { - bytes, err := json.Marshal(value) - if err != nil { - return err - } - return w.WriteField(field, string(bytes), opts...) -} - -// Close closes the writer. -func (w *MultipartWriter) Close() error { - return w.writer.Close() -} - -func (w *MultipartWriter) writeField( - field string, - value string, - contentType string, -) error { - part, err := w.newFormField(field, contentType) - if err != nil { - return err - } - _, err = part.Write([]byte(value)) - return err -} - -func (w *MultipartWriter) writeFile( - field string, - file io.Reader, - contentType string, -) error { - filename := getFilename(file) - if contentType == "" { - contentType = getContentType(file) - } - part, err := w.newFormPart(field, filename, contentType) - if err != nil { - return err - } - _, err = io.Copy(part, file) - return err -} - -// newFormField creates a new form field. -func (w *MultipartWriter) newFormField( - field string, - contentType string, -) (io.Writer, error) { - return w.newFormPart(field, "" /* filename */, contentType) -} - -// newFormPart creates a new form data part. -func (w *MultipartWriter) newFormPart( - field string, - filename string, - contentType string, -) (io.Writer, error) { - h := make(textproto.MIMEHeader) - h.Set("Content-Disposition", getContentDispositionHeaderValue(field, filename)) - if contentType != "" { - h.Set("Content-Type", contentType) - } - return w.writer.CreatePart(h) -} - -// writeMultipartOptions are options used to adapt the behavior of the multipart writer. -type writeMultipartOptions struct { - contentType string -} - -// newWriteMultipartOptions returns a new write multipart options. -func newWriteMultipartOptions(opts ...WriteMultipartOption) *writeMultipartOptions { - options := new(writeMultipartOptions) - for _, opt := range opts { - opt(options) - } - return options -} - -// getContentType returns the Content-Type for the given file, if any. -func getContentType(file io.Reader) string { - if v, ok := file.(ContentTyped); ok { - return v.ContentType() - } - return "" -} - -// getFilename returns the name for the given file, if any. -func getFilename(file io.Reader) string { - if v, ok := file.(Named); ok { - return v.Name() - } - return "" -} - -// getContentDispositionHeaderValue returns the value for the Content-Disposition header. -func getContentDispositionHeaderValue(field string, filename string) string { - contentDisposition := fmt.Sprintf("form-data; name=%q", field) - if filename != "" { - contentDisposition += fmt.Sprintf(`; filename=%q`, escapeQuotes(filename)) - } - return contentDisposition -} - -// https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/mime/multipart/writer.go;l=132 -var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") - -// escapeQuotes is directly referenced from the standard library. -// -// https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/mime/multipart/writer.go;l=134 -func escapeQuotes(s string) string { - return quoteEscaper.Replace(s) -} diff --git a/seed/go-sdk/grpc-proto/core/multipart_test.go b/seed/go-sdk/grpc-proto/core/multipart_test.go deleted file mode 100644 index ba73d413f0c..00000000000 --- a/seed/go-sdk/grpc-proto/core/multipart_test.go +++ /dev/null @@ -1,251 +0,0 @@ -package core - -import ( - "encoding/json" - "io" - "mime/multipart" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const maxFormMemory = 32 << 20 // 32MB - -type mockFile struct { - name string - content string - contentType string - - reader io.Reader -} - -func (f *mockFile) Read(p []byte) (n int, err error) { - if f.reader == nil { - f.reader = strings.NewReader(f.content) - } - return f.reader.Read(p) -} - -func (f *mockFile) Name() string { - return f.name -} - -func (f *mockFile) ContentType() string { - return f.contentType -} - -func TestMultipartWriter(t *testing.T) { - t.Run("empty", func(t *testing.T) { - w := NewMultipartWriter() - assert.NotNil(t, w.Buffer()) - assert.Contains(t, w.ContentType(), "multipart/form-data; boundary=") - require.NoError(t, w.Close()) - }) - - t.Run("write field", func(t *testing.T) { - tests := []struct { - desc string - giveField string - giveValue string - giveContentType string - }{ - { - desc: "empty field", - giveField: "empty", - giveValue: "", - }, - { - desc: "simple field", - giveField: "greeting", - giveValue: "hello world", - }, - { - desc: "field with content type", - giveField: "message", - giveValue: "hello", - giveContentType: "text/plain", - }, - } - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - w := NewMultipartWriter() - - var opts []WriteMultipartOption - if tt.giveContentType != "" { - opts = append(opts, WithMultipartContentType(tt.giveContentType)) - } - - require.NoError(t, w.WriteField(tt.giveField, tt.giveValue, opts...)) - require.NoError(t, w.Close()) - - reader := multipart.NewReader(w.Buffer(), w.writer.Boundary()) - form, err := reader.ReadForm(maxFormMemory) - require.NoError(t, err) - - assert.Equal(t, []string{tt.giveValue}, form.Value[tt.giveField]) - require.NoError(t, form.RemoveAll()) - }) - } - }) - - t.Run("write file", func(t *testing.T) { - tests := []struct { - desc string - giveField string - giveFile *mockFile - giveContentType string - }{ - { - desc: "simple file", - giveField: "file", - giveFile: &mockFile{ - name: "test.txt", - content: "hello world", - contentType: "text/plain", - }, - }, - { - desc: "override content type", - giveField: "file", - giveFile: &mockFile{ - name: "test.txt", - content: "hello world", - contentType: "text/plain", - }, - giveContentType: "application/octet-stream", - }, - } - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - w := NewMultipartWriter() - - var opts []WriteMultipartOption - if tt.giveContentType != "" { - opts = append(opts, WithMultipartContentType(tt.giveContentType)) - } - - require.NoError(t, w.WriteFile(tt.giveField, tt.giveFile, opts...)) - require.NoError(t, w.Close()) - - reader := multipart.NewReader(w.Buffer(), w.writer.Boundary()) - form, err := reader.ReadForm(maxFormMemory) - require.NoError(t, err) - defer func() { - require.NoError(t, form.RemoveAll()) - }() - - files := form.File[tt.giveField] - require.Len(t, files, 1) - - file := files[0] - assert.Equal(t, tt.giveFile.name, file.Filename) - - f, err := file.Open() - require.NoError(t, err) - defer func() { - require.NoError(t, f.Close()) - }() - - content, err := io.ReadAll(f) - require.NoError(t, err) - assert.Equal(t, tt.giveFile.content, string(content)) - - expectedContentType := tt.giveContentType - if expectedContentType == "" { - expectedContentType = tt.giveFile.contentType - } - if expectedContentType != "" { - assert.Equal(t, expectedContentType, file.Header.Get("Content-Type")) - } - }) - } - }) - - t.Run("write JSON", func(t *testing.T) { - type testStruct struct { - Name string `json:"name"` - Value int `json:"value"` - } - - tests := []struct { - desc string - giveField string - giveValue interface{} - }{ - { - desc: "struct", - giveField: "data", - giveValue: testStruct{Name: "test", Value: 123}, - }, - { - desc: "map", - giveField: "data", - giveValue: map[string]string{"key": "value"}, - }, - } - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - w := NewMultipartWriter() - - require.NoError(t, w.WriteJSON(tt.giveField, tt.giveValue)) - require.NoError(t, w.Close()) - - reader := multipart.NewReader(w.Buffer(), w.writer.Boundary()) - form, err := reader.ReadForm(maxFormMemory) - require.NoError(t, err) - defer func() { - require.NoError(t, form.RemoveAll()) - }() - - expected, err := json.Marshal(tt.giveValue) - require.NoError(t, err) - assert.Equal(t, []string{string(expected)}, form.Value[tt.giveField]) - }) - } - }) - - t.Run("complex", func(t *testing.T) { - w := NewMultipartWriter() - - // Add multiple fields and files - require.NoError(t, w.WriteField("foo", "bar")) - require.NoError(t, w.WriteField("baz", "qux")) - - hello := mockFile{name: "file.txt", content: "Hello, world!", contentType: "text/plain"} - require.NoError(t, w.WriteFile("file", &hello)) - require.NoError(t, w.WriteJSON("data", map[string]string{"key": "value"})) - require.NoError(t, w.Close()) - - reader := multipart.NewReader(w.Buffer(), w.writer.Boundary()) - form, err := reader.ReadForm(maxFormMemory) - require.NoError(t, err) - defer func() { - require.NoError(t, form.RemoveAll()) - }() - - assert.Equal(t, []string{"bar"}, form.Value["foo"]) - assert.Equal(t, []string{"qux"}, form.Value["baz"]) - assert.Equal(t, []string{`{"key":"value"}`}, form.Value["data"]) - - files := form.File["file"] - require.Len(t, files, 1) - - file := files[0] - assert.Equal(t, "file.txt", file.Filename) - - f, err := file.Open() - require.NoError(t, err) - defer func() { - require.NoError(t, f.Close()) - }() - - content, err := io.ReadAll(f) - require.NoError(t, err) - assert.Equal(t, "Hello, world!", string(content)) - }) -} diff --git a/seed/go-sdk/grpc-proto/core/core.go b/seed/go-sdk/grpc-proto/internal/caller.go similarity index 70% rename from seed/go-sdk/grpc-proto/core/core.go rename to seed/go-sdk/grpc-proto/internal/caller.go index 6b5a8f3c011..cfeebc21ef3 100644 --- a/seed/go-sdk/grpc-proto/core/core.go +++ b/seed/go-sdk/grpc-proto/internal/caller.go @@ -1,4 +1,4 @@ -package core +package internal import ( "bytes" @@ -7,11 +7,12 @@ import ( "errors" "fmt" "io" - "mime/multipart" "net/http" "net/url" "reflect" "strings" + + "github.com/grpc-proto/fern/core" ) const ( @@ -20,105 +21,25 @@ const ( contentTypeHeader = "Content-Type" ) -// HTTPClient is an interface for a subset of the *http.Client. -type HTTPClient interface { - Do(*http.Request) (*http.Response, error) -} - -// EncodeURL encodes the given arguments into the URL, escaping -// values as needed. -func EncodeURL(urlFormat string, args ...interface{}) string { - escapedArgs := make([]interface{}, 0, len(args)) - for _, arg := range args { - escapedArgs = append(escapedArgs, url.PathEscape(fmt.Sprintf("%v", arg))) - } - return fmt.Sprintf(urlFormat, escapedArgs...) -} - -// MergeHeaders merges the given headers together, where the right -// takes precedence over the left. -func MergeHeaders(left, right http.Header) http.Header { - for key, values := range right { - if len(values) > 1 { - left[key] = values - continue - } - if value := right.Get(key); value != "" { - left.Set(key, value) - } - } - return left -} - -// WriteMultipartJSON writes the given value as a JSON part. -// This is used to serialize non-primitive multipart properties -// (i.e. lists, objects, etc). -func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { - bytes, err := json.Marshal(value) - if err != nil { - return err - } - return writer.WriteField(field, string(bytes)) -} - -// APIError is a lightweight wrapper around the standard error -// interface that preserves the status code from the RPC, if any. -type APIError struct { - err error - - StatusCode int `json:"-"` -} - -// NewAPIError constructs a new API error. -func NewAPIError(statusCode int, err error) *APIError { - return &APIError{ - err: err, - StatusCode: statusCode, - } -} - -// Unwrap returns the underlying error. This also makes the error compatible -// with errors.As and errors.Is. -func (a *APIError) Unwrap() error { - if a == nil { - return nil - } - return a.err -} - -// Error returns the API error's message. -func (a *APIError) Error() string { - if a == nil || (a.err == nil && a.StatusCode == 0) { - return "" - } - if a.err == nil { - return fmt.Sprintf("%d", a.StatusCode) - } - if a.StatusCode == 0 { - return a.err.Error() - } - return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) -} - // ErrorDecoder decodes *http.Response errors and returns a -// typed API error (e.g. *APIError). +// typed API error (e.g. *core.APIError). type ErrorDecoder func(statusCode int, body io.Reader) error // Caller calls APIs and deserializes their response, if any. type Caller struct { - client HTTPClient + client core.HTTPClient retrier *Retrier } // CallerParams represents the parameters used to constrcut a new *Caller. type CallerParams struct { - Client HTTPClient + Client core.HTTPClient MaxAttempts uint } // NewCaller returns a new *Caller backed by the given parameters. func NewCaller(params *CallerParams) *Caller { - var httpClient HTTPClient = http.DefaultClient + var httpClient core.HTTPClient = http.DefaultClient if params.Client != nil { httpClient = params.Client } @@ -140,7 +61,7 @@ type CallParams struct { Headers http.Header BodyProperties map[string]interface{} QueryParameters url.Values - Client HTTPClient + Client core.HTTPClient Request interface{} Response interface{} ResponseIsOptional bool @@ -309,9 +230,9 @@ func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { // The error didn't have a response body, // so all we can do is return an error // with the status code. - return NewAPIError(response.StatusCode, nil) + return core.NewAPIError(response.StatusCode, nil) } - return NewAPIError(response.StatusCode, errors.New(string(bytes))) + return core.NewAPIError(response.StatusCode, errors.New(string(bytes))) } // isNil is used to determine if the request value is equal to nil (i.e. an interface diff --git a/seed/go-sdk/grpc-proto-exhaustive/core/core_test.go b/seed/go-sdk/grpc-proto/internal/caller_test.go similarity index 97% rename from seed/go-sdk/grpc-proto-exhaustive/core/core_test.go rename to seed/go-sdk/grpc-proto/internal/caller_test.go index e6eaef3a86a..93b682f32b0 100644 --- a/seed/go-sdk/grpc-proto-exhaustive/core/core_test.go +++ b/seed/go-sdk/grpc-proto/internal/caller_test.go @@ -1,4 +1,4 @@ -package core +package internal import ( "bytes" @@ -13,6 +13,7 @@ import ( "strconv" "testing" + "github.com/grpc-proto/fern/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -50,7 +51,7 @@ type Response struct { // NotFoundError represents a 404. type NotFoundError struct { - *APIError + *core.APIError Message string `json:"message"` } @@ -98,7 +99,7 @@ func TestCall(t *testing.T) { }, giveErrorDecoder: newTestErrorDecoder(t), wantError: &NotFoundError{ - APIError: NewAPIError( + APIError: core.NewAPIError( http.StatusNotFound, errors.New(`{"message":"ID \"404\" not found"}`), ), @@ -111,7 +112,7 @@ func TestCall(t *testing.T) { "X-API-Status": []string{"fail"}, }, giveRequest: nil, - wantError: NewAPIError( + wantError: core.NewAPIError( http.StatusBadRequest, errors.New("invalid request"), ), @@ -136,7 +137,7 @@ func TestCall(t *testing.T) { giveRequest: &Request{ Id: strconv.Itoa(http.StatusInternalServerError), }, - wantError: NewAPIError( + wantError: core.NewAPIError( http.StatusInternalServerError, errors.New("failed to process request"), ), @@ -324,7 +325,7 @@ func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { switch request.Id { case strconv.Itoa(http.StatusNotFound): notFoundError := &NotFoundError{ - APIError: &APIError{ + APIError: &core.APIError{ StatusCode: http.StatusNotFound, }, Message: fmt.Sprintf("ID %q not found", request.Id), @@ -375,7 +376,7 @@ func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { require.NoError(t, err) var ( - apiError = NewAPIError(statusCode, errors.New(string(raw))) + apiError = core.NewAPIError(statusCode, errors.New(string(raw))) decoder = json.NewDecoder(bytes.NewReader(raw)) ) if statusCode == http.StatusNotFound { diff --git a/seed/go-sdk/grpc-proto/core/extra_properties.go b/seed/go-sdk/grpc-proto/internal/extra_properties.go similarity index 99% rename from seed/go-sdk/grpc-proto/core/extra_properties.go rename to seed/go-sdk/grpc-proto/internal/extra_properties.go index a6af3e12410..540c3fd89ee 100644 --- a/seed/go-sdk/grpc-proto/core/extra_properties.go +++ b/seed/go-sdk/grpc-proto/internal/extra_properties.go @@ -1,4 +1,4 @@ -package core +package internal import ( "bytes" diff --git a/seed/go-fiber/grpc-proto-exhaustive/core/extra_properties_test.go b/seed/go-sdk/grpc-proto/internal/extra_properties_test.go similarity index 99% rename from seed/go-fiber/grpc-proto-exhaustive/core/extra_properties_test.go rename to seed/go-sdk/grpc-proto/internal/extra_properties_test.go index dc66fccd7f1..aa2510ee512 100644 --- a/seed/go-fiber/grpc-proto-exhaustive/core/extra_properties_test.go +++ b/seed/go-sdk/grpc-proto/internal/extra_properties_test.go @@ -1,4 +1,4 @@ -package core +package internal import ( "encoding/json" diff --git a/seed/go-sdk/grpc-proto/internal/http.go b/seed/go-sdk/grpc-proto/internal/http.go new file mode 100644 index 00000000000..2be0805a8be --- /dev/null +++ b/seed/go-sdk/grpc-proto/internal/http.go @@ -0,0 +1,37 @@ +package internal + +import ( + "fmt" + "net/http" + "net/url" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// EncodeURL encodes the given arguments into the URL, escaping +// values as needed. +func EncodeURL(urlFormat string, args ...interface{}) string { + escapedArgs := make([]interface{}, 0, len(args)) + for _, arg := range args { + escapedArgs = append(escapedArgs, url.PathEscape(fmt.Sprintf("%v", arg))) + } + return fmt.Sprintf(urlFormat, escapedArgs...) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} diff --git a/seed/go-sdk/grpc-proto-exhaustive/core/query.go b/seed/go-sdk/grpc-proto/internal/query.go similarity index 99% rename from seed/go-sdk/grpc-proto-exhaustive/core/query.go rename to seed/go-sdk/grpc-proto/internal/query.go index 2670ff7feda..6129e71ffe5 100644 --- a/seed/go-sdk/grpc-proto-exhaustive/core/query.go +++ b/seed/go-sdk/grpc-proto/internal/query.go @@ -1,4 +1,4 @@ -package core +package internal import ( "encoding/base64" diff --git a/seed/go-sdk/grpc-proto/core/query_test.go b/seed/go-sdk/grpc-proto/internal/query_test.go similarity index 99% rename from seed/go-sdk/grpc-proto/core/query_test.go rename to seed/go-sdk/grpc-proto/internal/query_test.go index 5498fa92aa5..2e58ccadde1 100644 --- a/seed/go-sdk/grpc-proto/core/query_test.go +++ b/seed/go-sdk/grpc-proto/internal/query_test.go @@ -1,4 +1,4 @@ -package core +package internal import ( "testing" diff --git a/seed/go-sdk/grpc-proto/core/retrier.go b/seed/go-sdk/grpc-proto/internal/retrier.go similarity index 98% rename from seed/go-sdk/grpc-proto/core/retrier.go rename to seed/go-sdk/grpc-proto/internal/retrier.go index ea24916b786..6040147154b 100644 --- a/seed/go-sdk/grpc-proto/core/retrier.go +++ b/seed/go-sdk/grpc-proto/internal/retrier.go @@ -1,4 +1,4 @@ -package core +package internal import ( "crypto/rand" @@ -130,7 +130,6 @@ func (r *Retrier) run( func (r *Retrier) shouldRetry(response *http.Response) bool { return response.StatusCode == http.StatusTooManyRequests || response.StatusCode == http.StatusRequestTimeout || - response.StatusCode == http.StatusConflict || response.StatusCode >= http.StatusInternalServerError } diff --git a/seed/go-sdk/grpc-proto-exhaustive/core/retrier_test.go b/seed/go-sdk/grpc-proto/internal/retrier_test.go similarity index 95% rename from seed/go-sdk/grpc-proto-exhaustive/core/retrier_test.go rename to seed/go-sdk/grpc-proto/internal/retrier_test.go index 7638274d738..35a67096caa 100644 --- a/seed/go-sdk/grpc-proto-exhaustive/core/retrier_test.go +++ b/seed/go-sdk/grpc-proto/internal/retrier_test.go @@ -1,16 +1,15 @@ -package core +package internal import ( "context" "encoding/json" "io" - "net/http" "net/http/httptest" - "testing" "time" + "github.com/grpc-proto/fern/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -23,7 +22,7 @@ type RetryTestCase struct { giveResponse *Response wantResponse *Response - wantError *APIError + wantError *core.APIError } func TestRetrier(t *testing.T) { @@ -52,7 +51,7 @@ func TestRetrier(t *testing.T) { http.StatusRequestTimeout, http.StatusOK, }, - wantError: &APIError{ + wantError: &core.APIError{ StatusCode: http.StatusRequestTimeout, }, }, @@ -70,7 +69,7 @@ func TestRetrier(t *testing.T) { description: "retry does not occur on status code 404", giveAttempts: 2, giveStatusCodes: []int{http.StatusNotFound, http.StatusOK}, - wantError: &APIError{ + wantError: &core.APIError{ StatusCode: http.StatusNotFound, }, }, @@ -121,9 +120,9 @@ func TestRetrier(t *testing.T) { ) if test.wantError != nil { - require.IsType(t, err, &APIError{}) + require.IsType(t, err, &core.APIError{}) expectedErrorCode := test.wantError.StatusCode - actualErrorCode := err.(*APIError).StatusCode + actualErrorCode := err.(*core.APIError).StatusCode assert.Equal(t, expectedErrorCode, actualErrorCode) return } diff --git a/seed/go-fiber/grpc-proto/core/stringer.go b/seed/go-sdk/grpc-proto/internal/stringer.go similarity index 94% rename from seed/go-fiber/grpc-proto/core/stringer.go rename to seed/go-sdk/grpc-proto/internal/stringer.go index 000cf448641..312801851e0 100644 --- a/seed/go-fiber/grpc-proto/core/stringer.go +++ b/seed/go-sdk/grpc-proto/internal/stringer.go @@ -1,4 +1,4 @@ -package core +package internal import "encoding/json" diff --git a/seed/go-fiber/grpc-proto/core/time.go b/seed/go-sdk/grpc-proto/internal/time.go similarity index 99% rename from seed/go-fiber/grpc-proto/core/time.go rename to seed/go-sdk/grpc-proto/internal/time.go index d009ab30c90..ab0e269fade 100644 --- a/seed/go-fiber/grpc-proto/core/time.go +++ b/seed/go-sdk/grpc-proto/internal/time.go @@ -1,4 +1,4 @@ -package core +package internal import ( "encoding/json" diff --git a/seed/go-sdk/grpc-proto/userservice.go b/seed/go-sdk/grpc-proto/userservice.go index cbadca02982..fb39ffb45e7 100644 --- a/seed/go-sdk/grpc-proto/userservice.go +++ b/seed/go-sdk/grpc-proto/userservice.go @@ -5,7 +5,7 @@ package api import ( json "encoding/json" fmt "fmt" - core "github.com/grpc-proto/fern/core" + internal "github.com/grpc-proto/fern/internal" ) type CreateRequest struct { @@ -42,7 +42,7 @@ func (c *CreateResponse) UnmarshalJSON(data []byte) error { } *c = CreateResponse(value) - extraProperties, err := core.ExtractExtraProperties(data, *c) + extraProperties, err := internal.ExtractExtraProperties(data, *c) if err != nil { return err } @@ -54,11 +54,11 @@ func (c *CreateResponse) UnmarshalJSON(data []byte) error { func (c *CreateResponse) String() string { if len(c._rawJSON) > 0 { - if value, err := core.StringifyJSON(c._rawJSON); err == nil { + if value, err := internal.StringifyJSON(c._rawJSON); err == nil { return value } } - if value, err := core.StringifyJSON(c); err == nil { + if value, err := internal.StringifyJSON(c); err == nil { return value } return fmt.Sprintf("%#v", c) @@ -287,7 +287,7 @@ func (u *UserModel) UnmarshalJSON(data []byte) error { } *u = UserModel(value) - extraProperties, err := core.ExtractExtraProperties(data, *u) + extraProperties, err := internal.ExtractExtraProperties(data, *u) if err != nil { return err } @@ -299,11 +299,11 @@ func (u *UserModel) UnmarshalJSON(data []byte) error { func (u *UserModel) String() string { if len(u._rawJSON) > 0 { - if value, err := core.StringifyJSON(u._rawJSON); err == nil { + if value, err := internal.StringifyJSON(u._rawJSON); err == nil { return value } } - if value, err := core.StringifyJSON(u); err == nil { + if value, err := internal.StringifyJSON(u); err == nil { return value } return fmt.Sprintf("%#v", u) diff --git a/seed/go-sdk/grpc-proto/userservice/client.go b/seed/go-sdk/grpc-proto/userservice/client.go index 9805230e831..634cae165e2 100644 --- a/seed/go-sdk/grpc-proto/userservice/client.go +++ b/seed/go-sdk/grpc-proto/userservice/client.go @@ -6,13 +6,14 @@ import ( context "context" fern "github.com/grpc-proto/fern" core "github.com/grpc-proto/fern/core" + internal "github.com/grpc-proto/fern/internal" option "github.com/grpc-proto/fern/option" http "net/http" ) type Client struct { baseURL string - caller *core.Caller + caller *internal.Caller header http.Header } @@ -20,8 +21,8 @@ func NewClient(opts ...option.RequestOption) *Client { options := core.NewRequestOptions(opts...) return &Client{ baseURL: options.BaseURL, - caller: core.NewCaller( - &core.CallerParams{ + caller: internal.NewCaller( + &internal.CallerParams{ Client: options.HTTPClient, MaxAttempts: options.MaxAttempts, }, @@ -46,13 +47,13 @@ func (c *Client) Create( } endpointURL := baseURL + "/users" - headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + headers := internal.MergeHeaders(c.header.Clone(), options.ToHeader()) headers.Set("Content-Type", "application/json") var response *fern.CreateResponse if err := c.caller.Call( ctx, - &core.CallParams{ + &internal.CallParams{ URL: endpointURL, Method: http.MethodPost, MaxAttempts: options.MaxAttempts, diff --git a/seed/java-sdk/file-upload/.mock/definition/service.yml b/seed/java-sdk/file-upload/.mock/definition/service.yml index 5ae95021c2d..956e6ba73ba 100644 --- a/seed/java-sdk/file-upload/.mock/definition/service.yml +++ b/seed/java-sdk/file-upload/.mock/definition/service.yml @@ -64,6 +64,9 @@ service: bar: type: MyObject content-type: application/json + foobar: + type: optional + content-type: application/json types: Id: string diff --git a/seed/java-sdk/file-upload/src/main/java/com/seed/fileUpload/resources/service/ServiceClient.java b/seed/java-sdk/file-upload/src/main/java/com/seed/fileUpload/resources/service/ServiceClient.java index e59eb7b29e0..27503ce10cc 100644 --- a/seed/java-sdk/file-upload/src/main/java/com/seed/fileUpload/resources/service/ServiceClient.java +++ b/seed/java-sdk/file-upload/src/main/java/com/seed/fileUpload/resources/service/ServiceClient.java @@ -245,6 +245,9 @@ public void withContentType(File file, WithContentTypeRequest request, RequestOp body.addFormDataPart("file", file.getName(), RequestBody.create(fileMimeTypeMediaType, file)); body.addFormDataPart("foo", ObjectMappers.JSON_MAPPER.writeValueAsString(request.getFoo())); body.addFormDataPart("bar", ObjectMappers.JSON_MAPPER.writeValueAsString(request.getBar())); + if (request.getFoobar().isPresent()) { + body.addFormDataPart("foobar", ObjectMappers.JSON_MAPPER.writeValueAsString(request.getFoobar())); + } } catch (Exception e) { throw new RuntimeException(e); } diff --git a/seed/java-sdk/file-upload/src/main/java/com/seed/fileUpload/resources/service/requests/WithContentTypeRequest.java b/seed/java-sdk/file-upload/src/main/java/com/seed/fileUpload/resources/service/requests/WithContentTypeRequest.java index 898761d410b..fdfe156e6a5 100644 --- a/seed/java-sdk/file-upload/src/main/java/com/seed/fileUpload/resources/service/requests/WithContentTypeRequest.java +++ b/seed/java-sdk/file-upload/src/main/java/com/seed/fileUpload/resources/service/requests/WithContentTypeRequest.java @@ -9,12 +9,14 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.seed.fileUpload.core.ObjectMappers; import com.seed.fileUpload.resources.service.types.MyObject; import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.Optional; import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @@ -24,11 +26,15 @@ public final class WithContentTypeRequest { private final MyObject bar; + private final Optional foobar; + private final Map additionalProperties; - private WithContentTypeRequest(String foo, MyObject bar, Map additionalProperties) { + private WithContentTypeRequest( + String foo, MyObject bar, Optional foobar, Map additionalProperties) { this.foo = foo; this.bar = bar; + this.foobar = foobar; this.additionalProperties = additionalProperties; } @@ -42,6 +48,11 @@ public MyObject getBar() { return bar; } + @JsonProperty("foobar") + public Optional getFoobar() { + return foobar; + } + @java.lang.Override public boolean equals(Object other) { if (this == other) return true; @@ -54,12 +65,12 @@ public Map getAdditionalProperties() { } private boolean equalTo(WithContentTypeRequest other) { - return foo.equals(other.foo) && bar.equals(other.bar); + return foo.equals(other.foo) && bar.equals(other.bar) && foobar.equals(other.foobar); } @java.lang.Override public int hashCode() { - return Objects.hash(this.foo, this.bar); + return Objects.hash(this.foo, this.bar, this.foobar); } @java.lang.Override @@ -83,6 +94,10 @@ public interface BarStage { public interface _FinalStage { WithContentTypeRequest build(); + + _FinalStage foobar(Optional foobar); + + _FinalStage foobar(MyObject foobar); } @JsonIgnoreProperties(ignoreUnknown = true) @@ -91,6 +106,8 @@ public static final class Builder implements FooStage, BarStage, _FinalStage { private MyObject bar; + private Optional foobar = Optional.empty(); + @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -100,6 +117,7 @@ private Builder() {} public Builder from(WithContentTypeRequest other) { foo(other.getFoo()); bar(other.getBar()); + foobar(other.getFoobar()); return this; } @@ -117,9 +135,22 @@ public _FinalStage bar(@NotNull MyObject bar) { return this; } + @java.lang.Override + public _FinalStage foobar(MyObject foobar) { + this.foobar = Optional.ofNullable(foobar); + return this; + } + + @java.lang.Override + @JsonSetter(value = "foobar", nulls = Nulls.SKIP) + public _FinalStage foobar(Optional foobar) { + this.foobar = foobar; + return this; + } + @java.lang.Override public WithContentTypeRequest build() { - return new WithContentTypeRequest(foo, bar, additionalProperties); + return new WithContentTypeRequest(foo, bar, foobar, additionalProperties); } } } diff --git a/seed/openapi/exhaustive/.mock/definition/endpoints/content-type.yml b/seed/openapi/exhaustive/.mock/definition/endpoints/content-type.yml new file mode 100644 index 00000000000..7c54e39fa5a --- /dev/null +++ b/seed/openapi/exhaustive/.mock/definition/endpoints/content-type.yml @@ -0,0 +1,19 @@ +imports: + objects: ../types/object.yml + +service: + auth: true + base-path: /foo + endpoints: + postJsonPatchContentType: + path: /bar + method: POST + request: + body: objects.ObjectWithOptionalField + content-type: application/json-patch+json + postJsonPatchContentWithCharsetType: + path: /baz + method: POST + request: + body: objects.ObjectWithOptionalField + content-type: application/json-patch+json; charset=utf-8 diff --git a/seed/openapi/file-upload/.mock/definition/service.yml b/seed/openapi/file-upload/.mock/definition/service.yml index 5ae95021c2d..956e6ba73ba 100644 --- a/seed/openapi/file-upload/.mock/definition/service.yml +++ b/seed/openapi/file-upload/.mock/definition/service.yml @@ -64,6 +64,9 @@ service: bar: type: MyObject content-type: application/json + foobar: + type: optional + content-type: application/json types: Id: string diff --git a/seed/openapi/file-upload/openapi.yml b/seed/openapi/file-upload/openapi.yml index 0c61bfe4186..19a941c34e0 100644 --- a/seed/openapi/file-upload/openapi.yml +++ b/seed/openapi/file-upload/openapi.yml @@ -149,6 +149,9 @@ paths: type: string bar: $ref: '#/components/schemas/MyObject' + foobar: + $ref: '#/components/schemas/MyObject' + nullable: true components: schemas: Id: diff --git a/seed/openapi/license/.mock/definition/__package__.yml b/seed/openapi/license/.mock/definition/__package__.yml new file mode 100644 index 00000000000..b1e4d4a878f --- /dev/null +++ b/seed/openapi/license/.mock/definition/__package__.yml @@ -0,0 +1,13 @@ +types: + Type: + docs: A simple type with just a name. + properties: + name: string + +service: + auth: false + base-path: / + endpoints: + get: + path: "/" + method: GET diff --git a/seed/openapi/license/.mock/definition/api.yml b/seed/openapi/license/.mock/definition/api.yml new file mode 100644 index 00000000000..5523ff1f181 --- /dev/null +++ b/seed/openapi/license/.mock/definition/api.yml @@ -0,0 +1 @@ +name: license diff --git a/seed/openapi/license/.mock/fern.config.json b/seed/openapi/license/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/openapi/license/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/openapi/license/.mock/generators.yml b/seed/openapi/license/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/openapi/license/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/openapi/license/openapi.yml b/seed/openapi/license/openapi.yml new file mode 100644 index 00000000000..5ef1896c9e8 --- /dev/null +++ b/seed/openapi/license/openapi.yml @@ -0,0 +1,26 @@ +openapi: 3.0.1 +info: + title: license + version: '' +paths: + /: + get: + operationId: get + tags: + - '' + parameters: [] + responses: + '204': + description: '' +components: + schemas: + Type: + title: Type + type: object + description: A simple type with just a name. + properties: + name: + type: string + required: + - name + securitySchemes: {} diff --git a/seed/openapi/license/snippet-templates.json b/seed/openapi/license/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/openapi/license/snippet.json b/seed/openapi/license/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/php-model/exhaustive/.mock/definition/endpoints/content-type.yml b/seed/php-model/exhaustive/.mock/definition/endpoints/content-type.yml new file mode 100644 index 00000000000..7c54e39fa5a --- /dev/null +++ b/seed/php-model/exhaustive/.mock/definition/endpoints/content-type.yml @@ -0,0 +1,19 @@ +imports: + objects: ../types/object.yml + +service: + auth: true + base-path: /foo + endpoints: + postJsonPatchContentType: + path: /bar + method: POST + request: + body: objects.ObjectWithOptionalField + content-type: application/json-patch+json + postJsonPatchContentWithCharsetType: + path: /baz + method: POST + request: + body: objects.ObjectWithOptionalField + content-type: application/json-patch+json; charset=utf-8 diff --git a/seed/php-model/file-upload/.mock/definition/service.yml b/seed/php-model/file-upload/.mock/definition/service.yml index 5ae95021c2d..956e6ba73ba 100644 --- a/seed/php-model/file-upload/.mock/definition/service.yml +++ b/seed/php-model/file-upload/.mock/definition/service.yml @@ -64,6 +64,9 @@ service: bar: type: MyObject content-type: application/json + foobar: + type: optional + content-type: application/json types: Id: string diff --git a/seed/php-model/grpc-proto-exhaustive/.mock/generators.yml b/seed/php-model/grpc-proto-exhaustive/.mock/generators.yml index c23323621f2..972ed6d7b73 100644 --- a/seed/php-model/grpc-proto-exhaustive/.mock/generators.yml +++ b/seed/php-model/grpc-proto-exhaustive/.mock/generators.yml @@ -1,7 +1,6 @@ api: - - path: openapi/openapi.yml - - proto: - root: proto - target: proto/data/v1/data.proto - overrides: overrides.yml - local-generation: true + - proto: + root: proto + target: proto/data/v1/data.proto + overrides: overrides.yml + local-generation: true \ No newline at end of file diff --git a/seed/php-model/grpc-proto-exhaustive/.mock/openapi/openapi.yml b/seed/php-model/grpc-proto-exhaustive/.mock/openapi/openapi.yml deleted file mode 100644 index ebc23143df3..00000000000 --- a/seed/php-model/grpc-proto-exhaustive/.mock/openapi/openapi.yml +++ /dev/null @@ -1,33 +0,0 @@ -openapi: 3.0.3 -info: - title: Test API - version: 1.0.0 -servers: - - url: https://localhost -tags: - - name: dataservice -paths: - /foo: - post: - tag: dataservice - x-fern-sdk-group-name: - - dataservice - x-fern-sdk-method-name: foo - security: - - ApiKeyAuth: [] - operationId: foo - responses: - "200": - content: - application/json: - schema: - type: object - -security: - - ApiKeyAuth: [] -components: - securitySchemes: - ApiKeyAuth: - type: apiKey - in: header - name: X-API-Key diff --git a/seed/php-model/license/.github/workflows/ci.yml b/seed/php-model/license/.github/workflows/ci.yml new file mode 100644 index 00000000000..258bf33a19f --- /dev/null +++ b/seed/php-model/license/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Build + run: | + composer build + + - name: Analyze + run: | + composer analyze + + unit-tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + + - name: Install tools + run: | + composer install + + - name: Run Tests + run: | + composer test \ No newline at end of file diff --git a/seed/php-model/license/.gitignore b/seed/php-model/license/.gitignore new file mode 100644 index 00000000000..f38efc46ade --- /dev/null +++ b/seed/php-model/license/.gitignore @@ -0,0 +1,4 @@ +.php-cs-fixer.cache +.phpunit.result.cache +composer.lock +vendor/ \ No newline at end of file diff --git a/seed/php-model/license/.mock/definition/__package__.yml b/seed/php-model/license/.mock/definition/__package__.yml new file mode 100644 index 00000000000..b1e4d4a878f --- /dev/null +++ b/seed/php-model/license/.mock/definition/__package__.yml @@ -0,0 +1,13 @@ +types: + Type: + docs: A simple type with just a name. + properties: + name: string + +service: + auth: false + base-path: / + endpoints: + get: + path: "/" + method: GET diff --git a/seed/php-model/license/.mock/definition/api.yml b/seed/php-model/license/.mock/definition/api.yml new file mode 100644 index 00000000000..5523ff1f181 --- /dev/null +++ b/seed/php-model/license/.mock/definition/api.yml @@ -0,0 +1 @@ +name: license diff --git a/seed/php-model/license/.mock/fern.config.json b/seed/php-model/license/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/php-model/license/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/php-model/license/.mock/generators.yml b/seed/php-model/license/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/php-model/license/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/php-model/license/composer.json b/seed/php-model/license/composer.json new file mode 100644 index 00000000000..5c96c0056e0 --- /dev/null +++ b/seed/php-model/license/composer.json @@ -0,0 +1,40 @@ + +{ + "name": "seed/seed", + "version": "0.0.1", + "description": "Seed PHP Library", + "keywords": [ + "seed", + "api", + "sdk" + ], + "license": [], + "require": { + "php": "^8.1", + "ext-json": "*", + "guzzlehttp/guzzle": "^7.9" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "friendsofphp/php-cs-fixer": "3.5.0", + "phpstan/phpstan": "^1.12" + }, + "autoload": { + "psr-4": { + "Seed\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "\\Seed\\Tests\\": "tests/" + } + }, + "scripts": { + "build": [ + "@php -l src", + "@php -l tests" + ], + "test": "phpunit", + "analyze": "phpstan analyze src tests" + } +} diff --git a/seed/php-model/license/phpstan.neon b/seed/php-model/license/phpstan.neon new file mode 100644 index 00000000000..29a11a92a19 --- /dev/null +++ b/seed/php-model/license/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: max + paths: + - src + - tests \ No newline at end of file diff --git a/seed/php-model/license/phpunit.xml b/seed/php-model/license/phpunit.xml new file mode 100644 index 00000000000..54630a51163 --- /dev/null +++ b/seed/php-model/license/phpunit.xml @@ -0,0 +1,7 @@ + + + + tests + + + \ No newline at end of file diff --git a/seed/php-model/license/snippet-templates.json b/seed/php-model/license/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/php-model/license/snippet.json b/seed/php-model/license/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/php-model/license/src/Core/Json/JsonDecoder.php b/seed/php-model/license/src/Core/Json/JsonDecoder.php new file mode 100644 index 00000000000..2ddff027348 --- /dev/null +++ b/seed/php-model/license/src/Core/Json/JsonDecoder.php @@ -0,0 +1,161 @@ + $type The type definition for deserialization. + * @return mixed[]|array The deserialized array. + * @throws JsonException If the decoded value is not an array. + */ + public static function decodeArray(string $json, array $type): array + { + $decoded = self::decode($json); + if (!is_array($decoded)) { + throw new JsonException("Unexpected non-array json value: " . $json); + } + return JsonDeserializer::deserializeArray($decoded, $type); + } + + /** + * Decodes a JSON string and deserializes it based on the provided union type definition. + * + * @param string $json The JSON string to decode. + * @param Union $union The union type definition for deserialization. + * @return mixed The deserialized value. + * @throws JsonException If the deserialization for all types in the union fails. + */ + public static function decodeUnion(string $json, Union $union): mixed + { + $decoded = self::decode($json); + return JsonDeserializer::deserializeUnion($decoded, $union); + } + /** + * Decodes a JSON string and returns a mixed. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded mixed. + * @throws JsonException If the decoded value is not an mixed. + */ + public static function decodeMixed(string $json): mixed + { + return self::decode($json); + } + + /** + * Decodes a JSON string into a PHP value. + * + * @param string $json The JSON string to decode. + * @return mixed The decoded value. + * @throws JsonException If an error occurs during JSON decoding. + */ + public static function decode(string $json): mixed + { + return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/seed/php-model/license/src/Core/Json/JsonDeserializer.php b/seed/php-model/license/src/Core/Json/JsonDeserializer.php new file mode 100644 index 00000000000..5f0ca2d7ed0 --- /dev/null +++ b/seed/php-model/license/src/Core/Json/JsonDeserializer.php @@ -0,0 +1,204 @@ + $data The array to be deserialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The deserialized array. + * @throws JsonException If deserialization fails. + */ + public static function deserializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::deserializeMap($data, $type) + : self::deserializeList($data, $type); + } + + /** + * Deserializes a value based on its type definition. + * + * @param mixed $data The data to deserialize. + * @param mixed $type The type definition. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::deserializeUnion($data, $type); + } + + if (is_array($type)) { + return self::deserializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::deserializeSingleValue($data, $type); + } + + /** + * Deserializes a value based on the possible types in a union type definition. + * + * @param mixed $data The data to deserialize. + * @param Union $type The union type definition. + * @return mixed The deserialized value. + * @throws JsonException If none of the union types can successfully deserialize the value. + */ + public static function deserializeUnion(mixed $data, Union $type): mixed + { + foreach ($type->types as $unionType) { + try { + return self::deserializeValue($data, $unionType); + } catch (Exception) { + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot deserialize value of type $readableType with any of the union types: " . $type + ); + } + + /** + * Deserializes a single value based on its expected type. + * + * @param mixed $data The data to deserialize. + * @param string $type The expected type. + * @return mixed The deserialized value. + * @throws JsonException If deserialization fails. + */ + private static function deserializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if ($type === 'date' && is_string($data)) { + return self::deserializeDate($data); + } + + if ($type === 'datetime' && is_string($data)) { + return self::deserializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && is_array($data)) { + return self::deserializeObject($data, $type); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP, and because + // floats make come through from json_decoded as integers + if ($type === 'float' && (is_numeric($data))) { + return (float) $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to deserialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Deserializes an array into an object of the given type. + * + * @param array $data The data to deserialize. + * @param string $type The class name of the object to deserialize into. + * + * @return object The deserialized object. + * + * @throws JsonException If the type does not implement JsonSerializableType. + */ + public static function deserializeObject(array $data, string $type): object + { + if (!is_subclass_of($type, JsonSerializableType::class)) { + throw new JsonException("$type is not a subclass of JsonSerializableType."); + } + return $type::jsonDeserialize($data); + } + + /** + * Deserializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to deserialize. + * @param array $type The type definition for the map. + * @return array The deserialized map. + * @throws JsonException If deserialization fails. + */ + private static function deserializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, (string)$keyType); + $result[$key] = self::deserializeValue($item, $valueType); + } + + return $result; + } + + /** + * Deserializes a list (indexed array) with a defined value type. + * + * @param array $data The list to deserialize. + * @param array $type The type definition for the list. + * @return array The deserialized list. + * @throws JsonException If deserialization fails. + */ + private static function deserializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::deserializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/license/src/Core/Json/JsonEncoder.php b/seed/php-model/license/src/Core/Json/JsonEncoder.php new file mode 100644 index 00000000000..0dbf3fcc994 --- /dev/null +++ b/seed/php-model/license/src/Core/Json/JsonEncoder.php @@ -0,0 +1,20 @@ +jsonSerialize(); + $encoded = JsonEncoder::encode($serializedObject); + if (!$encoded) { + throw new Exception("Could not encode type"); + } + return $encoded; + } + + /** + * Serializes the object to an array. + * + * @return mixed[] Array representation of the object. + * @throws JsonException If serialization fails. + */ + public function jsonSerialize(): array + { + $result = []; + $reflectionClass = new \ReflectionClass($this); + + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property); + if ($jsonKey == null) { + continue; + } + $value = $property->getValue($this); + + // Handle DateTime properties + $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; + if ($dateTypeAttr && $value instanceof DateTime) { + $dateType = $dateTypeAttr->newInstance()->type; + $value = ($dateType === Date::TYPE_DATE) + ? JsonSerializer::serializeDate($value) + : JsonSerializer::serializeDateTime($value); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonSerializer::serializeUnion($value, $unionType); + } + + // Handle arrays with type annotations + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if ($arrayTypeAttr && is_array($value)) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonSerializer::serializeArray($value, $arrayType); + } + + // Handle object + if (is_object($value)) { + $value = JsonSerializer::serializeObject($value); + } + + if ($value !== null) { + $result[$jsonKey] = $value; + } + } + + return $result; + } + + /** + * Deserializes a JSON string into an instance of the calling class. + * + * @param string $json JSON string to deserialize. + * @return static Deserialized object. + * @throws JsonException If decoding fails or the result is not an array. + * @throws Exception If deserialization fails. + */ + public static function fromJson(string $json): static + { + $decodedJson = JsonDecoder::decode($json); + if (!is_array($decodedJson)) { + throw new JsonException("Unexpected non-array decoded type: " . gettype($decodedJson)); + } + return self::jsonDeserialize($decodedJson); + } + + /** + * Deserializes an array into an instance of the calling class. + * + * @param array $data Array data to deserialize. + * @return static Deserialized object. + * @throws JsonException If deserialization fails. + */ + public static function jsonDeserialize(array $data): static + { + $reflectionClass = new \ReflectionClass(static::class); + $constructor = $reflectionClass->getConstructor(); + + if ($constructor === null) { + throw new JsonException("No constructor found."); + } + + $args = []; + foreach ($reflectionClass->getProperties() as $property) { + $jsonKey = self::getJsonKey($property) ?? $property->getName(); + + if (array_key_exists($jsonKey, $data)) { + $value = $data[$jsonKey]; + + // Handle Date annotation + $dateTypeAttr = $property->getAttributes(Date::class)[0] ?? null; + if ($dateTypeAttr) { + $dateType = $dateTypeAttr->newInstance()->type; + if (!is_string($value)) { + throw new JsonException("Unexpected non-string type for date."); + } + $value = ($dateType === Date::TYPE_DATE) + ? JsonDeserializer::deserializeDate($value) + : JsonDeserializer::deserializeDateTime($value); + } + + // Handle Array annotation + $arrayTypeAttr = $property->getAttributes(ArrayType::class)[0] ?? null; + if (is_array($value) && $arrayTypeAttr) { + $arrayType = $arrayTypeAttr->newInstance()->type; + $value = JsonDeserializer::deserializeArray($value, $arrayType); + } + + // Handle Union annotations + $unionTypeAttr = $property->getAttributes(Union::class)[0] ?? null; + if ($unionTypeAttr) { + $unionType = $unionTypeAttr->newInstance(); + $value = JsonDeserializer::deserializeUnion($value, $unionType); + } + + // Handle object + $type = $property->getType(); + if (is_array($value) && $type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $value = JsonDeserializer::deserializeObject($value, $type->getName()); + } + + $args[$property->getName()] = $value; + } else { + $defaultValue = $property->getDefaultValue() ?? null; + $args[$property->getName()] = $defaultValue; + } + } + // @phpstan-ignore-next-line + return new static($args); + } + + /** + * Retrieves the JSON key associated with a property. + * + * @param ReflectionProperty $property The reflection property. + * @return ?string The JSON key, or null if not available. + */ + private static function getJsonKey(ReflectionProperty $property): ?string + { + $jsonPropertyAttr = $property->getAttributes(JsonProperty::class)[0] ?? null; + return $jsonPropertyAttr?->newInstance()?->name; + } +} diff --git a/seed/php-model/license/src/Core/Json/JsonSerializer.php b/seed/php-model/license/src/Core/Json/JsonSerializer.php new file mode 100644 index 00000000000..7dd6fe517af --- /dev/null +++ b/seed/php-model/license/src/Core/Json/JsonSerializer.php @@ -0,0 +1,192 @@ +format(Constant::DateFormat); + } + + /** + * Serializes a DateTime object into a string using the date-time format. + * + * @param DateTime $date The DateTime object to serialize. + * @return string The serialized date-time string. + */ + public static function serializeDateTime(DateTime $date): string + { + return $date->format(Constant::DateTimeFormat); + } + + /** + * Serializes an array based on type annotations (either a list or map). + * + * @param mixed[]|array $data The array to be serialized. + * @param mixed[]|array $type The type definition from the annotation. + * @return mixed[]|array The serialized array. + * @throws JsonException If serialization fails. + */ + public static function serializeArray(array $data, array $type): array + { + return Utils::isMapType($type) + ? self::serializeMap($data, $type) + : self::serializeList($data, $type); + } + + /** + * Serializes a value based on its type definition. + * + * @param mixed $data The value to serialize. + * @param mixed $type The type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeValue(mixed $data, mixed $type): mixed + { + if ($type instanceof Union) { + return self::serializeUnion($data, $type); + } + + if (is_array($type)) { + return self::serializeArray((array)$data, $type); + } + + if (gettype($type) != "string") { + throw new JsonException("Unexpected non-string type."); + } + + return self::serializeSingleValue($data, $type); + } + + /** + * Serializes a value for a union type definition. + * + * @param mixed $data The value to serialize. + * @param Union $unionType The union type definition. + * @return mixed The serialized value. + * @throws JsonException If serialization fails for all union types. + */ + public static function serializeUnion(mixed $data, Union $unionType): mixed + { + foreach ($unionType->types as $type) { + try { + return self::serializeValue($data, $type); + } catch (Exception) { + // Try the next type in the union + continue; + } + } + $readableType = Utils::getReadableType($data); + throw new JsonException( + "Cannot serialize value of type $readableType with any of the union types: " . $unionType + ); + } + + /** + * Serializes a single value based on its type. + * + * @param mixed $data The value to serialize. + * @param string $type The expected type. + * @return mixed The serialized value. + * @throws JsonException If serialization fails. + */ + private static function serializeSingleValue(mixed $data, string $type): mixed + { + if ($type === 'null' && $data === null) { + return null; + } + + if (($type === 'date' || $type === 'datetime') && $data instanceof DateTime) { + return $type === 'date' ? self::serializeDate($data) : self::serializeDateTime($data); + } + + if ($type === 'mixed') { + return $data; + } + + if (class_exists($type) && $data instanceof $type) { + return self::serializeObject($data); + } + + // Handle floats as a special case since gettype($data) returns "double" for float values in PHP. + if ($type === 'float' && is_float($data)) { + return $data; + } + + if (gettype($data) === $type) { + return $data; + } + + throw new JsonException("Unable to serialize value of type '" . gettype($data) . "' as '$type'."); + } + + /** + * Serializes an object to a JSON-serializable format. + * + * @param object $data The object to serialize. + * @return mixed The serialized data. + * @throws JsonException If the object does not implement JsonSerializable. + */ + public static function serializeObject(object $data): mixed + { + if (!is_subclass_of($data, JsonSerializable::class)) { + $type = get_class($data); + throw new JsonException("Class $type must implement JsonSerializable."); + } + return $data->jsonSerialize(); + } + + /** + * Serializes a map (associative array) with defined key and value types. + * + * @param array $data The associative array to serialize. + * @param array $type The type definition for the map. + * @return array The serialized map. + * @throws JsonException If serialization fails. + */ + private static function serializeMap(array $data, array $type): array + { + $keyType = array_key_first($type); + if ($keyType === null) { + throw new JsonException("Unexpected no key in ArrayType."); + } + $valueType = $type[$keyType]; + $result = []; + + foreach ($data as $key => $item) { + $key = Utils::castKey($key, $keyType); + $result[$key] = self::serializeValue($item, $valueType); + } + + return $result; + } + + /** + * Serializes a list (indexed array) where only the value type is defined. + * + * @param array $data The list to serialize. + * @param array $type The type definition for the list. + * @return array The serialized list. + * @throws JsonException If serialization fails. + */ + private static function serializeList(array $data, array $type): array + { + $valueType = $type[0]; + return array_map(fn ($item) => self::serializeValue($item, $valueType), $data); + } +} diff --git a/seed/php-model/license/src/Core/Json/Utils.php b/seed/php-model/license/src/Core/Json/Utils.php new file mode 100644 index 00000000000..7577c058916 --- /dev/null +++ b/seed/php-model/license/src/Core/Json/Utils.php @@ -0,0 +1,61 @@ + $type The type definition from the annotation. + * @return bool True if the type is a map, false if it's a list. + */ + public static function isMapType(array $type): bool + { + return count($type) === 1 && !array_is_list($type); + } + + /** + * Casts the key to the appropriate type based on the key type. + * + * @param mixed $key The key to be cast. + * @param string $keyType The type to cast the key to ('string', 'integer', 'float'). + * @return mixed The casted key. + * @throws JsonException + */ + public static function castKey(mixed $key, string $keyType): mixed + { + if (!is_scalar($key)) { + throw new JsonException("Key must be a scalar type."); + } + return match ($keyType) { + 'integer' => (int)$key, + 'float' => (float)$key, + 'string' => (string)$key, + default => $key, + }; + } + + /** + * Returns a human-readable representation of the input's type. + * + * @param mixed $input The input value to determine the type of. + * @return string A readable description of the input type. + */ + public static function getReadableType(mixed $input): string + { + if (is_object($input)) { + return get_class($input); + } elseif (is_array($input)) { + return 'array(' . count($input) . ' items)'; + } elseif (is_null($input)) { + return 'null'; + } else { + return gettype($input); + } + } +} diff --git a/seed/php-model/license/src/Core/Types/ArrayType.php b/seed/php-model/license/src/Core/Types/ArrayType.php new file mode 100644 index 00000000000..a26d29008ec --- /dev/null +++ b/seed/php-model/license/src/Core/Types/ArrayType.php @@ -0,0 +1,16 @@ + 'valueType'] for maps, or ['valueType'] for lists + */ + public function __construct(public array $type) + { + } +} diff --git a/seed/php-model/license/src/Core/Types/Constant.php b/seed/php-model/license/src/Core/Types/Constant.php new file mode 100644 index 00000000000..5ac4518cc6d --- /dev/null +++ b/seed/php-model/license/src/Core/Types/Constant.php @@ -0,0 +1,12 @@ +> The types allowed for this property, which can be strings, arrays, or nested Union types. + */ + public array $types; + + /** + * Constructor for the Union attribute. + * + * @param string|Union|array ...$types The list of types that the property can accept. + * This can include primitive types (e.g., 'string', 'int'), arrays, or other Union instances. + * + * Example: + * ```php + * #[Union('string', 'null', 'date', new Union('boolean', 'int'))] + * ``` + */ + public function __construct(string|Union|array ...$types) + { + $this->types = $types; + } + + /** + * Converts the Union type to a string representation. + * + * @return string A string representation of the union types. + */ + public function __toString(): string + { + return implode(' | ', array_map(function ($type) { + if (is_string($type)) { + return $type; + } elseif ($type instanceof Union) { + return (string) $type; // Recursively handle nested unions + } elseif (is_array($type)) { + return 'array'; // Handle arrays + } + }, $this->types)); + } +} diff --git a/seed/php-model/license/src/Type.php b/seed/php-model/license/src/Type.php new file mode 100644 index 00000000000..6e0056d463b --- /dev/null +++ b/seed/php-model/license/src/Type.php @@ -0,0 +1,29 @@ +name = $values['name']; + } +} diff --git a/seed/php-model/license/tests/Seed/Core/Json/DateArrayTest.php b/seed/php-model/license/tests/Seed/Core/Json/DateArrayTest.php new file mode 100644 index 00000000000..a72cfdbdd22 --- /dev/null +++ b/seed/php-model/license/tests/Seed/Core/Json/DateArrayTest.php @@ -0,0 +1,54 @@ +dates = $values['dates']; + } +} + +class DateArrayTest extends TestCase +{ + public function testDateTimeInArrays(): void + { + $expectedJson = json_encode( + [ + 'dates' => ['2023-01-01', '2023-02-01', '2023-03-01'] + ], + JSON_THROW_ON_ERROR + ); + + $object = DateArray::fromJson($expectedJson); + $this->assertInstanceOf(DateTime::class, $object->dates[0], 'dates[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dates[0]->format('Y-m-d'), 'dates[0] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[1], 'dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-02-01', $object->dates[1]->format('Y-m-d'), 'dates[1] should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->dates[2], 'dates[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->dates[2]->format('Y-m-d'), 'dates[2] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for dates array.'); + } +} diff --git a/seed/php-model/license/tests/Seed/Core/Json/EmptyArrayTest.php b/seed/php-model/license/tests/Seed/Core/Json/EmptyArrayTest.php new file mode 100644 index 00000000000..d243a08916d --- /dev/null +++ b/seed/php-model/license/tests/Seed/Core/Json/EmptyArrayTest.php @@ -0,0 +1,71 @@ + $emptyMapArray + */ + #[JsonProperty('empty_map_array')] + #[ArrayType(['integer' => new Union('string', 'null')])] + public array $emptyMapArray; + + /** + * @var array $emptyDatesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('empty_dates_array')] + public array $emptyDatesArray; + + /** + * @param array{ + * emptyStringArray: string[], + * emptyMapArray: array, + * emptyDatesArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->emptyStringArray = $values['emptyStringArray']; + $this->emptyMapArray = $values['emptyMapArray']; + $this->emptyDatesArray = $values['emptyDatesArray']; + } +} + +class EmptyArrayTest extends TestCase +{ + public function testEmptyArray(): void + { + $expectedJson = json_encode( + [ + 'empty_string_array' => [], + 'empty_map_array' => [], + 'empty_dates_array' => [] + ], + JSON_THROW_ON_ERROR + ); + + $object = EmptyArray::fromJson($expectedJson); + $this->assertEmpty($object->emptyStringArray, 'empty_string_array should be empty.'); + $this->assertEmpty($object->emptyMapArray, 'empty_map_array should be empty.'); + $this->assertEmpty($object->emptyDatesArray, 'empty_dates_array should be empty.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for EmptyArraysType.'); + } +} diff --git a/seed/php-model/license/tests/Seed/Core/Json/EnumTest.php b/seed/php-model/license/tests/Seed/Core/Json/EnumTest.php new file mode 100644 index 00000000000..bf83d5b8ab0 --- /dev/null +++ b/seed/php-model/license/tests/Seed/Core/Json/EnumTest.php @@ -0,0 +1,76 @@ +value; + } +} + +class ShapeType extends JsonSerializableType +{ + /** + * @var Shape $shape + */ + #[JsonProperty('shape')] + public Shape $shape; + + /** + * @var Shape[] $shapes + */ + #[ArrayType([Shape::class])] + #[JsonProperty('shapes')] + public array $shapes; + + /** + * @param Shape $shape + * @param Shape[] $shapes + */ + public function __construct( + Shape $shape, + array $shapes, + ) { + $this->shape = $shape; + $this->shapes = $shapes; + } +} + +class EnumTest extends TestCase +{ + public function testEnumSerialization(): void + { + $object = new ShapeType( + Shape::Circle, + [Shape::Square, Shape::Circle, Shape::Triangle] + ); + + $expectedJson = json_encode([ + 'shape' => 'CIRCLE', + 'shapes' => ['SQUARE', 'CIRCLE', 'TRIANGLE'] + ], JSON_THROW_ON_ERROR); + + $actualJson = $object->toJson(); + + $this->assertJsonStringEqualsJsonString( + $expectedJson, + $actualJson, + 'Serialized JSON does not match expected JSON for shape and shapes properties.' + ); + } +} diff --git a/seed/php-model/license/tests/Seed/Core/Json/ExhaustiveTest.php b/seed/php-model/license/tests/Seed/Core/Json/ExhaustiveTest.php new file mode 100644 index 00000000000..f542d6a535d --- /dev/null +++ b/seed/php-model/license/tests/Seed/Core/Json/ExhaustiveTest.php @@ -0,0 +1,197 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class Type extends JsonSerializableType +{ + /** + * @var Nested nestedType + */ + #[JsonProperty('nested_type')] + public Nested $nestedType; /** + + * @var string $simpleProperty + */ + #[JsonProperty('simple_property')] + public string $simpleProperty; + + /** + * @var DateTime $dateProperty + */ + #[Date(Date::TYPE_DATE)] + #[JsonProperty('date_property')] + public DateTime $dateProperty; + + /** + * @var DateTime $datetimeProperty + */ + #[Date(Date::TYPE_DATETIME)] + #[JsonProperty('datetime_property')] + public DateTime $datetimeProperty; + + /** + * @var array $stringArray + */ + #[ArrayType(['string'])] + #[JsonProperty('string_array')] + public array $stringArray; + + /** + * @var array $mapProperty + */ + #[ArrayType(['string' => 'integer'])] + #[JsonProperty('map_property')] + public array $mapProperty; + + /** + * @var array $objectArray + */ + #[ArrayType(['integer' => new Union(Nested::class, 'null')])] + #[JsonProperty('object_array')] + public array $objectArray; + + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union('string', 'null')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @var array $datesArray + */ + #[ArrayType([new Union('date', 'null')])] + #[JsonProperty('dates_array')] + public array $datesArray; + + /** + * @var string|null $nullableProperty + */ + #[JsonProperty('nullable_property')] + public ?string $nullableProperty; + + /** + * @param array{ + * nestedType: Nested, + * simpleProperty: string, + * dateProperty: DateTime, + * datetimeProperty: DateTime, + * stringArray: array, + * mapProperty: array, + * objectArray: array, + * nestedArray: array>, + * datesArray: array, + * nullableProperty?: string|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedType = $values['nestedType']; + $this->simpleProperty = $values['simpleProperty']; + $this->dateProperty = $values['dateProperty']; + $this->datetimeProperty = $values['datetimeProperty']; + $this->stringArray = $values['stringArray']; + $this->mapProperty = $values['mapProperty']; + $this->objectArray = $values['objectArray']; + $this->nestedArray = $values['nestedArray']; + $this->datesArray = $values['datesArray']; + $this->nullableProperty = $values['nullableProperty'] ?? null; + } +} + +class ExhaustiveTest extends TestCase +{ + /** + * Test serialization and deserialization of all types in Type. + */ + public function testExhaustive(): void + { + $expectedJson = json_encode( + [ + 'nested_type' => ['nested_property' => '1995-07-20'], + 'simple_property' => 'Test String', + // Omit 'nullable_property' to test null serialization + 'date_property' => '2023-01-01', + 'datetime_property' => '2023-01-01T12:34:56+00:00', + 'string_array' => ['one', 'two', 'three'], + 'map_property' => ['key1' => 1, 'key2' => 2], + 'object_array' => [ + 1 => ['nested_property' => '2021-07-20'], + 2 => null, // Testing nullable objects in array + ], + 'nested_array' => [ + 1 => [1 => 'value1', 2 => null], // Testing nullable strings in nested array + 2 => [3 => 'value3', 4 => 'value4'] + ], + 'dates_array' => ['2023-01-01', null, '2023-03-01'] // Testing nullable dates in array> + ], + JSON_THROW_ON_ERROR + ); + + $object = Type::fromJson($expectedJson); + + // Check that nullable property is null and not included in JSON + $this->assertNull($object->nullableProperty, 'Nullable property should be null.'); + + // Check date properties + $this->assertInstanceOf(DateTime::class, $object->dateProperty, 'date_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->dateProperty->format('Y-m-d'), 'date_property should have the correct date.'); + $this->assertInstanceOf(DateTime::class, $object->datetimeProperty, 'datetime_property should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:34:56', $object->datetimeProperty->format('Y-m-d H:i:s'), 'datetime_property should have the correct datetime.'); + + // Check scalar arrays + $this->assertEquals(['one', 'two', 'three'], $object->stringArray, 'string_array should match the original data.'); + $this->assertEquals(['key1' => 1, 'key2' => 2], $object->mapProperty, 'map_property should match the original data.'); + + // Check object array with nullable elements + $this->assertInstanceOf(Nested::class, $object->objectArray[1], 'object_array[1] should be an instance of TestNestedType1.'); + $this->assertEquals('2021-07-20', $object->objectArray[1]->nestedProperty->format('Y-m-d'), 'object_array[1]->nestedProperty should match the original data.'); + $this->assertNull($object->objectArray[2], 'object_array[2] should be null.'); + + // Check nested array with nullable strings + $this->assertEquals('value1', $object->nestedArray[1][1], 'nested_array[1][1] should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertEquals('value3', $object->nestedArray[2][3], 'nested_array[2][3] should match the original data.'); + $this->assertEquals('value4', $object->nestedArray[2][4], 'nested_array[2][4] should match the original data.'); + + // Check dates array with nullable DateTime objects + $this->assertInstanceOf(DateTime::class, $object->datesArray[0], 'dates_array[0] should be a DateTime instance.'); + $this->assertEquals('2023-01-01', $object->datesArray[0]->format('Y-m-d'), 'dates_array[0] should have the correct date.'); + $this->assertNull($object->datesArray[1], 'dates_array[1] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->datesArray[2], 'dates_array[2] should be a DateTime instance.'); + $this->assertEquals('2023-03-01', $object->datesArray[2]->format('Y-m-d'), 'dates_array[2] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'The serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-model/license/tests/Seed/Core/Json/InvalidTest.php b/seed/php-model/license/tests/Seed/Core/Json/InvalidTest.php new file mode 100644 index 00000000000..7d1d79406a5 --- /dev/null +++ b/seed/php-model/license/tests/Seed/Core/Json/InvalidTest.php @@ -0,0 +1,42 @@ +integerProperty = $values['integerProperty']; + } +} + +class InvalidTest extends TestCase +{ + public function testInvalidJsonThrowsException(): void + { + $this->expectException(\TypeError::class); + $json = json_encode( + [ + 'integer_property' => 'not_an_integer' + ], + JSON_THROW_ON_ERROR + ); + Invalid::fromJson($json); + } +} diff --git a/seed/php-model/license/tests/Seed/Core/Json/NestedUnionArrayTest.php b/seed/php-model/license/tests/Seed/Core/Json/NestedUnionArrayTest.php new file mode 100644 index 00000000000..0fcdd06667e --- /dev/null +++ b/seed/php-model/license/tests/Seed/Core/Json/NestedUnionArrayTest.php @@ -0,0 +1,89 @@ +nestedProperty = $values['nestedProperty']; + } +} + +class NestedUnionArray extends JsonSerializableType +{ + /** + * @var array> $nestedArray + */ + #[ArrayType(['integer' => ['integer' => new Union(UnionObject::class, 'null', 'date')]])] + #[JsonProperty('nested_array')] + public array $nestedArray; + + /** + * @param array{ + * nestedArray: array>, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nestedArray = $values['nestedArray']; + } +} + +class NestedUnionArrayTest extends TestCase +{ + public function testNestedUnionArray(): void + { + $expectedJson = json_encode( + [ + 'nested_array' => [ + 1 => [ + 1 => ['nested_property' => 'Nested One'], + 2 => null, + 4 => '2023-01-02' + ], + 2 => [ + 5 => ['nested_property' => 'Nested Two'], + 7 => '2023-02-02' + ] + ] + ], + JSON_THROW_ON_ERROR + ); + + $object = NestedUnionArray::fromJson($expectedJson); + $this->assertInstanceOf(UnionObject::class, $object->nestedArray[1][1], 'nested_array[1][1] should be an instance of Object.'); + $this->assertEquals('Nested One', $object->nestedArray[1][1]->nestedProperty, 'nested_array[1][1]->nestedProperty should match the original data.'); + $this->assertNull($object->nestedArray[1][2], 'nested_array[1][2] should be null.'); + $this->assertInstanceOf(DateTime::class, $object->nestedArray[1][4], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-01-02T00:00:00+00:00', $object->nestedArray[1][4]->format(Constant::DateTimeFormat), 'nested_array[1][4] should have the correct datetime.'); + $this->assertInstanceOf(UnionObject::class, $object->nestedArray[2][5], 'nested_array[2][5] should be an instance of Object.'); + $this->assertEquals('Nested Two', $object->nestedArray[2][5]->nestedProperty, 'nested_array[2][5]->nestedProperty should match the original data.'); + $this->assertInstanceOf(DateTime::class, $object->nestedArray[2][7], 'nested_array[1][4] should be a DateTime instance.'); + $this->assertEquals('2023-02-02', $object->nestedArray[2][7]->format('Y-m-d'), 'nested_array[1][4] should have the correct date.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nested_array.'); + } +} diff --git a/seed/php-model/license/tests/Seed/Core/Json/NullPropertyTest.php b/seed/php-model/license/tests/Seed/Core/Json/NullPropertyTest.php new file mode 100644 index 00000000000..ce20a244282 --- /dev/null +++ b/seed/php-model/license/tests/Seed/Core/Json/NullPropertyTest.php @@ -0,0 +1,53 @@ +nonNullProperty = $values['nonNullProperty']; + $this->nullProperty = $values['nullProperty'] ?? null; + } +} + +class NullPropertyTest extends TestCase +{ + public function testNullPropertiesAreOmitted(): void + { + $object = new NullProperty( + [ + "nonNullProperty" => "Test String", + "nullProperty" => null + ] + ); + + $serialized = $object->jsonSerialize(); + $this->assertArrayHasKey('non_null_property', $serialized, 'non_null_property should be present in the serialized JSON.'); + $this->assertArrayNotHasKey('null_property', $serialized, 'null_property should be omitted from the serialized JSON.'); + $this->assertEquals('Test String', $serialized['non_null_property'], 'non_null_property should have the correct value.'); + } +} diff --git a/seed/php-model/license/tests/Seed/Core/Json/NullableArrayTest.php b/seed/php-model/license/tests/Seed/Core/Json/NullableArrayTest.php new file mode 100644 index 00000000000..fe0f19de6b1 --- /dev/null +++ b/seed/php-model/license/tests/Seed/Core/Json/NullableArrayTest.php @@ -0,0 +1,49 @@ + $nullableStringArray + */ + #[ArrayType([new Union('string', 'null')])] + #[JsonProperty('nullable_string_array')] + public array $nullableStringArray; + + /** + * @param array{ + * nullableStringArray: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->nullableStringArray = $values['nullableStringArray']; + } +} + +class NullableArrayTest extends TestCase +{ + public function testNullableArray(): void + { + $expectedJson = json_encode( + [ + 'nullable_string_array' => ['one', null, 'three'] + ], + JSON_THROW_ON_ERROR + ); + + $object = NullableArray::fromJson($expectedJson); + $this->assertEquals(['one', null, 'three'], $object->nullableStringArray, 'nullable_string_array should match the original data.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for nullable_string_array.'); + } +} diff --git a/seed/php-model/license/tests/Seed/Core/Json/ScalarTest.php b/seed/php-model/license/tests/Seed/Core/Json/ScalarTest.php new file mode 100644 index 00000000000..604b7c0b959 --- /dev/null +++ b/seed/php-model/license/tests/Seed/Core/Json/ScalarTest.php @@ -0,0 +1,116 @@ + $intFloatArray + */ + #[ArrayType([new Union('integer', 'float')])] + #[JsonProperty('int_float_array')] + public array $intFloatArray; + + /** + * @var array $floatArray + */ + #[ArrayType(['float'])] + #[JsonProperty('float_array')] + public array $floatArray; + + /** + * @var bool|null $nullableBooleanProperty + */ + #[JsonProperty('nullable_boolean_property')] + public ?bool $nullableBooleanProperty; + + /** + * @param array{ + * integerProperty: int, + * floatProperty: float, + * otherFloatProperty: float, + * booleanProperty: bool, + * stringProperty: string, + * intFloatArray: array, + * floatArray: array, + * nullableBooleanProperty?: bool|null, + * } $values + */ + public function __construct( + array $values, + ) { + $this->integerProperty = $values['integerProperty']; + $this->floatProperty = $values['floatProperty']; + $this->otherFloatProperty = $values['otherFloatProperty']; + $this->booleanProperty = $values['booleanProperty']; + $this->stringProperty = $values['stringProperty']; + $this->intFloatArray = $values['intFloatArray']; + $this->floatArray = $values['floatArray']; + $this->nullableBooleanProperty = $values['nullableBooleanProperty'] ?? null; + } +} + +class ScalarTest extends TestCase +{ + public function testAllScalarTypesIncludingFloat(): void + { + $expectedJson = json_encode( + [ + 'integer_property' => 42, + 'float_property' => 3.14159, + 'other_float_property' => 3, + 'boolean_property' => true, + 'string_property' => 'Hello, World!', + 'int_float_array' => [1, 2.5, 3, 4.75], + 'float_array' => [1, 2, 3, 4] // Ensure we handle "integer-looking" floats + ], + JSON_THROW_ON_ERROR + ); + + $object = Scalar::fromJson($expectedJson); + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals(3.14159, $object->floatProperty, 'float_property should be 3.14159.'); + $this->assertTrue($object->booleanProperty, 'boolean_property should be true.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + $this->assertNull($object->nullableBooleanProperty, 'nullable_boolean_property should be null.'); + $this->assertEquals([1, 2.5, 3, 4.75], $object->intFloatArray, 'int_float_array should match the original data.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTest.'); + } +} diff --git a/seed/php-model/license/tests/Seed/Core/Json/TraitTest.php b/seed/php-model/license/tests/Seed/Core/Json/TraitTest.php new file mode 100644 index 00000000000..837f239115f --- /dev/null +++ b/seed/php-model/license/tests/Seed/Core/Json/TraitTest.php @@ -0,0 +1,60 @@ +integerProperty = $values['integerProperty']; + $this->stringProperty = $values['stringProperty']; + } +} + +class TraitTest extends TestCase +{ + public function testTraitPropertyAndString(): void + { + $expectedJson = json_encode( + [ + 'integer_property' => 42, + 'string_property' => 'Hello, World!', + ], + JSON_THROW_ON_ERROR + ); + + $object = TypeWithTrait::fromJson($expectedJson); + $this->assertEquals(42, $object->integerProperty, 'integer_property should be 42.'); + $this->assertEquals('Hello, World!', $object->stringProperty, 'string_property should be "Hello, World!".'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for ScalarTypesTestWithTrait.'); + } +} diff --git a/seed/php-model/license/tests/Seed/Core/Json/UnionArrayTest.php b/seed/php-model/license/tests/Seed/Core/Json/UnionArrayTest.php new file mode 100644 index 00000000000..09933d2321d --- /dev/null +++ b/seed/php-model/license/tests/Seed/Core/Json/UnionArrayTest.php @@ -0,0 +1,57 @@ + $mixedDates + */ + #[ArrayType(['integer' => new Union('datetime', 'string', 'null')])] + #[JsonProperty('mixed_dates')] + public array $mixedDates; + + /** + * @param array{ + * mixedDates: array, + * } $values + */ + public function __construct( + array $values, + ) { + $this->mixedDates = $values['mixedDates']; + } +} + +class UnionArrayTest extends TestCase +{ + public function testUnionArray(): void + { + $expectedJson = json_encode( + [ + 'mixed_dates' => [ + 1 => '2023-01-01T12:00:00+00:00', + 2 => null, + 3 => 'Some String' + ] + ], + JSON_THROW_ON_ERROR + ); + + $object = UnionArray::fromJson($expectedJson); + $this->assertInstanceOf(DateTime::class, $object->mixedDates[1], 'mixed_dates[1] should be a DateTime instance.'); + $this->assertEquals('2023-01-01 12:00:00', $object->mixedDates[1]->format('Y-m-d H:i:s'), 'mixed_dates[1] should have the correct datetime.'); + $this->assertNull($object->mixedDates[2], 'mixed_dates[2] should be null.'); + $this->assertEquals('Some String', $object->mixedDates[3], 'mixed_dates[3] should be "Some String".'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match original JSON for mixed_dates.'); + } +} diff --git a/seed/php-model/license/tests/Seed/Core/Json/UnionPropertyTest.php b/seed/php-model/license/tests/Seed/Core/Json/UnionPropertyTest.php new file mode 100644 index 00000000000..3119baace62 --- /dev/null +++ b/seed/php-model/license/tests/Seed/Core/Json/UnionPropertyTest.php @@ -0,0 +1,115 @@ + 'integer'], UnionProperty::class)] + #[JsonProperty('complexUnion')] + public mixed $complexUnion; + + /** + * @param array{ + * complexUnion: string|int|null|array|UnionProperty + * } $values + */ + public function __construct( + array $values, + ) { + $this->complexUnion = $values['complexUnion']; + } +} + +class UnionPropertyTest extends TestCase +{ + public function testWithMapOfIntToInt(): void + { + $expectedJson = json_encode( + [ + 'complexUnion' => [1 => 100, 2 => 200] + ], + JSON_THROW_ON_ERROR + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsArray($object->complexUnion, 'complexUnion should be an array.'); + $this->assertEquals([1 => 100, 2 => 200], $object->complexUnion, 'complexUnion should match the original map of int => int.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNestedUnionPropertyType(): void + { + $expectedJson = json_encode( + [ + 'complexUnion' => new UnionProperty( + [ + 'complexUnion' => 'Nested String' + ] + ) + ], + JSON_THROW_ON_ERROR + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertInstanceOf(UnionProperty::class, $object->complexUnion, 'complexUnion should be an instance of UnionPropertyType.'); + $this->assertEquals('Nested String', $object->complexUnion->complexUnion, 'Nested complexUnion should match the original value.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithNull(): void + { + $expectedJson = json_encode( + [], + JSON_THROW_ON_ERROR + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertNull($object->complexUnion, 'complexUnion should be null.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithInteger(): void + { + $expectedJson = json_encode( + [ + 'complexUnion' => 42 + ], + JSON_THROW_ON_ERROR + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsInt($object->complexUnion, 'complexUnion should be an integer.'); + $this->assertEquals(42, $object->complexUnion, 'complexUnion should match the original integer.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } + + public function testWithString(): void + { + $expectedJson = json_encode( + [ + 'complexUnion' => 'Some String' + ], + JSON_THROW_ON_ERROR + ); + + $object = UnionProperty::fromJson($expectedJson); + $this->assertIsString($object->complexUnion, 'complexUnion should be a string.'); + $this->assertEquals('Some String', $object->complexUnion, 'complexUnion should match the original string.'); + + $actualJson = $object->toJson(); + $this->assertJsonStringEqualsJsonString($expectedJson, $actualJson, 'Serialized JSON does not match the original JSON.'); + } +} diff --git a/seed/php-sdk/file-upload/.mock/definition/service.yml b/seed/php-sdk/file-upload/.mock/definition/service.yml index 5ae95021c2d..956e6ba73ba 100644 --- a/seed/php-sdk/file-upload/.mock/definition/service.yml +++ b/seed/php-sdk/file-upload/.mock/definition/service.yml @@ -64,6 +64,9 @@ service: bar: type: MyObject content-type: application/json + foobar: + type: optional + content-type: application/json types: Id: string diff --git a/seed/php-sdk/file-upload/src/Service/Requests/WithContentTypeRequest.php b/seed/php-sdk/file-upload/src/Service/Requests/WithContentTypeRequest.php index c30015ed1a7..e2f619bcebb 100644 --- a/seed/php-sdk/file-upload/src/Service/Requests/WithContentTypeRequest.php +++ b/seed/php-sdk/file-upload/src/Service/Requests/WithContentTypeRequest.php @@ -26,11 +26,18 @@ class WithContentTypeRequest extends JsonSerializableType #[JsonProperty('bar')] public MyObject $bar; + /** + * @var ?MyObject $foobar + */ + #[JsonProperty('foobar')] + public ?MyObject $foobar; + /** * @param array{ * file: File, * foo: string, * bar: MyObject, + * foobar?: ?MyObject, * } $values */ public function __construct( @@ -39,5 +46,6 @@ public function __construct( $this->file = $values['file']; $this->foo = $values['foo']; $this->bar = $values['bar']; + $this->foobar = $values['foobar'] ?? null; } } diff --git a/seed/php-sdk/file-upload/src/Service/ServiceClient.php b/seed/php-sdk/file-upload/src/Service/ServiceClient.php index d7b9cc634e2..8984c6e0799 100644 --- a/seed/php-sdk/file-upload/src/Service/ServiceClient.php +++ b/seed/php-sdk/file-upload/src/Service/ServiceClient.php @@ -207,6 +207,13 @@ public function withContentType(WithContentTypeRequest $request, ?array $options value: $request->bar->toJson(), contentType: 'application/json', ); + if ($request->foobar != null) { + $body->add( + name: 'foobar', + value: $request->foobar->toJson(), + contentType: 'application/json', + ); + } try { $response = $this->client->sendRequest( new MultipartApiRequest( diff --git a/seed/python-sdk/alias-extends/src/seed/core/file.py b/seed/python-sdk/alias-extends/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/alias-extends/src/seed/core/file.py +++ b/seed/python-sdk/alias-extends/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/alias/src/seed/core/file.py b/seed/python-sdk/alias/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/alias/src/seed/core/file.py +++ b/seed/python-sdk/alias/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/any-auth/src/seed/core/file.py b/seed/python-sdk/any-auth/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/any-auth/src/seed/core/file.py +++ b/seed/python-sdk/any-auth/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/api-wide-base-path/src/seed/core/file.py b/seed/python-sdk/api-wide-base-path/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/api-wide-base-path/src/seed/core/file.py +++ b/seed/python-sdk/api-wide-base-path/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/audiences/src/seed/core/file.py b/seed/python-sdk/audiences/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/audiences/src/seed/core/file.py +++ b/seed/python-sdk/audiences/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/auth-environment-variables/src/seed/core/file.py b/seed/python-sdk/auth-environment-variables/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/auth-environment-variables/src/seed/core/file.py +++ b/seed/python-sdk/auth-environment-variables/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/basic-auth-environment-variables/src/seed/core/file.py b/seed/python-sdk/basic-auth-environment-variables/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/basic-auth-environment-variables/src/seed/core/file.py +++ b/seed/python-sdk/basic-auth-environment-variables/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/basic-auth/src/seed/core/file.py b/seed/python-sdk/basic-auth/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/basic-auth/src/seed/core/file.py +++ b/seed/python-sdk/basic-auth/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/bearer-token-environment-variable/src/seed/core/file.py b/seed/python-sdk/bearer-token-environment-variable/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/bearer-token-environment-variable/src/seed/core/file.py +++ b/seed/python-sdk/bearer-token-environment-variable/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/bytes/src/seed/core/file.py b/seed/python-sdk/bytes/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/bytes/src/seed/core/file.py +++ b/seed/python-sdk/bytes/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/circular-references-advanced/src/seed/core/file.py b/seed/python-sdk/circular-references-advanced/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/circular-references-advanced/src/seed/core/file.py +++ b/seed/python-sdk/circular-references-advanced/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/circular-references/src/seed/core/file.py b/seed/python-sdk/circular-references/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/circular-references/src/seed/core/file.py +++ b/seed/python-sdk/circular-references/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/cross-package-type-names/src/seed/core/file.py b/seed/python-sdk/cross-package-type-names/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/cross-package-type-names/src/seed/core/file.py +++ b/seed/python-sdk/cross-package-type-names/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/custom-auth/src/seed/core/file.py b/seed/python-sdk/custom-auth/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/custom-auth/src/seed/core/file.py +++ b/seed/python-sdk/custom-auth/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/enum/no-custom-config/src/seed/core/file.py b/seed/python-sdk/enum/no-custom-config/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/enum/no-custom-config/src/seed/core/file.py +++ b/seed/python-sdk/enum/no-custom-config/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/enum/real-enum-forward-compat/src/seed/core/file.py b/seed/python-sdk/enum/real-enum-forward-compat/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/enum/real-enum-forward-compat/src/seed/core/file.py +++ b/seed/python-sdk/enum/real-enum-forward-compat/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/enum/real-enum/src/seed/core/file.py b/seed/python-sdk/enum/real-enum/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/enum/real-enum/src/seed/core/file.py +++ b/seed/python-sdk/enum/real-enum/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/enum/strenum/src/seed/core/file.py b/seed/python-sdk/enum/strenum/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/enum/strenum/src/seed/core/file.py +++ b/seed/python-sdk/enum/strenum/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/error-property/src/seed/core/file.py b/seed/python-sdk/error-property/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/error-property/src/seed/core/file.py +++ b/seed/python-sdk/error-property/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/examples/client-filename/src/seed/core/file.py b/seed/python-sdk/examples/client-filename/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/examples/client-filename/src/seed/core/file.py +++ b/seed/python-sdk/examples/client-filename/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/examples/no-custom-config/src/seed/core/file.py b/seed/python-sdk/examples/no-custom-config/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/examples/no-custom-config/src/seed/core/file.py +++ b/seed/python-sdk/examples/no-custom-config/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/examples/readme/src/seed/core/file.py b/seed/python-sdk/examples/readme/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/examples/readme/src/seed/core/file.py +++ b/seed/python-sdk/examples/readme/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/exhaustive/aliases_with_validation/src/seed/core/file.py b/seed/python-sdk/exhaustive/aliases_with_validation/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/exhaustive/aliases_with_validation/src/seed/core/file.py +++ b/seed/python-sdk/exhaustive/aliases_with_validation/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/exhaustive/aliases_without_validation/src/seed/core/file.py b/seed/python-sdk/exhaustive/aliases_without_validation/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/exhaustive/aliases_without_validation/src/seed/core/file.py +++ b/seed/python-sdk/exhaustive/aliases_without_validation/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/exhaustive/deps_with_min_python_version/src/seed/core/file.py b/seed/python-sdk/exhaustive/deps_with_min_python_version/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/exhaustive/deps_with_min_python_version/src/seed/core/file.py +++ b/seed/python-sdk/exhaustive/deps_with_min_python_version/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/exhaustive/extra_dependencies/src/seed/core/file.py b/seed/python-sdk/exhaustive/extra_dependencies/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/exhaustive/extra_dependencies/src/seed/core/file.py +++ b/seed/python-sdk/exhaustive/extra_dependencies/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/exhaustive/extra_dev_dependencies/src/seed/core/file.py b/seed/python-sdk/exhaustive/extra_dev_dependencies/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/exhaustive/extra_dev_dependencies/src/seed/core/file.py +++ b/seed/python-sdk/exhaustive/extra_dev_dependencies/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/exhaustive/five-second-timeout/src/seed/core/file.py b/seed/python-sdk/exhaustive/five-second-timeout/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/exhaustive/five-second-timeout/src/seed/core/file.py +++ b/seed/python-sdk/exhaustive/five-second-timeout/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/exhaustive/follow_redirects_by_default/src/seed/core/file.py b/seed/python-sdk/exhaustive/follow_redirects_by_default/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/exhaustive/follow_redirects_by_default/src/seed/core/file.py +++ b/seed/python-sdk/exhaustive/follow_redirects_by_default/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/exhaustive/improved_imports/src/seed/core/file.py b/seed/python-sdk/exhaustive/improved_imports/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/exhaustive/improved_imports/src/seed/core/file.py +++ b/seed/python-sdk/exhaustive/improved_imports/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/exhaustive/infinite-timeout/src/seed/core/file.py b/seed/python-sdk/exhaustive/infinite-timeout/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/exhaustive/infinite-timeout/src/seed/core/file.py +++ b/seed/python-sdk/exhaustive/infinite-timeout/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/exhaustive/inline_request_params/src/seed/core/file.py b/seed/python-sdk/exhaustive/inline_request_params/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/exhaustive/inline_request_params/src/seed/core/file.py +++ b/seed/python-sdk/exhaustive/inline_request_params/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/exhaustive/no-custom-config/src/seed/core/file.py b/seed/python-sdk/exhaustive/no-custom-config/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/exhaustive/no-custom-config/src/seed/core/file.py +++ b/seed/python-sdk/exhaustive/no-custom-config/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/exhaustive/pydantic-extra-fields/src/seed/core/file.py b/seed/python-sdk/exhaustive/pydantic-extra-fields/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/exhaustive/pydantic-extra-fields/src/seed/core/file.py +++ b/seed/python-sdk/exhaustive/pydantic-extra-fields/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/exhaustive/pydantic-v1-with-utils/src/seed/core/file.py b/seed/python-sdk/exhaustive/pydantic-v1-with-utils/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/exhaustive/pydantic-v1-with-utils/src/seed/core/file.py +++ b/seed/python-sdk/exhaustive/pydantic-v1-with-utils/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/exhaustive/pydantic-v1-wrapped/src/seed/core/file.py b/seed/python-sdk/exhaustive/pydantic-v1-wrapped/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/exhaustive/pydantic-v1-wrapped/src/seed/core/file.py +++ b/seed/python-sdk/exhaustive/pydantic-v1-wrapped/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/exhaustive/pydantic-v1/src/seed/core/file.py b/seed/python-sdk/exhaustive/pydantic-v1/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/exhaustive/pydantic-v1/src/seed/core/file.py +++ b/seed/python-sdk/exhaustive/pydantic-v1/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/exhaustive/pyproject_extras/src/seed/core/file.py b/seed/python-sdk/exhaustive/pyproject_extras/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/exhaustive/pyproject_extras/src/seed/core/file.py +++ b/seed/python-sdk/exhaustive/pyproject_extras/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/exhaustive/skip-pydantic-validation/src/seed/core/file.py b/seed/python-sdk/exhaustive/skip-pydantic-validation/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/exhaustive/skip-pydantic-validation/src/seed/core/file.py +++ b/seed/python-sdk/exhaustive/skip-pydantic-validation/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/exhaustive/union-utils/src/seed/core/file.py b/seed/python-sdk/exhaustive/union-utils/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/exhaustive/union-utils/src/seed/core/file.py +++ b/seed/python-sdk/exhaustive/union-utils/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/extends/src/seed/core/file.py b/seed/python-sdk/extends/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/extends/src/seed/core/file.py +++ b/seed/python-sdk/extends/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/extra-properties/src/seed/core/file.py b/seed/python-sdk/extra-properties/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/extra-properties/src/seed/core/file.py +++ b/seed/python-sdk/extra-properties/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/file-download/default-chunk-size/src/seed/core/file.py b/seed/python-sdk/file-download/default-chunk-size/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/file-download/default-chunk-size/src/seed/core/file.py +++ b/seed/python-sdk/file-download/default-chunk-size/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/file-download/no-custom-config/src/seed/core/file.py b/seed/python-sdk/file-download/no-custom-config/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/file-download/no-custom-config/src/seed/core/file.py +++ b/seed/python-sdk/file-download/no-custom-config/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/file-upload/.mock/definition/service.yml b/seed/python-sdk/file-upload/.mock/definition/service.yml index 5ae95021c2d..956e6ba73ba 100644 --- a/seed/python-sdk/file-upload/.mock/definition/service.yml +++ b/seed/python-sdk/file-upload/.mock/definition/service.yml @@ -64,6 +64,9 @@ service: bar: type: MyObject content-type: application/json + foobar: + type: optional + content-type: application/json types: Id: string diff --git a/seed/python-sdk/file-upload/snippet-templates.json b/seed/python-sdk/file-upload/snippet-templates.json index a66d6282bc8..dfa4a6c3e91 100644 --- a/seed/python-sdk/file-upload/snippet-templates.json +++ b/seed/python-sdk/file-upload/snippet-templates.json @@ -987,6 +987,36 @@ "inputDelimiter": ",\n\t\t", "type": "generic" } + }, + { + "type": "template", + "value": { + "imports": [ + "from seed.service import MyObject" + ], + "isOptional": true, + "templateString": "foobar=MyObject(\n\t\t$FERN_INPUT\n\t)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "foo=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": "foobar.foo", + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t\t", + "type": "generic" + } } ], "inputDelimiter": ",\n\t", @@ -1072,6 +1102,36 @@ "inputDelimiter": ",\n\t\t", "type": "generic" } + }, + { + "type": "template", + "value": { + "imports": [ + "from seed.service import MyObject" + ], + "isOptional": true, + "templateString": "foobar=MyObject(\n\t\t$FERN_INPUT\n\t)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "foo=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": "foobar.foo", + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t\t", + "type": "generic" + } } ], "inputDelimiter": ",\n\t", diff --git a/seed/python-sdk/file-upload/src/seed/core/file.py b/seed/python-sdk/file-upload/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/file-upload/src/seed/core/file.py +++ b/seed/python-sdk/file-upload/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/file-upload/src/seed/service/client.py b/seed/python-sdk/file-upload/src/seed/service/client.py index ac1158b85b1..dfb1e621c68 100644 --- a/seed/python-sdk/file-upload/src/seed/service/client.py +++ b/seed/python-sdk/file-upload/src/seed/service/client.py @@ -197,7 +197,13 @@ def just_file_with_query_params( raise ApiError(status_code=_response.status_code, body=_response_json) def with_content_type( - self, *, file: core.File, foo: str, bar: MyObject, request_options: typing.Optional[RequestOptions] = None + self, + *, + file: core.File, + foo: str, + bar: MyObject, + foobar: typing.Optional[MyObject] = OMIT, + request_options: typing.Optional[RequestOptions] = None, ) -> None: """ Parameters @@ -209,6 +215,8 @@ def with_content_type( bar : MyObject + foobar : typing.Optional[MyObject] + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -223,8 +231,13 @@ def with_content_type( "foo": foo, }, files={ - "file": core.with_content_type(file=file, content_type="application/octet-stream"), + "file": core.with_content_type(file=file, default_content_type="application/octet-stream"), "bar": (None, json.dumps(jsonable_encoder(bar)), "application/json"), + **( + {"foobar": (None, json.dumps(jsonable_encoder(foobar)), "application/json")} + if foobar is not OMIT + else {} + ), }, request_options=request_options, omit=OMIT, @@ -418,7 +431,13 @@ async def just_file_with_query_params( raise ApiError(status_code=_response.status_code, body=_response_json) async def with_content_type( - self, *, file: core.File, foo: str, bar: MyObject, request_options: typing.Optional[RequestOptions] = None + self, + *, + file: core.File, + foo: str, + bar: MyObject, + foobar: typing.Optional[MyObject] = OMIT, + request_options: typing.Optional[RequestOptions] = None, ) -> None: """ Parameters @@ -430,6 +449,8 @@ async def with_content_type( bar : MyObject + foobar : typing.Optional[MyObject] + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -444,8 +465,13 @@ async def with_content_type( "foo": foo, }, files={ - "file": core.with_content_type(file=file, content_type="application/octet-stream"), + "file": core.with_content_type(file=file, default_content_type="application/octet-stream"), "bar": (None, json.dumps(jsonable_encoder(bar)), "application/json"), + **( + {"foobar": (None, json.dumps(jsonable_encoder(foobar)), "application/json")} + if foobar is not OMIT + else {} + ), }, request_options=request_options, omit=OMIT, diff --git a/seed/python-sdk/folders/src/seed/core/file.py b/seed/python-sdk/folders/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/folders/src/seed/core/file.py +++ b/seed/python-sdk/folders/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/grpc-proto-exhaustive/src/seed/core/file.py b/seed/python-sdk/grpc-proto-exhaustive/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/grpc-proto-exhaustive/src/seed/core/file.py +++ b/seed/python-sdk/grpc-proto-exhaustive/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/grpc-proto/src/seed/core/file.py b/seed/python-sdk/grpc-proto/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/grpc-proto/src/seed/core/file.py +++ b/seed/python-sdk/grpc-proto/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/idempotency-headers/src/seed/core/file.py b/seed/python-sdk/idempotency-headers/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/idempotency-headers/src/seed/core/file.py +++ b/seed/python-sdk/idempotency-headers/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/imdb/src/seed/core/file.py b/seed/python-sdk/imdb/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/imdb/src/seed/core/file.py +++ b/seed/python-sdk/imdb/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/license/src/seed/core/file.py b/seed/python-sdk/license/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/license/src/seed/core/file.py +++ b/seed/python-sdk/license/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/literal/no-custom-config/src/seed/core/file.py b/seed/python-sdk/literal/no-custom-config/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/literal/no-custom-config/src/seed/core/file.py +++ b/seed/python-sdk/literal/no-custom-config/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/literal/use_typeddict_requests/src/seed/core/file.py b/seed/python-sdk/literal/use_typeddict_requests/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/literal/use_typeddict_requests/src/seed/core/file.py +++ b/seed/python-sdk/literal/use_typeddict_requests/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/mixed-case/src/seed/core/file.py b/seed/python-sdk/mixed-case/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/mixed-case/src/seed/core/file.py +++ b/seed/python-sdk/mixed-case/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/mixed-file-directory/src/seed/core/file.py b/seed/python-sdk/mixed-file-directory/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/mixed-file-directory/src/seed/core/file.py +++ b/seed/python-sdk/mixed-file-directory/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/multi-line-docs/src/seed/core/file.py b/seed/python-sdk/multi-line-docs/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/multi-line-docs/src/seed/core/file.py +++ b/seed/python-sdk/multi-line-docs/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/multi-url-environment-no-default/src/seed/core/file.py b/seed/python-sdk/multi-url-environment-no-default/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/multi-url-environment-no-default/src/seed/core/file.py +++ b/seed/python-sdk/multi-url-environment-no-default/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/multi-url-environment/src/seed/core/file.py b/seed/python-sdk/multi-url-environment/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/multi-url-environment/src/seed/core/file.py +++ b/seed/python-sdk/multi-url-environment/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/no-environment/src/seed/core/file.py b/seed/python-sdk/no-environment/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/no-environment/src/seed/core/file.py +++ b/seed/python-sdk/no-environment/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/oauth-client-credentials-default/src/seed/core/file.py b/seed/python-sdk/oauth-client-credentials-default/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/oauth-client-credentials-default/src/seed/core/file.py +++ b/seed/python-sdk/oauth-client-credentials-default/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/oauth-client-credentials-environment-variables/src/seed/core/file.py b/seed/python-sdk/oauth-client-credentials-environment-variables/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/oauth-client-credentials-environment-variables/src/seed/core/file.py +++ b/seed/python-sdk/oauth-client-credentials-environment-variables/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/oauth-client-credentials-nested-root/src/seed/core/file.py b/seed/python-sdk/oauth-client-credentials-nested-root/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/oauth-client-credentials-nested-root/src/seed/core/file.py +++ b/seed/python-sdk/oauth-client-credentials-nested-root/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/oauth-client-credentials/src/seed/core/file.py b/seed/python-sdk/oauth-client-credentials/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/oauth-client-credentials/src/seed/core/file.py +++ b/seed/python-sdk/oauth-client-credentials/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/object/src/seed/core/file.py b/seed/python-sdk/object/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/object/src/seed/core/file.py +++ b/seed/python-sdk/object/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/objects-with-imports/src/seed/core/file.py b/seed/python-sdk/objects-with-imports/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/objects-with-imports/src/seed/core/file.py +++ b/seed/python-sdk/objects-with-imports/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/optional/src/seed/core/file.py b/seed/python-sdk/optional/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/optional/src/seed/core/file.py +++ b/seed/python-sdk/optional/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/package-yml/src/seed/core/file.py b/seed/python-sdk/package-yml/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/package-yml/src/seed/core/file.py +++ b/seed/python-sdk/package-yml/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/pagination/src/seed/core/file.py b/seed/python-sdk/pagination/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/pagination/src/seed/core/file.py +++ b/seed/python-sdk/pagination/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/plain-text/src/seed/core/file.py b/seed/python-sdk/plain-text/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/plain-text/src/seed/core/file.py +++ b/seed/python-sdk/plain-text/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/query-parameters/src/seed/core/file.py b/seed/python-sdk/query-parameters/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/query-parameters/src/seed/core/file.py +++ b/seed/python-sdk/query-parameters/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/reserved-keywords/src/seed/core/file.py b/seed/python-sdk/reserved-keywords/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/reserved-keywords/src/seed/core/file.py +++ b/seed/python-sdk/reserved-keywords/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/response-property/src/seed/core/file.py b/seed/python-sdk/response-property/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/response-property/src/seed/core/file.py +++ b/seed/python-sdk/response-property/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/server-sent-event-examples/src/seed/core/file.py b/seed/python-sdk/server-sent-event-examples/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/server-sent-event-examples/src/seed/core/file.py +++ b/seed/python-sdk/server-sent-event-examples/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/server-sent-events/src/seed/core/file.py b/seed/python-sdk/server-sent-events/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/server-sent-events/src/seed/core/file.py +++ b/seed/python-sdk/server-sent-events/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/simple-fhir/src/seed/core/file.py b/seed/python-sdk/simple-fhir/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/simple-fhir/src/seed/core/file.py +++ b/seed/python-sdk/simple-fhir/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/single-url-environment-default/src/seed/core/file.py b/seed/python-sdk/single-url-environment-default/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/single-url-environment-default/src/seed/core/file.py +++ b/seed/python-sdk/single-url-environment-default/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/single-url-environment-no-default/src/seed/core/file.py b/seed/python-sdk/single-url-environment-no-default/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/single-url-environment-no-default/src/seed/core/file.py +++ b/seed/python-sdk/single-url-environment-no-default/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/streaming-parameter/src/seed/core/file.py b/seed/python-sdk/streaming-parameter/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/streaming-parameter/src/seed/core/file.py +++ b/seed/python-sdk/streaming-parameter/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/streaming/no-custom-config/src/seed/core/file.py b/seed/python-sdk/streaming/no-custom-config/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/streaming/no-custom-config/src/seed/core/file.py +++ b/seed/python-sdk/streaming/no-custom-config/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/streaming/skip-pydantic-validation/src/seed/core/file.py b/seed/python-sdk/streaming/skip-pydantic-validation/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/streaming/skip-pydantic-validation/src/seed/core/file.py +++ b/seed/python-sdk/streaming/skip-pydantic-validation/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/trace/src/seed/core/file.py b/seed/python-sdk/trace/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/trace/src/seed/core/file.py +++ b/seed/python-sdk/trace/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/undiscriminated-unions/src/seed/core/file.py b/seed/python-sdk/undiscriminated-unions/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/undiscriminated-unions/src/seed/core/file.py +++ b/seed/python-sdk/undiscriminated-unions/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/unions/no-custom-config/src/seed/core/file.py b/seed/python-sdk/unions/no-custom-config/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/unions/no-custom-config/src/seed/core/file.py +++ b/seed/python-sdk/unions/no-custom-config/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/unions/union-naming-v1/src/seed/core/file.py b/seed/python-sdk/unions/union-naming-v1/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/unions/union-naming-v1/src/seed/core/file.py +++ b/seed/python-sdk/unions/union-naming-v1/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/unions/union-utils/src/seed/core/file.py b/seed/python-sdk/unions/union-utils/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/unions/union-utils/src/seed/core/file.py +++ b/seed/python-sdk/unions/union-utils/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/unknown/src/seed/core/file.py b/seed/python-sdk/unknown/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/unknown/src/seed/core/file.py +++ b/seed/python-sdk/unknown/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/validation/no-custom-config/src/seed/core/file.py b/seed/python-sdk/validation/no-custom-config/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/validation/no-custom-config/src/seed/core/file.py +++ b/seed/python-sdk/validation/no-custom-config/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/validation/with-defaults/src/seed/core/file.py b/seed/python-sdk/validation/with-defaults/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/validation/with-defaults/src/seed/core/file.py +++ b/seed/python-sdk/validation/with-defaults/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/variables/src/seed/core/file.py b/seed/python-sdk/variables/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/variables/src/seed/core/file.py +++ b/seed/python-sdk/variables/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/version-no-default/src/seed/core/file.py b/seed/python-sdk/version-no-default/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/version-no-default/src/seed/core/file.py +++ b/seed/python-sdk/version-no-default/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/version/src/seed/core/file.py b/seed/python-sdk/version/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/version/src/seed/core/file.py +++ b/seed/python-sdk/version/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/python-sdk/websocket/src/seed/core/file.py b/seed/python-sdk/websocket/src/seed/core/file.py index b4cbba30f73..44b0d27c089 100644 --- a/seed/python-sdk/websocket/src/seed/core/file.py +++ b/seed/python-sdk/websocket/src/seed/core/file.py @@ -43,20 +43,25 @@ def convert_file_dict_to_httpx_tuples( return httpx_tuples -def with_content_type(*, file: File, content_type: str) -> File: - """ """ +def with_content_type(*, file: File, default_content_type: str) -> File: + """ + This function resolves to the file's content type, if provided, and defaults + to the default_content_type value if not. + """ if isinstance(file, tuple): if len(file) == 2: filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore - return (filename, content, content_type) + return (filename, content, default_content_type) elif len(file) == 3: - filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore - return (filename, content, content_type) + filename, content, file_content_type = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type) elif len(file) == 4: - filename, content, _, headers = cast( # type: ignore + filename, content, file_content_type, headers = cast( # type: ignore Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file ) - return (filename, content, content_type, headers) + out_content_type = file_content_type or default_content_type + return (filename, content, out_content_type, headers) else: raise ValueError(f"Unexpected tuple length: {len(file)}") - return (None, file, content_type) + return (None, file, default_content_type) diff --git a/seed/ruby-sdk/file-upload/.mock/definition/service.yml b/seed/ruby-sdk/file-upload/.mock/definition/service.yml index 5ae95021c2d..956e6ba73ba 100644 --- a/seed/ruby-sdk/file-upload/.mock/definition/service.yml +++ b/seed/ruby-sdk/file-upload/.mock/definition/service.yml @@ -64,6 +64,9 @@ service: bar: type: MyObject content-type: application/json + foobar: + type: optional + content-type: application/json types: Id: string diff --git a/seed/ruby-sdk/file-upload/lib/fern_file_upload/service/client.rb b/seed/ruby-sdk/file-upload/lib/fern_file_upload/service/client.rb index a89110427c8..a880149b973 100644 --- a/seed/ruby-sdk/file-upload/lib/fern_file_upload/service/client.rb +++ b/seed/ruby-sdk/file-upload/lib/fern_file_upload/service/client.rb @@ -126,9 +126,11 @@ def just_file_with_query_params(integer:, list_of_strings:, file:, maybe_string: # @param foo [String] # @param bar [Hash] Request of type SeedFileUploadClient::Service::MyObject, as a Hash # * :foo (String) + # @param foobar [Hash] Request of type SeedFileUploadClient::Service::MyObject, as a Hash + # * :foo (String) # @param request_options [SeedFileUploadClient::RequestOptions] # @return [Void] - def with_content_type(file:, foo:, bar:, request_options: nil) + def with_content_type(file:, foo:, bar:, foobar: nil, request_options: nil) @request_client.conn.post do |req| req.options.timeout = request_options.timeout_in_seconds unless request_options&.timeout_in_seconds.nil? req.headers = { @@ -143,7 +145,8 @@ def with_content_type(file:, foo:, bar:, request_options: nil) **(request_options&.additional_body_parameters || {}), file: SeedFileUploadClient::FileUtilities.as_faraday_multipart(file_like: file), foo: foo, - bar: bar + bar: bar, + foobar: foobar }.compact req.url "#{@request_client.get_url(request_options: request_options)}/with-content-type" end @@ -275,9 +278,11 @@ def just_file_with_query_params(integer:, list_of_strings:, file:, maybe_string: # @param foo [String] # @param bar [Hash] Request of type SeedFileUploadClient::Service::MyObject, as a Hash # * :foo (String) + # @param foobar [Hash] Request of type SeedFileUploadClient::Service::MyObject, as a Hash + # * :foo (String) # @param request_options [SeedFileUploadClient::RequestOptions] # @return [Void] - def with_content_type(file:, foo:, bar:, request_options: nil) + def with_content_type(file:, foo:, bar:, foobar: nil, request_options: nil) Async do @request_client.conn.post do |req| req.options.timeout = request_options.timeout_in_seconds unless request_options&.timeout_in_seconds.nil? @@ -293,7 +298,8 @@ def with_content_type(file:, foo:, bar:, request_options: nil) **(request_options&.additional_body_parameters || {}), file: SeedFileUploadClient::FileUtilities.as_faraday_multipart(file_like: file), foo: foo, - bar: bar + bar: bar, + foobar: foobar }.compact req.url "#{@request_client.get_url(request_options: request_options)}/with-content-type" end diff --git a/seed/ts-sdk/file-upload/no-custom-config/.mock/definition/service.yml b/seed/ts-sdk/file-upload/no-custom-config/.mock/definition/service.yml index 5ae95021c2d..956e6ba73ba 100644 --- a/seed/ts-sdk/file-upload/no-custom-config/.mock/definition/service.yml +++ b/seed/ts-sdk/file-upload/no-custom-config/.mock/definition/service.yml @@ -64,6 +64,9 @@ service: bar: type: MyObject content-type: application/json + foobar: + type: optional + content-type: application/json types: Id: string diff --git a/seed/ts-sdk/file-upload/no-custom-config/snippet-templates.json b/seed/ts-sdk/file-upload/no-custom-config/snippet-templates.json index 27b5722a735..a940deec171 100644 --- a/seed/ts-sdk/file-upload/no-custom-config/snippet-templates.json +++ b/seed/ts-sdk/file-upload/no-custom-config/snippet-templates.json @@ -690,6 +690,34 @@ "type": "generic" }, "type": "template" + }, + { + "value": { + "imports": [], + "templateString": "foobar: {\n\t\t\t$FERN_INPUT\n\t\t}", + "isOptional": true, + "inputDelimiter": ",\n\t\t\t", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "foo: $FERN_INPUT", + "isOptional": true, + "templateInputs": [ + { + "location": "BODY", + "path": "foobar.foo", + "type": "payload" + } + ], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "template" } ], "type": "generic" diff --git a/seed/ts-sdk/file-upload/no-custom-config/src/api/resources/service/client/Client.ts b/seed/ts-sdk/file-upload/no-custom-config/src/api/resources/service/client/Client.ts index e7658d6e2e8..855d8bafe79 100644 --- a/seed/ts-sdk/file-upload/no-custom-config/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/file-upload/no-custom-config/src/api/resources/service/client/Client.ts @@ -292,6 +292,10 @@ export class Service { await _request.appendFile("file", file); await _request.append("foo", request.foo); await _request.append("bar", JSON.stringify(request.bar)); + if (request.foobar != null) { + await _request.append("foobar", JSON.stringify(request.foobar)); + } + const _maybeEncodedRequest = await _request.getRequest(); const _response = await core.fetcher({ url: urlJoin(await core.Supplier.get(this._options.environment), "/with-content-type"), diff --git a/seed/ts-sdk/file-upload/no-custom-config/src/api/resources/service/client/requests/WithContentTypeRequest.ts b/seed/ts-sdk/file-upload/no-custom-config/src/api/resources/service/client/requests/WithContentTypeRequest.ts index 55d1d09484d..7eb1395cca4 100644 --- a/seed/ts-sdk/file-upload/no-custom-config/src/api/resources/service/client/requests/WithContentTypeRequest.ts +++ b/seed/ts-sdk/file-upload/no-custom-config/src/api/resources/service/client/requests/WithContentTypeRequest.ts @@ -7,4 +7,5 @@ import * as SeedFileUpload from "../../../../index"; export interface WithContentTypeRequest { foo: string; bar: SeedFileUpload.MyObject; + foobar?: SeedFileUpload.MyObject; } diff --git a/seed/ts-sdk/file-upload/wrap-file-properties/.mock/definition/service.yml b/seed/ts-sdk/file-upload/wrap-file-properties/.mock/definition/service.yml index 5ae95021c2d..956e6ba73ba 100644 --- a/seed/ts-sdk/file-upload/wrap-file-properties/.mock/definition/service.yml +++ b/seed/ts-sdk/file-upload/wrap-file-properties/.mock/definition/service.yml @@ -64,6 +64,9 @@ service: bar: type: MyObject content-type: application/json + foobar: + type: optional + content-type: application/json types: Id: string diff --git a/seed/ts-sdk/file-upload/wrap-file-properties/snippet-templates.json b/seed/ts-sdk/file-upload/wrap-file-properties/snippet-templates.json index da21c3e0902..01e08b1ad81 100644 --- a/seed/ts-sdk/file-upload/wrap-file-properties/snippet-templates.json +++ b/seed/ts-sdk/file-upload/wrap-file-properties/snippet-templates.json @@ -709,6 +709,34 @@ "type": "generic" }, "type": "template" + }, + { + "value": { + "imports": [], + "templateString": "foobar: {\n\t\t\t$FERN_INPUT\n\t\t}", + "isOptional": true, + "inputDelimiter": ",\n\t\t\t", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "foo: $FERN_INPUT", + "isOptional": true, + "templateInputs": [ + { + "location": "BODY", + "path": "foobar.foo", + "type": "payload" + } + ], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "template" } ], "type": "generic" diff --git a/seed/ts-sdk/file-upload/wrap-file-properties/src/api/resources/service/client/Client.ts b/seed/ts-sdk/file-upload/wrap-file-properties/src/api/resources/service/client/Client.ts index c9e5012983d..bbdf4c22fb8 100644 --- a/seed/ts-sdk/file-upload/wrap-file-properties/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/file-upload/wrap-file-properties/src/api/resources/service/client/Client.ts @@ -278,6 +278,10 @@ export class Service { await _request.appendFile("file", request.file); await _request.append("foo", request.foo); await _request.append("bar", JSON.stringify(request.bar)); + if (request.foobar != null) { + await _request.append("foobar", JSON.stringify(request.foobar)); + } + const _maybeEncodedRequest = await _request.getRequest(); const _response = await core.fetcher({ url: urlJoin(await core.Supplier.get(this._options.environment), "/with-content-type"), diff --git a/seed/ts-sdk/file-upload/wrap-file-properties/src/api/resources/service/client/requests/WithContentTypeRequest.ts b/seed/ts-sdk/file-upload/wrap-file-properties/src/api/resources/service/client/requests/WithContentTypeRequest.ts index bcdc38081f7..dde9943af84 100644 --- a/seed/ts-sdk/file-upload/wrap-file-properties/src/api/resources/service/client/requests/WithContentTypeRequest.ts +++ b/seed/ts-sdk/file-upload/wrap-file-properties/src/api/resources/service/client/requests/WithContentTypeRequest.ts @@ -9,4 +9,5 @@ export interface WithContentTypeRequest { file: File | fs.ReadStream | Blob; foo: string; bar: SeedFileUpload.MyObject; + foobar?: SeedFileUpload.MyObject; } diff --git a/test-definitions/fern/apis/file-upload/definition/service.yml b/test-definitions/fern/apis/file-upload/definition/service.yml index 5ae95021c2d..956e6ba73ba 100644 --- a/test-definitions/fern/apis/file-upload/definition/service.yml +++ b/test-definitions/fern/apis/file-upload/definition/service.yml @@ -64,6 +64,9 @@ service: bar: type: MyObject content-type: application/json + foobar: + type: optional + content-type: application/json types: Id: string