diff --git a/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-nullable-named-request-types.swift b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-nullable-named-request-types.swift new file mode 100644 index 00000000000..d59f3395ae0 --- /dev/null +++ b/generators/swift/sdk/src/generators/client/util/__test__/snapshots/formatted-endpoint-paths/java-nullable-named-request-types.swift @@ -0,0 +1,3 @@ +// service_ +"/postWithNullableNamedRequestBodyType/\(id)" +"/postWithNonNullableNamedRequestBodyType/\(id)" \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/java-nullable-named-request-types/type__NullableObject.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/java-nullable-named-request-types/type__NullableObject.json new file mode 100644 index 00000000000..43bfb9babe9 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/java-nullable-named-request-types/type__NullableObject.json @@ -0,0 +1,37 @@ +{ + "type": "object", + "properties": { + "id": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "age": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/java-nullable-named-request-types/type__ResponseBody.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/java-nullable-named-request-types/type__ResponseBody.json new file mode 100644 index 00000000000..0da93297fa9 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/java-nullable-named-request-types/type__ResponseBody.json @@ -0,0 +1,17 @@ +{ + "type": "object", + "properties": { + "success": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/java-nullable-named-request-types.json b/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/java-nullable-named-request-types.json new file mode 100644 index 00000000000..9bf54290c82 --- /dev/null +++ b/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/java-nullable-named-request-types.json @@ -0,0 +1,533 @@ +{ + "version": "1.0.0", + "types": { + "type_:NullableObject": { + "type": "object", + "declaration": { + "name": { + "originalName": "NullableObject", + "camelCase": { + "unsafeName": "nullableObject", + "safeName": "nullableObject" + }, + "snakeCase": { + "unsafeName": "nullable_object", + "safeName": "nullable_object" + }, + "screamingSnakeCase": { + "unsafeName": "NULLABLE_OBJECT", + "safeName": "NULLABLE_OBJECT" + }, + "pascalCase": { + "unsafeName": "NullableObject", + "safeName": "NullableObject" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "wireValue": "id" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + } + }, + { + "name": { + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "wireValue": "name" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + } + }, + { + "name": { + "name": { + "originalName": "age", + "camelCase": { + "unsafeName": "age", + "safeName": "age" + }, + "snakeCase": { + "unsafeName": "age", + "safeName": "age" + }, + "screamingSnakeCase": { + "unsafeName": "AGE", + "safeName": "AGE" + }, + "pascalCase": { + "unsafeName": "Age", + "safeName": "Age" + } + }, + "wireValue": "age" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "INTEGER" + } + } + } + ], + "additionalProperties": false + }, + "type_:ResponseBody": { + "type": "object", + "declaration": { + "name": { + "originalName": "ResponseBody", + "camelCase": { + "unsafeName": "responseBody", + "safeName": "responseBody" + }, + "snakeCase": { + "unsafeName": "response_body", + "safeName": "response_body" + }, + "screamingSnakeCase": { + "unsafeName": "RESPONSE_BODY", + "safeName": "RESPONSE_BODY" + }, + "pascalCase": { + "unsafeName": "ResponseBody", + "safeName": "ResponseBody" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "name": { + "originalName": "success", + "camelCase": { + "unsafeName": "success", + "safeName": "success" + }, + "snakeCase": { + "unsafeName": "success", + "safeName": "success" + }, + "screamingSnakeCase": { + "unsafeName": "SUCCESS", + "safeName": "SUCCESS" + }, + "pascalCase": { + "unsafeName": "Success", + "safeName": "Success" + } + }, + "wireValue": "success" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "BOOLEAN" + } + } + } + ], + "additionalProperties": false + } + }, + "headers": [], + "endpoints": { + "endpoint_.postWithNullableNamedRequestBodyType": { + "auth": null, + "declaration": { + "name": { + "originalName": "postWithNullableNamedRequestBodyType", + "camelCase": { + "unsafeName": "postWithNullableNamedRequestBodyType", + "safeName": "postWithNullableNamedRequestBodyType" + }, + "snakeCase": { + "unsafeName": "post_with_nullable_named_request_body_type", + "safeName": "post_with_nullable_named_request_body_type" + }, + "screamingSnakeCase": { + "unsafeName": "POST_WITH_NULLABLE_NAMED_REQUEST_BODY_TYPE", + "safeName": "POST_WITH_NULLABLE_NAMED_REQUEST_BODY_TYPE" + }, + "pascalCase": { + "unsafeName": "PostWithNullableNamedRequestBodyType", + "safeName": "PostWithNullableNamedRequestBodyType" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "POST", + "path": "/postWithNullableNamedRequestBodyType/{id}" + }, + "request": { + "type": "inlined", + "declaration": { + "name": { + "originalName": "PostWithNullableNamedRequestBodyTypeRequest", + "camelCase": { + "unsafeName": "postWithNullableNamedRequestBodyTypeRequest", + "safeName": "postWithNullableNamedRequestBodyTypeRequest" + }, + "snakeCase": { + "unsafeName": "post_with_nullable_named_request_body_type_request", + "safeName": "post_with_nullable_named_request_body_type_request" + }, + "screamingSnakeCase": { + "unsafeName": "POST_WITH_NULLABLE_NAMED_REQUEST_BODY_TYPE_REQUEST", + "safeName": "POST_WITH_NULLABLE_NAMED_REQUEST_BODY_TYPE_REQUEST" + }, + "pascalCase": { + "unsafeName": "PostWithNullableNamedRequestBodyTypeRequest", + "safeName": "PostWithNullableNamedRequestBodyTypeRequest" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "pathParameters": [ + { + "name": { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "wireValue": "id" + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + } + } + ], + "queryParameters": [], + "headers": [], + "body": { + "type": "referenced", + "bodyKey": { + "originalName": "body", + "camelCase": { + "unsafeName": "body", + "safeName": "body" + }, + "snakeCase": { + "unsafeName": "body", + "safeName": "body" + }, + "screamingSnakeCase": { + "unsafeName": "BODY", + "safeName": "BODY" + }, + "pascalCase": { + "unsafeName": "Body", + "safeName": "Body" + } + }, + "bodyType": { + "type": "typeReference", + "value": { + "type": "optional", + "value": { + "type": "named", + "value": "type_:NullableObject" + } + } + } + }, + "metadata": { + "includePathParameters": true, + "onlyPathParameters": false + } + }, + "response": { + "type": "json" + }, + "examples": null + }, + "endpoint_.postWithNonNullableNamedRequestBodyType": { + "auth": null, + "declaration": { + "name": { + "originalName": "postWithNonNullableNamedRequestBodyType", + "camelCase": { + "unsafeName": "postWithNonNullableNamedRequestBodyType", + "safeName": "postWithNonNullableNamedRequestBodyType" + }, + "snakeCase": { + "unsafeName": "post_with_non_nullable_named_request_body_type", + "safeName": "post_with_non_nullable_named_request_body_type" + }, + "screamingSnakeCase": { + "unsafeName": "POST_WITH_NON_NULLABLE_NAMED_REQUEST_BODY_TYPE", + "safeName": "POST_WITH_NON_NULLABLE_NAMED_REQUEST_BODY_TYPE" + }, + "pascalCase": { + "unsafeName": "PostWithNonNullableNamedRequestBodyType", + "safeName": "PostWithNonNullableNamedRequestBodyType" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "POST", + "path": "/postWithNonNullableNamedRequestBodyType/{id}" + }, + "request": { + "type": "inlined", + "declaration": { + "name": { + "originalName": "NonNullableObject", + "camelCase": { + "unsafeName": "nonNullableObject", + "safeName": "nonNullableObject" + }, + "snakeCase": { + "unsafeName": "non_nullable_object", + "safeName": "non_nullable_object" + }, + "screamingSnakeCase": { + "unsafeName": "NON_NULLABLE_OBJECT", + "safeName": "NON_NULLABLE_OBJECT" + }, + "pascalCase": { + "unsafeName": "NonNullableObject", + "safeName": "NonNullableObject" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "pathParameters": [ + { + "name": { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "wireValue": "id" + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + } + } + ], + "queryParameters": [], + "headers": [], + "body": { + "type": "properties", + "value": [ + { + "name": { + "name": { + "originalName": "nonNullableObjectId", + "camelCase": { + "unsafeName": "nonNullableObjectID", + "safeName": "nonNullableObjectID" + }, + "snakeCase": { + "unsafeName": "non_nullable_object_id", + "safeName": "non_nullable_object_id" + }, + "screamingSnakeCase": { + "unsafeName": "NON_NULLABLE_OBJECT_ID", + "safeName": "NON_NULLABLE_OBJECT_ID" + }, + "pascalCase": { + "unsafeName": "NonNullableObjectID", + "safeName": "NonNullableObjectID" + } + }, + "wireValue": "id" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + } + }, + { + "name": { + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "wireValue": "name" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + } + }, + { + "name": { + "name": { + "originalName": "age", + "camelCase": { + "unsafeName": "age", + "safeName": "age" + }, + "snakeCase": { + "unsafeName": "age", + "safeName": "age" + }, + "screamingSnakeCase": { + "unsafeName": "AGE", + "safeName": "AGE" + }, + "pascalCase": { + "unsafeName": "Age", + "safeName": "Age" + } + }, + "wireValue": "age" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "INTEGER" + } + } + } + ] + }, + "metadata": { + "includePathParameters": true, + "onlyPathParameters": false + } + }, + "response": { + "type": "json" + }, + "examples": null + } + }, + "pathParameters": [], + "environments": null, + "generatorConfig": null +} \ No newline at end of file diff --git a/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/java-nullable-named-request-types.json b/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/java-nullable-named-request-types.json new file mode 100644 index 00000000000..708117a5350 --- /dev/null +++ b/packages/cli/generation/ir-generator-tests/src/ir/__test__/test-definitions/java-nullable-named-request-types.json @@ -0,0 +1,2406 @@ +{ + "selfHosted": false, + "fdrApiDefinitionId": null, + "apiVersion": null, + "apiName": { + "originalName": "api", + "camelCase": { + "unsafeName": "api", + "safeName": "api" + }, + "snakeCase": { + "unsafeName": "api", + "safeName": "api" + }, + "screamingSnakeCase": { + "unsafeName": "API", + "safeName": "API" + }, + "pascalCase": { + "unsafeName": "API", + "safeName": "API" + } + }, + "apiDisplayName": "Java Nullable Named Request Types API", + "apiDocs": null, + "auth": { + "requirement": "ALL", + "schemes": [], + "docs": null + }, + "headers": [], + "idempotencyHeaders": [], + "types": { + "type_:NullableObject": { + "inline": null, + "name": { + "name": { + "originalName": "NullableObject", + "camelCase": { + "unsafeName": "nullableObject", + "safeName": "nullableObject" + }, + "snakeCase": { + "unsafeName": "nullable_object", + "safeName": "nullable_object" + }, + "screamingSnakeCase": { + "unsafeName": "NULLABLE_OBJECT", + "safeName": "NULLABLE_OBJECT" + }, + "pascalCase": { + "unsafeName": "NullableObject", + "safeName": "NullableObject" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:NullableObject" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "wireValue": "id" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "wireValue": "name" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "age", + "camelCase": { + "unsafeName": "age", + "safeName": "age" + }, + "snakeCase": { + "unsafeName": "age", + "safeName": "age" + }, + "screamingSnakeCase": { + "unsafeName": "AGE", + "safeName": "AGE" + }, + "pascalCase": { + "unsafeName": "Age", + "safeName": "Age" + } + }, + "wireValue": "age" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "INTEGER", + "v2": { + "type": "integer", + "default": null, + "validation": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + }, + "type_:ResponseBody": { + "inline": null, + "name": { + "name": { + "originalName": "ResponseBody", + "camelCase": { + "unsafeName": "responseBody", + "safeName": "responseBody" + }, + "snakeCase": { + "unsafeName": "response_body", + "safeName": "response_body" + }, + "screamingSnakeCase": { + "unsafeName": "RESPONSE_BODY", + "safeName": "RESPONSE_BODY" + }, + "pascalCase": { + "unsafeName": "ResponseBody", + "safeName": "ResponseBody" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:ResponseBody" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "success", + "camelCase": { + "unsafeName": "success", + "safeName": "success" + }, + "snakeCase": { + "unsafeName": "success", + "safeName": "success" + }, + "screamingSnakeCase": { + "unsafeName": "SUCCESS", + "safeName": "SUCCESS" + }, + "pascalCase": { + "unsafeName": "Success", + "safeName": "Success" + } + }, + "wireValue": "success" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "BOOLEAN", + "v2": { + "type": "boolean", + "default": null + } + } + } + } + }, + "propertyAccess": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": { + "json": {}, + "proto": null + }, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "v2Examples": null, + "availability": null, + "docs": null + } + }, + "errors": {}, + "services": { + "service_": { + "availability": null, + "name": { + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "displayName": null, + "basePath": { + "head": "", + "parts": [] + }, + "headers": [], + "pathParameters": [], + "encoding": { + "json": {}, + "proto": null + }, + "transport": { + "type": "http" + }, + "endpoints": [ + { + "id": "endpoint_.postWithNullableNamedRequestBodyType", + "name": { + "originalName": "postWithNullableNamedRequestBodyType", + "camelCase": { + "unsafeName": "postWithNullableNamedRequestBodyType", + "safeName": "postWithNullableNamedRequestBodyType" + }, + "snakeCase": { + "unsafeName": "post_with_nullable_named_request_body_type", + "safeName": "post_with_nullable_named_request_body_type" + }, + "screamingSnakeCase": { + "unsafeName": "POST_WITH_NULLABLE_NAMED_REQUEST_BODY_TYPE", + "safeName": "POST_WITH_NULLABLE_NAMED_REQUEST_BODY_TYPE" + }, + "pascalCase": { + "unsafeName": "PostWithNullableNamedRequestBodyType", + "safeName": "PostWithNullableNamedRequestBodyType" + } + }, + "displayName": null, + "auth": false, + "idempotent": false, + "baseUrl": null, + "v2BaseUrls": null, + "method": "POST", + "basePath": null, + "path": { + "head": "/postWithNullableNamedRequestBodyType/", + "parts": [ + { + "pathParameter": "id", + "tail": "" + } + ] + }, + "fullPath": { + "head": "postWithNullableNamedRequestBodyType/", + "parts": [ + { + "pathParameter": "id", + "tail": "" + } + ] + }, + "pathParameters": [ + { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "location": "ENDPOINT", + "variable": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "docs": null + } + ], + "allPathParameters": [ + { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "location": "ENDPOINT", + "variable": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "docs": null + } + ], + "queryParameters": [], + "headers": [], + "requestBody": { + "type": "reference", + "requestBodyType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": { + "originalName": "NullableObject", + "camelCase": { + "unsafeName": "nullableObject", + "safeName": "nullableObject" + }, + "snakeCase": { + "unsafeName": "nullable_object", + "safeName": "nullable_object" + }, + "screamingSnakeCase": { + "unsafeName": "NULLABLE_OBJECT", + "safeName": "NULLABLE_OBJECT" + }, + "pascalCase": { + "unsafeName": "NullableObject", + "safeName": "NullableObject" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:NullableObject", + "default": null, + "inline": null + } + } + }, + "docs": null, + "contentType": "application/json", + "v2Examples": null + }, + "v2RequestBodies": null, + "sdkRequest": { + "shape": { + "type": "wrapper", + "wrapperName": { + "originalName": "PostWithNullableNamedRequestBodyTypeRequest", + "camelCase": { + "unsafeName": "postWithNullableNamedRequestBodyTypeRequest", + "safeName": "postWithNullableNamedRequestBodyTypeRequest" + }, + "snakeCase": { + "unsafeName": "post_with_nullable_named_request_body_type_request", + "safeName": "post_with_nullable_named_request_body_type_request" + }, + "screamingSnakeCase": { + "unsafeName": "POST_WITH_NULLABLE_NAMED_REQUEST_BODY_TYPE_REQUEST", + "safeName": "POST_WITH_NULLABLE_NAMED_REQUEST_BODY_TYPE_REQUEST" + }, + "pascalCase": { + "unsafeName": "PostWithNullableNamedRequestBodyTypeRequest", + "safeName": "PostWithNullableNamedRequestBodyTypeRequest" + } + }, + "bodyKey": { + "originalName": "body", + "camelCase": { + "unsafeName": "body", + "safeName": "body" + }, + "snakeCase": { + "unsafeName": "body", + "safeName": "body" + }, + "screamingSnakeCase": { + "unsafeName": "BODY", + "safeName": "BODY" + }, + "pascalCase": { + "unsafeName": "Body", + "safeName": "Body" + } + }, + "includePathParameters": true, + "onlyPathParameters": false + }, + "requestParameterName": { + "originalName": "request", + "camelCase": { + "unsafeName": "request", + "safeName": "request" + }, + "snakeCase": { + "unsafeName": "request", + "safeName": "request" + }, + "screamingSnakeCase": { + "unsafeName": "REQUEST", + "safeName": "REQUEST" + }, + "pascalCase": { + "unsafeName": "Request", + "safeName": "Request" + } + }, + "streamParameter": null + }, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": { + "originalName": "ResponseBody", + "camelCase": { + "unsafeName": "responseBody", + "safeName": "responseBody" + }, + "snakeCase": { + "unsafeName": "response_body", + "safeName": "response_body" + }, + "screamingSnakeCase": { + "unsafeName": "RESPONSE_BODY", + "safeName": "RESPONSE_BODY" + }, + "pascalCase": { + "unsafeName": "ResponseBody", + "safeName": "ResponseBody" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:ResponseBody", + "default": null, + "inline": null + }, + "docs": "Successful response", + "v2Examples": null + } + }, + "status-code": 200 + }, + "v2Responses": null, + "errors": [], + "userSpecifiedExamples": [], + "autogeneratedExamples": [ + { + "example": { + "id": "32ad5d1a", + "url": "/postWithNullableNamedRequestBodyType/id", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [ + { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + } + } + ], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": { + "type": "reference", + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": null, + "valueType": { + "_type": "named", + "name": { + "originalName": "NullableObject", + "camelCase": { + "unsafeName": "nullableObject", + "safeName": "nullableObject" + }, + "snakeCase": { + "unsafeName": "nullable_object", + "safeName": "nullable_object" + }, + "screamingSnakeCase": { + "unsafeName": "NULLABLE_OBJECT", + "safeName": "NULLABLE_OBJECT" + }, + "pascalCase": { + "unsafeName": "NullableObject", + "safeName": "NullableObject" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:NullableObject", + "default": null, + "inline": null + } + } + } + }, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "success", + "camelCase": { + "unsafeName": "success", + "safeName": "success" + }, + "snakeCase": { + "unsafeName": "success", + "safeName": "success" + }, + "screamingSnakeCase": { + "unsafeName": "SUCCESS", + "safeName": "SUCCESS" + }, + "pascalCase": { + "unsafeName": "Success", + "safeName": "Success" + } + }, + "wireValue": "success" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "ResponseBody", + "camelCase": { + "unsafeName": "responseBody", + "safeName": "responseBody" + }, + "snakeCase": { + "unsafeName": "response_body", + "safeName": "response_body" + }, + "screamingSnakeCase": { + "unsafeName": "RESPONSE_BODY", + "safeName": "RESPONSE_BODY" + }, + "pascalCase": { + "unsafeName": "ResponseBody", + "safeName": "ResponseBody" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:ResponseBody" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "boolean", + "boolean": true + } + }, + "jsonExample": true + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "BOOLEAN", + "v2": { + "type": "boolean", + "default": null + } + } + } + } + }, + "jsonExample": true + }, + "propertyAccess": null + } + ] + }, + "typeName": { + "name": { + "originalName": "ResponseBody", + "camelCase": { + "unsafeName": "responseBody", + "safeName": "responseBody" + }, + "snakeCase": { + "unsafeName": "response_body", + "safeName": "response_body" + }, + "screamingSnakeCase": { + "unsafeName": "RESPONSE_BODY", + "safeName": "RESPONSE_BODY" + }, + "pascalCase": { + "unsafeName": "ResponseBody", + "safeName": "ResponseBody" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:ResponseBody" + } + }, + "jsonExample": { + "success": true + } + } + } + }, + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "v2Examples": null, + "source": null, + "audiences": null, + "availability": null, + "docs": null + }, + { + "id": "endpoint_.postWithNonNullableNamedRequestBodyType", + "name": { + "originalName": "postWithNonNullableNamedRequestBodyType", + "camelCase": { + "unsafeName": "postWithNonNullableNamedRequestBodyType", + "safeName": "postWithNonNullableNamedRequestBodyType" + }, + "snakeCase": { + "unsafeName": "post_with_non_nullable_named_request_body_type", + "safeName": "post_with_non_nullable_named_request_body_type" + }, + "screamingSnakeCase": { + "unsafeName": "POST_WITH_NON_NULLABLE_NAMED_REQUEST_BODY_TYPE", + "safeName": "POST_WITH_NON_NULLABLE_NAMED_REQUEST_BODY_TYPE" + }, + "pascalCase": { + "unsafeName": "PostWithNonNullableNamedRequestBodyType", + "safeName": "PostWithNonNullableNamedRequestBodyType" + } + }, + "displayName": null, + "auth": false, + "idempotent": false, + "baseUrl": null, + "v2BaseUrls": null, + "method": "POST", + "basePath": null, + "path": { + "head": "/postWithNonNullableNamedRequestBodyType/", + "parts": [ + { + "pathParameter": "id", + "tail": "" + } + ] + }, + "fullPath": { + "head": "postWithNonNullableNamedRequestBodyType/", + "parts": [ + { + "pathParameter": "id", + "tail": "" + } + ] + }, + "pathParameters": [ + { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "location": "ENDPOINT", + "variable": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "docs": null + } + ], + "allPathParameters": [ + { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "location": "ENDPOINT", + "variable": null, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "docs": null + } + ], + "queryParameters": [], + "headers": [], + "requestBody": { + "type": "inlinedRequestBody", + "name": { + "originalName": "NonNullableObject", + "camelCase": { + "unsafeName": "nonNullableObject", + "safeName": "nonNullableObject" + }, + "snakeCase": { + "unsafeName": "non_nullable_object", + "safeName": "non_nullable_object" + }, + "screamingSnakeCase": { + "unsafeName": "NON_NULLABLE_OBJECT", + "safeName": "NON_NULLABLE_OBJECT" + }, + "pascalCase": { + "unsafeName": "NonNullableObject", + "safeName": "NonNullableObject" + } + }, + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "nonNullableObjectId", + "camelCase": { + "unsafeName": "nonNullableObjectID", + "safeName": "nonNullableObjectID" + }, + "snakeCase": { + "unsafeName": "non_nullable_object_id", + "safeName": "non_nullable_object_id" + }, + "screamingSnakeCase": { + "unsafeName": "NON_NULLABLE_OBJECT_ID", + "safeName": "NON_NULLABLE_OBJECT_ID" + }, + "pascalCase": { + "unsafeName": "NonNullableObjectID", + "safeName": "NonNullableObjectID" + } + }, + "wireValue": "id" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "propertyAccess": null, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "wireValue": "name" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "propertyAccess": null, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "age", + "camelCase": { + "unsafeName": "age", + "safeName": "age" + }, + "snakeCase": { + "unsafeName": "age", + "safeName": "age" + }, + "screamingSnakeCase": { + "unsafeName": "AGE", + "safeName": "AGE" + }, + "pascalCase": { + "unsafeName": "Age", + "safeName": "Age" + } + }, + "wireValue": "age" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "INTEGER", + "v2": { + "type": "integer", + "default": null, + "validation": null + } + } + } + } + }, + "v2Examples": { + "userSpecifiedExamples": {}, + "autogeneratedExamples": {} + }, + "propertyAccess": null, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [], + "docs": null, + "v2Examples": null, + "contentType": "application/json" + }, + "v2RequestBodies": null, + "sdkRequest": { + "shape": { + "type": "wrapper", + "wrapperName": { + "originalName": "NonNullableObject", + "camelCase": { + "unsafeName": "nonNullableObject", + "safeName": "nonNullableObject" + }, + "snakeCase": { + "unsafeName": "non_nullable_object", + "safeName": "non_nullable_object" + }, + "screamingSnakeCase": { + "unsafeName": "NON_NULLABLE_OBJECT", + "safeName": "NON_NULLABLE_OBJECT" + }, + "pascalCase": { + "unsafeName": "NonNullableObject", + "safeName": "NonNullableObject" + } + }, + "bodyKey": { + "originalName": "body", + "camelCase": { + "unsafeName": "body", + "safeName": "body" + }, + "snakeCase": { + "unsafeName": "body", + "safeName": "body" + }, + "screamingSnakeCase": { + "unsafeName": "BODY", + "safeName": "BODY" + }, + "pascalCase": { + "unsafeName": "Body", + "safeName": "Body" + } + }, + "includePathParameters": true, + "onlyPathParameters": false + }, + "requestParameterName": { + "originalName": "request", + "camelCase": { + "unsafeName": "request", + "safeName": "request" + }, + "snakeCase": { + "unsafeName": "request", + "safeName": "request" + }, + "screamingSnakeCase": { + "unsafeName": "REQUEST", + "safeName": "REQUEST" + }, + "pascalCase": { + "unsafeName": "Request", + "safeName": "Request" + } + }, + "streamParameter": null + }, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": { + "originalName": "ResponseBody", + "camelCase": { + "unsafeName": "responseBody", + "safeName": "responseBody" + }, + "snakeCase": { + "unsafeName": "response_body", + "safeName": "response_body" + }, + "screamingSnakeCase": { + "unsafeName": "RESPONSE_BODY", + "safeName": "RESPONSE_BODY" + }, + "pascalCase": { + "unsafeName": "ResponseBody", + "safeName": "ResponseBody" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:ResponseBody", + "default": null, + "inline": null + }, + "docs": "Successful response", + "v2Examples": null + } + }, + "status-code": 200 + }, + "v2Responses": null, + "errors": [], + "userSpecifiedExamples": [ + { + "example": { + "id": "8a8806b8", + "name": null, + "url": "/postWithNonNullableNamedRequestBodyType/id", + "rootPathParameters": [], + "endpointPathParameters": [ + { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + } + } + ], + "servicePathParameters": [], + "endpointHeaders": [], + "serviceHeaders": [], + "queryParameters": [], + "request": { + "type": "inlinedRequestBody", + "properties": [], + "jsonExample": {} + }, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:ResponseBody", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "ResponseBody", + "camelCase": { + "unsafeName": "responseBody", + "safeName": "responseBody" + }, + "snakeCase": { + "unsafeName": "response_body", + "safeName": "response_body" + }, + "screamingSnakeCase": { + "unsafeName": "RESPONSE_BODY", + "safeName": "RESPONSE_BODY" + }, + "pascalCase": { + "unsafeName": "ResponseBody", + "safeName": "ResponseBody" + } + }, + "displayName": null + }, + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "success", + "camelCase": { + "unsafeName": "success", + "safeName": "success" + }, + "snakeCase": { + "unsafeName": "success", + "safeName": "success" + }, + "screamingSnakeCase": { + "unsafeName": "SUCCESS", + "safeName": "SUCCESS" + }, + "pascalCase": { + "unsafeName": "Success", + "safeName": "Success" + } + }, + "wireValue": "success" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "boolean", + "boolean": true + } + }, + "jsonExample": true + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "BOOLEAN", + "v2": { + "type": "boolean", + "default": null + } + } + } + } + }, + "jsonExample": true + }, + "originalTypeDeclaration": { + "typeId": "type_:ResponseBody", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "ResponseBody", + "camelCase": { + "unsafeName": "responseBody", + "safeName": "responseBody" + }, + "snakeCase": { + "unsafeName": "response_body", + "safeName": "response_body" + }, + "screamingSnakeCase": { + "unsafeName": "RESPONSE_BODY", + "safeName": "RESPONSE_BODY" + }, + "pascalCase": { + "unsafeName": "ResponseBody", + "safeName": "ResponseBody" + } + }, + "displayName": null + }, + "propertyAccess": null + } + ] + } + }, + "jsonExample": { + "success": true + } + } + } + }, + "docs": null + }, + "codeSamples": null + } + ], + "autogeneratedExamples": [ + { + "example": { + "id": "78a1f04e", + "url": "/postWithNonNullableNamedRequestBodyType/id", + "name": null, + "endpointHeaders": [], + "endpointPathParameters": [ + { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "id" + } + } + }, + "jsonExample": "id" + } + } + ], + "queryParameters": [], + "servicePathParameters": [], + "serviceHeaders": [], + "rootPathParameters": [], + "request": { + "type": "inlinedRequestBody", + "properties": [ + { + "name": { + "name": { + "originalName": "nonNullableObjectId", + "camelCase": { + "unsafeName": "nonNullableObjectID", + "safeName": "nonNullableObjectID" + }, + "snakeCase": { + "unsafeName": "non_nullable_object_id", + "safeName": "non_nullable_object_id" + }, + "screamingSnakeCase": { + "unsafeName": "NON_NULLABLE_OBJECT_ID", + "safeName": "NON_NULLABLE_OBJECT_ID" + }, + "pascalCase": { + "unsafeName": "NonNullableObjectID", + "safeName": "NonNullableObjectID" + } + }, + "wireValue": "id" + }, + "originalTypeDeclaration": null, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": null, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + } + } + }, + { + "name": { + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "wireValue": "name" + }, + "originalTypeDeclaration": null, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": null, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + } + } + }, + { + "name": { + "name": { + "originalName": "age", + "camelCase": { + "unsafeName": "age", + "safeName": "age" + }, + "snakeCase": { + "unsafeName": "age", + "safeName": "age" + }, + "screamingSnakeCase": { + "unsafeName": "AGE", + "safeName": "AGE" + }, + "pascalCase": { + "unsafeName": "Age", + "safeName": "Age" + } + }, + "wireValue": "age" + }, + "originalTypeDeclaration": null, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": null, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "INTEGER", + "v2": { + "type": "integer", + "default": null, + "validation": null + } + } + } + } + } + } + } + ], + "jsonExample": {} + }, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "success", + "camelCase": { + "unsafeName": "success", + "safeName": "success" + }, + "snakeCase": { + "unsafeName": "success", + "safeName": "success" + }, + "screamingSnakeCase": { + "unsafeName": "SUCCESS", + "safeName": "SUCCESS" + }, + "pascalCase": { + "unsafeName": "Success", + "safeName": "Success" + } + }, + "wireValue": "success" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "ResponseBody", + "camelCase": { + "unsafeName": "responseBody", + "safeName": "responseBody" + }, + "snakeCase": { + "unsafeName": "response_body", + "safeName": "response_body" + }, + "screamingSnakeCase": { + "unsafeName": "RESPONSE_BODY", + "safeName": "RESPONSE_BODY" + }, + "pascalCase": { + "unsafeName": "ResponseBody", + "safeName": "ResponseBody" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:ResponseBody" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "boolean", + "boolean": true + } + }, + "jsonExample": true + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "BOOLEAN", + "v2": { + "type": "boolean", + "default": null + } + } + } + } + }, + "jsonExample": true + }, + "propertyAccess": null + } + ] + }, + "typeName": { + "name": { + "originalName": "ResponseBody", + "camelCase": { + "unsafeName": "responseBody", + "safeName": "responseBody" + }, + "snakeCase": { + "unsafeName": "response_body", + "safeName": "response_body" + }, + "screamingSnakeCase": { + "unsafeName": "RESPONSE_BODY", + "safeName": "RESPONSE_BODY" + }, + "pascalCase": { + "unsafeName": "ResponseBody", + "safeName": "ResponseBody" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "displayName": null, + "typeId": "type_:ResponseBody" + } + }, + "jsonExample": { + "success": true + } + } + } + }, + "docs": null + } + } + ], + "pagination": null, + "transport": null, + "v2Examples": null, + "source": null, + "audiences": null, + "availability": null, + "docs": null + } + ], + "audiences": null + } + }, + "constants": { + "errorInstanceIdKey": { + "name": { + "originalName": "errorInstanceId", + "camelCase": { + "unsafeName": "errorInstanceID", + "safeName": "errorInstanceID" + }, + "snakeCase": { + "unsafeName": "error_instance_id", + "safeName": "error_instance_id" + }, + "screamingSnakeCase": { + "unsafeName": "ERROR_INSTANCE_ID", + "safeName": "ERROR_INSTANCE_ID" + }, + "pascalCase": { + "unsafeName": "ErrorInstanceID", + "safeName": "ErrorInstanceID" + } + }, + "wireValue": "errorInstanceId" + } + }, + "environments": null, + "errorDiscriminationStrategy": { + "type": "statusCode" + }, + "basePath": null, + "pathParameters": [], + "variables": [], + "serviceTypeReferenceInfo": { + "typesReferencedOnlyByService": { + "service_": [ + "type_:NullableObject", + "type_:ResponseBody" + ] + }, + "sharedTypes": [] + }, + "webhookGroups": {}, + "websocketChannels": {}, + "readmeConfig": null, + "sourceConfig": null, + "publishConfig": null, + "dynamic": { + "version": "1.0.0", + "types": { + "type_:NullableObject": { + "type": "object", + "declaration": { + "name": { + "originalName": "NullableObject", + "camelCase": { + "unsafeName": "nullableObject", + "safeName": "nullableObject" + }, + "snakeCase": { + "unsafeName": "nullable_object", + "safeName": "nullable_object" + }, + "screamingSnakeCase": { + "unsafeName": "NULLABLE_OBJECT", + "safeName": "NULLABLE_OBJECT" + }, + "pascalCase": { + "unsafeName": "NullableObject", + "safeName": "NullableObject" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "wireValue": "id" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + } + }, + { + "name": { + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "wireValue": "name" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + } + }, + { + "name": { + "name": { + "originalName": "age", + "camelCase": { + "unsafeName": "age", + "safeName": "age" + }, + "snakeCase": { + "unsafeName": "age", + "safeName": "age" + }, + "screamingSnakeCase": { + "unsafeName": "AGE", + "safeName": "AGE" + }, + "pascalCase": { + "unsafeName": "Age", + "safeName": "Age" + } + }, + "wireValue": "age" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "INTEGER" + } + } + } + ], + "additionalProperties": false + }, + "type_:ResponseBody": { + "type": "object", + "declaration": { + "name": { + "originalName": "ResponseBody", + "camelCase": { + "unsafeName": "responseBody", + "safeName": "responseBody" + }, + "snakeCase": { + "unsafeName": "response_body", + "safeName": "response_body" + }, + "screamingSnakeCase": { + "unsafeName": "RESPONSE_BODY", + "safeName": "RESPONSE_BODY" + }, + "pascalCase": { + "unsafeName": "ResponseBody", + "safeName": "ResponseBody" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "properties": [ + { + "name": { + "name": { + "originalName": "success", + "camelCase": { + "unsafeName": "success", + "safeName": "success" + }, + "snakeCase": { + "unsafeName": "success", + "safeName": "success" + }, + "screamingSnakeCase": { + "unsafeName": "SUCCESS", + "safeName": "SUCCESS" + }, + "pascalCase": { + "unsafeName": "Success", + "safeName": "Success" + } + }, + "wireValue": "success" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "BOOLEAN" + } + } + } + ], + "additionalProperties": false + } + }, + "headers": [], + "endpoints": { + "endpoint_.postWithNullableNamedRequestBodyType": { + "auth": null, + "declaration": { + "name": { + "originalName": "postWithNullableNamedRequestBodyType", + "camelCase": { + "unsafeName": "postWithNullableNamedRequestBodyType", + "safeName": "postWithNullableNamedRequestBodyType" + }, + "snakeCase": { + "unsafeName": "post_with_nullable_named_request_body_type", + "safeName": "post_with_nullable_named_request_body_type" + }, + "screamingSnakeCase": { + "unsafeName": "POST_WITH_NULLABLE_NAMED_REQUEST_BODY_TYPE", + "safeName": "POST_WITH_NULLABLE_NAMED_REQUEST_BODY_TYPE" + }, + "pascalCase": { + "unsafeName": "PostWithNullableNamedRequestBodyType", + "safeName": "PostWithNullableNamedRequestBodyType" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "POST", + "path": "/postWithNullableNamedRequestBodyType/{id}" + }, + "request": { + "type": "inlined", + "declaration": { + "name": { + "originalName": "PostWithNullableNamedRequestBodyTypeRequest", + "camelCase": { + "unsafeName": "postWithNullableNamedRequestBodyTypeRequest", + "safeName": "postWithNullableNamedRequestBodyTypeRequest" + }, + "snakeCase": { + "unsafeName": "post_with_nullable_named_request_body_type_request", + "safeName": "post_with_nullable_named_request_body_type_request" + }, + "screamingSnakeCase": { + "unsafeName": "POST_WITH_NULLABLE_NAMED_REQUEST_BODY_TYPE_REQUEST", + "safeName": "POST_WITH_NULLABLE_NAMED_REQUEST_BODY_TYPE_REQUEST" + }, + "pascalCase": { + "unsafeName": "PostWithNullableNamedRequestBodyTypeRequest", + "safeName": "PostWithNullableNamedRequestBodyTypeRequest" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "pathParameters": [ + { + "name": { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "wireValue": "id" + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + } + } + ], + "queryParameters": [], + "headers": [], + "body": { + "type": "referenced", + "bodyKey": { + "originalName": "body", + "camelCase": { + "unsafeName": "body", + "safeName": "body" + }, + "snakeCase": { + "unsafeName": "body", + "safeName": "body" + }, + "screamingSnakeCase": { + "unsafeName": "BODY", + "safeName": "BODY" + }, + "pascalCase": { + "unsafeName": "Body", + "safeName": "Body" + } + }, + "bodyType": { + "type": "typeReference", + "value": { + "type": "optional", + "value": { + "type": "named", + "value": "type_:NullableObject" + } + } + } + }, + "metadata": { + "includePathParameters": true, + "onlyPathParameters": false + } + }, + "response": { + "type": "json" + }, + "examples": null + }, + "endpoint_.postWithNonNullableNamedRequestBodyType": { + "auth": null, + "declaration": { + "name": { + "originalName": "postWithNonNullableNamedRequestBodyType", + "camelCase": { + "unsafeName": "postWithNonNullableNamedRequestBodyType", + "safeName": "postWithNonNullableNamedRequestBodyType" + }, + "snakeCase": { + "unsafeName": "post_with_non_nullable_named_request_body_type", + "safeName": "post_with_non_nullable_named_request_body_type" + }, + "screamingSnakeCase": { + "unsafeName": "POST_WITH_NON_NULLABLE_NAMED_REQUEST_BODY_TYPE", + "safeName": "POST_WITH_NON_NULLABLE_NAMED_REQUEST_BODY_TYPE" + }, + "pascalCase": { + "unsafeName": "PostWithNonNullableNamedRequestBodyType", + "safeName": "PostWithNonNullableNamedRequestBodyType" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "location": { + "method": "POST", + "path": "/postWithNonNullableNamedRequestBodyType/{id}" + }, + "request": { + "type": "inlined", + "declaration": { + "name": { + "originalName": "NonNullableObject", + "camelCase": { + "unsafeName": "nonNullableObject", + "safeName": "nonNullableObject" + }, + "snakeCase": { + "unsafeName": "non_nullable_object", + "safeName": "non_nullable_object" + }, + "screamingSnakeCase": { + "unsafeName": "NON_NULLABLE_OBJECT", + "safeName": "NON_NULLABLE_OBJECT" + }, + "pascalCase": { + "unsafeName": "NonNullableObject", + "safeName": "NonNullableObject" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + } + }, + "pathParameters": [ + { + "name": { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "wireValue": "id" + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + } + } + ], + "queryParameters": [], + "headers": [], + "body": { + "type": "properties", + "value": [ + { + "name": { + "name": { + "originalName": "nonNullableObjectId", + "camelCase": { + "unsafeName": "nonNullableObjectID", + "safeName": "nonNullableObjectID" + }, + "snakeCase": { + "unsafeName": "non_nullable_object_id", + "safeName": "non_nullable_object_id" + }, + "screamingSnakeCase": { + "unsafeName": "NON_NULLABLE_OBJECT_ID", + "safeName": "NON_NULLABLE_OBJECT_ID" + }, + "pascalCase": { + "unsafeName": "NonNullableObjectID", + "safeName": "NonNullableObjectID" + } + }, + "wireValue": "id" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + } + }, + { + "name": { + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "wireValue": "name" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "STRING" + } + } + }, + { + "name": { + "name": { + "originalName": "age", + "camelCase": { + "unsafeName": "age", + "safeName": "age" + }, + "snakeCase": { + "unsafeName": "age", + "safeName": "age" + }, + "screamingSnakeCase": { + "unsafeName": "AGE", + "safeName": "AGE" + }, + "pascalCase": { + "unsafeName": "Age", + "safeName": "Age" + } + }, + "wireValue": "age" + }, + "typeReference": { + "type": "optional", + "value": { + "type": "primitive", + "value": "INTEGER" + } + } + } + ] + }, + "metadata": { + "includePathParameters": true, + "onlyPathParameters": false + } + }, + "response": { + "type": "json" + }, + "examples": null + } + }, + "pathParameters": [], + "environments": null, + "generatorConfig": null + }, + "audiences": null, + "subpackages": {}, + "rootPackage": { + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "websocket": null, + "service": "service_", + "types": [ + "type_:NullableObject", + "type_:ResponseBody" + ], + "errors": [], + "subpackages": [], + "webhooks": null, + "navigationConfig": null, + "hasEndpointsInTree": true, + "docs": null + }, + "sdkConfig": { + "isAuthMandatory": false, + "hasStreamingEndpoints": false, + "hasPaginatedEndpoints": false, + "hasFileDownloadEndpoints": false, + "platformHeaders": { + "language": "X-Fern-Language", + "sdkName": "X-Fern-SDK-Name", + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null + } + } +} \ No newline at end of file diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/fdr/java-nullable-named-request-types.json b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/fdr/java-nullable-named-request-types.json new file mode 100644 index 00000000000..a181ac0ba26 --- /dev/null +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/fdr/java-nullable-named-request-types.json @@ -0,0 +1,305 @@ +{ + "types": { + "type_:NullableObject": { + "name": "NullableObject", + "shape": { + "type": "object", + "extends": [], + "properties": [ + { + "key": "id", + "valueType": { + "type": "optional", + "itemType": { + "type": "primitive", + "value": { + "type": "string" + } + } + } + }, + { + "key": "name", + "valueType": { + "type": "optional", + "itemType": { + "type": "primitive", + "value": { + "type": "string" + } + } + } + }, + { + "key": "age", + "valueType": { + "type": "optional", + "itemType": { + "type": "primitive", + "value": { + "type": "integer" + } + } + } + } + ] + } + }, + "type_:ResponseBody": { + "name": "ResponseBody", + "shape": { + "type": "object", + "extends": [], + "properties": [ + { + "key": "success", + "valueType": { + "type": "optional", + "itemType": { + "type": "primitive", + "value": { + "type": "boolean" + } + } + } + } + ] + } + } + }, + "subpackages": {}, + "rootPackage": { + "endpoints": [ + { + "auth": false, + "method": "POST", + "id": "postWithNullableNamedRequestBodyType", + "originalEndpointId": "endpoint_.postWithNullableNamedRequestBodyType", + "name": "Post With Nullable Named Request Body Type", + "path": { + "pathParameters": [ + { + "key": "id", + "type": { + "type": "primitive", + "value": { + "type": "string" + } + } + } + ], + "parts": [ + { + "type": "literal", + "value": "" + }, + { + "type": "literal", + "value": "/postWithNullableNamedRequestBodyType/" + }, + { + "type": "pathParameter", + "value": "id" + }, + { + "type": "literal", + "value": "" + } + ] + }, + "queryParameters": [], + "headers": [], + "request": { + "type": { + "type": "json", + "contentType": "application/json", + "shape": { + "type": "reference", + "value": { + "type": "optional", + "itemType": { + "type": "id", + "value": "type_:NullableObject" + } + } + } + } + }, + "requestsV2": {}, + "response": { + "type": { + "type": "reference", + "value": { + "type": "id", + "value": "type_:ResponseBody" + } + }, + "statusCode": 200, + "description": "Successful response" + }, + "responsesV2": {}, + "errorsV2": [], + "examples": [ + { + "path": "/postWithNullableNamedRequestBodyType/id", + "pathParameters": { + "id": "id" + }, + "queryParameters": {}, + "headers": {}, + "requestBodyV3": { + "type": "json" + }, + "responseStatusCode": 200, + "responseBody": { + "success": true + }, + "responseBodyV3": { + "type": "json", + "value": { + "success": true + } + }, + "codeSamples": [] + } + ] + }, + { + "auth": false, + "method": "POST", + "id": "postWithNonNullableNamedRequestBodyType", + "originalEndpointId": "endpoint_.postWithNonNullableNamedRequestBodyType", + "name": "Post With Non Nullable Named Request Body Type", + "path": { + "pathParameters": [ + { + "key": "id", + "type": { + "type": "primitive", + "value": { + "type": "string" + } + } + } + ], + "parts": [ + { + "type": "literal", + "value": "" + }, + { + "type": "literal", + "value": "/postWithNonNullableNamedRequestBodyType/" + }, + { + "type": "pathParameter", + "value": "id" + }, + { + "type": "literal", + "value": "" + } + ] + }, + "queryParameters": [], + "headers": [], + "request": { + "type": { + "type": "json", + "contentType": "application/json", + "shape": { + "type": "object", + "extends": [], + "properties": [ + { + "key": "id", + "valueType": { + "type": "optional", + "itemType": { + "type": "primitive", + "value": { + "type": "string" + } + } + } + }, + { + "key": "name", + "valueType": { + "type": "optional", + "itemType": { + "type": "primitive", + "value": { + "type": "string" + } + } + } + }, + { + "key": "age", + "valueType": { + "type": "optional", + "itemType": { + "type": "primitive", + "value": { + "type": "integer" + } + } + } + } + ] + } + } + }, + "requestsV2": {}, + "response": { + "type": { + "type": "reference", + "value": { + "type": "id", + "value": "type_:ResponseBody" + } + }, + "statusCode": 200, + "description": "Successful response" + }, + "responsesV2": {}, + "errorsV2": [], + "examples": [ + { + "path": "/postWithNonNullableNamedRequestBodyType/id", + "pathParameters": { + "id": "id" + }, + "queryParameters": {}, + "headers": {}, + "requestBody": {}, + "requestBodyV3": { + "type": "json", + "value": {} + }, + "responseStatusCode": 200, + "responseBody": { + "success": true + }, + "responseBodyV3": { + "type": "json", + "value": { + "success": true + } + } + } + ] + } + ], + "webhooks": [], + "websockets": [], + "types": [ + "type_:NullableObject", + "type_:ResponseBody" + ], + "subpackages": [] + }, + "snippetsConfiguration": {}, + "globalHeaders": [] +} \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/.github/workflows/ci.yml b/seed/java-sdk/java-nullable-named-request-types/custom-config/.github/workflows/ci.yml new file mode 100644 index 00000000000..7bffd9de06d --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Compile + run: ./gradlew compileJava + + test: + needs: [ compile ] + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Test + run: ./gradlew test + publish: + needs: [ compile, test ] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Publish to maven + run: | + ./gradlew publish + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_PUBLISH_REGISTRY_URL: "" diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/.gitignore b/seed/java-sdk/java-nullable-named-request-types/custom-config/.gitignore new file mode 100644 index 00000000000..d4199abc2cd --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/.gitignore @@ -0,0 +1,24 @@ +*.class +.project +.gradle +? +.classpath +.checkstyle +.settings +.node +build + +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Eclipse/IntelliJ APT +generated_src/ +generated_testSrc/ +generated/ + +bin +build \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/README.md b/seed/java-sdk/java-nullable-named-request-types/custom-config/README.md new file mode 100644 index 00000000000..a09670a3616 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/README.md @@ -0,0 +1,198 @@ +# Seed Java Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FJava) +[![Maven Central](https://img.shields.io/maven-central/v/com.fern/java-nullable-named-request-types)](https://central.sonatype.com/artifact/com.fern/java-nullable-named-request-types) + +The Seed Java library provides convenient access to the Seed APIs from Java. + +## Installation + +### Gradle + +Add the dependency in your `build.gradle` file: + +```groovy +dependencies { + implementation 'com.fern:java-nullable-named-request-types' +} +``` + +### Maven + +Add the dependency in your `pom.xml` file: + +```xml + + com.fern + java-nullable-named-request-types + 0.0.1 + +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```java +package com.example.usage; + +import com.seed.api.Foo; +import com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest; +import com.seed.api.types.NullableObject; + +public class Example { + public static void main(String[] args) { + Foo client = Foo + .builder() + .build(); + + client.postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest + .builder() + .id("id") + .body( + NullableObject + .builder() + .id("id") + .name("name") + .age(1) + .build() + ) + .build() + ); + } +} +``` + +## Base Url + +You can set a custom base URL when constructing the client. + +```java +import com.seed.api.Foo; + +Foo client = Foo + .builder() + .url("https://example.com") + .build(); +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), an API exception will be thrown. + +```java +import com.seed.api.core.SeedApiApiException; + +try { + client.postWithNullableNamedRequestBodyType(...); +} catch (SeedApiApiException e) { + // Do something with the API exception... +} +``` + +## Advanced + +### Custom Client + +This SDK is built to work with any instance of `OkHttpClient`. By default, if no client is provided, the SDK will construct one. +However, you can pass your own client like so: + +```java +import com.seed.api.Foo; +import okhttp3.OkHttpClient; + +OkHttpClient customClient = ...; + +Foo client = Foo + .builder() + .httpClient(customClient) + .build(); +``` + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `maxRetries` client option to configure this behavior. + +```java +import com.seed.api.Foo; + +Foo client = Foo + .builder() + .maxRetries(1) + .build(); +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level. + +```java +import com.seed.api.Foo; +import com.seed.api.core.RequestOptions; + +// Client level +Foo client = Foo + .builder() + .timeout(10) + .build(); + +// Request level +client.postWithNullableNamedRequestBodyType( + ..., + RequestOptions + .builder() + .timeout(10) + .build() +); +``` + +### Custom Headers + +The SDK allows you to add custom headers to requests. You can configure headers at the client level or at the request level. + +```java +import com.seed.api.Foo; +import com.seed.api.core.RequestOptions; + +// Client level +Foo client = Foo + .builder() + .addHeader("X-Custom-Header", "custom-value") + .addHeader("X-Request-Id", "abc-123") + .build(); +; + +// Request level +client.postWithNullableNamedRequestBodyType( + ..., + RequestOptions + .builder() + .addHeader("X-Request-Header", "request-value") + .build() +); +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/build.gradle b/seed/java-sdk/java-nullable-named-request-types/custom-config/build.gradle new file mode 100644 index 00000000000..c4f5285166e --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/build.gradle @@ -0,0 +1,102 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'com.diffplug.spotless' version '6.11.0' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + api 'com.squareup.okhttp3:okhttp:4.12.0' + api 'com.fasterxml.jackson.core:jackson-databind:2.17.2' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.17.2' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.2' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2' +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +tasks.withType(Javadoc) { + failOnError false + options.addStringOption('Xdoclint:none', '-quiet') +} + +spotless { + java { + palantirJavaFormat() + } +} + + +java { + withSourcesJar() + withJavadocJar() +} + + +group = 'com.fern' + +version = '0.0.1' + +jar { + dependsOn(":generatePomFileForMavenPublication") + archiveBaseName = "java-nullable-named-request-types" +} + +sourcesJar { + archiveBaseName = "java-nullable-named-request-types" +} + +javadocJar { + archiveBaseName = "java-nullable-named-request-types" +} + +test { + useJUnitPlatform() + testLogging { + showStandardStreams = true + } +} + +publishing { + publications { + maven(MavenPublication) { + groupId = 'com.fern' + artifactId = 'java-nullable-named-request-types' + version = '0.0.1' + from components.java + pom { + licenses { + license { + name = 'The MIT License (MIT)' + url = 'https://mit-license.org/' + } + } + scm { + connection = 'scm:git:git://github.com/java-nullable-named-request-types/fern.git' + developerConnection = 'scm:git:git://github.com/java-nullable-named-request-types/fern.git' + url = 'https://github.com/java-nullable-named-request-types/fern' + } + } + } + } + repositories { + maven { + url "$System.env.MAVEN_PUBLISH_REGISTRY_URL" + credentials { + username "$System.env.MAVEN_USERNAME" + password "$System.env.MAVEN_PASSWORD" + } + } + } +} + diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/reference.md b/seed/java-sdk/java-nullable-named-request-types/custom-config/reference.md new file mode 100644 index 00000000000..4c8a43f68f5 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/reference.md @@ -0,0 +1,130 @@ +# Reference +
client.postWithNullableNamedRequestBodyType(id, request) -> ResponseBody +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```java +client.postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest + .builder() + .id("id") + .body( + NullableObject + .builder() + .id("id") + .name("name") + .age(1) + .build() + ) + .build() +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `String` + +
+
+ +
+
+ +**request:** `Optional` + +
+
+
+
+ + +
+
+
+ +
client.postWithNonNullableNamedRequestBodyType(id, request) -> ResponseBody +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```java +client.postWithNonNullableNamedRequestBodyType( + NonNullableObject + .builder() + .id("id") + .build() +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `String` + +
+
+ +
+
+ +**nonNullableObjectId:** `Optional` + +
+
+ +
+
+ +**name:** `Optional` + +
+
+ +
+
+ +**age:** `Optional` + +
+
+
+
+ + +
+
+
diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/sample-app/build.gradle b/seed/java-sdk/java-nullable-named-request-types/custom-config/sample-app/build.gradle new file mode 100644 index 00000000000..4ee8f227b7a --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/sample-app/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java-library' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + implementation rootProject +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/sample-app/src/main/java/sample/App.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/sample-app/src/main/java/sample/App.java new file mode 100644 index 00000000000..9b7a9955991 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/sample-app/src/main/java/sample/App.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package sample; + +import java.lang.String; + +public final class App { + public static void main(String[] args) { + // import com.seed.api.AsyncFoo + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/settings.gradle b/seed/java-sdk/java-nullable-named-request-types/custom-config/settings.gradle new file mode 100644 index 00000000000..1663c62bb41 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'java-nullable-named-request-types' + +include 'sample-app' \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/snippet-templates.json b/seed/java-sdk/java-nullable-named-request-types/custom-config/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/snippet.json b/seed/java-sdk/java-nullable-named-request-types/custom-config/snippet.json new file mode 100644 index 00000000000..56b51668093 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/snippet.json @@ -0,0 +1,44 @@ +{ + "endpoints": [ + { + "example_identifier": "7d78177e", + "id": { + "method": "POST", + "path": "/postWithNullableNamedRequestBodyType/{id}", + "identifier_override": "endpoint_.postWithNullableNamedRequestBodyType" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.Foo;\nimport com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest;\nimport com.seed.api.types.NullableObject;\n\npublic class Example {\n public static void main(String[] args) {\n Foo client = Foo\n .builder()\n .build();\n\n client.postWithNullableNamedRequestBodyType(\n PostWithNullableNamedRequestBodyTypeRequest\n .builder()\n .id(\"id\")\n .body(\n NullableObject\n .builder()\n .id(\"id\")\n .name(\"name\")\n .age(1)\n .build()\n )\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.Foo;\nimport com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest;\nimport com.seed.api.types.NullableObject;\n\npublic class Example {\n public static void main(String[] args) {\n Foo client = Foo\n .builder()\n .build();\n\n client.postWithNullableNamedRequestBodyType(\n PostWithNullableNamedRequestBodyTypeRequest\n .builder()\n .id(\"id\")\n .body(\n NullableObject\n .builder()\n .id(\"id\")\n .name(\"name\")\n .age(1)\n .build()\n )\n .build()\n );\n }\n}\n" + } + }, + { + "example_identifier": "8a8806b8", + "id": { + "method": "POST", + "path": "/postWithNonNullableNamedRequestBodyType/{id}", + "identifier_override": "endpoint_.postWithNonNullableNamedRequestBodyType" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.Foo;\nimport com.seed.api.requests.NonNullableObject;\n\npublic class Example {\n public static void main(String[] args) {\n Foo client = Foo\n .builder()\n .build();\n\n client.postWithNonNullableNamedRequestBodyType(\n NonNullableObject\n .builder()\n .id(\"id\")\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.Foo;\nimport com.seed.api.requests.NonNullableObject;\n\npublic class Example {\n public static void main(String[] args) {\n Foo client = Foo\n .builder()\n .build();\n\n client.postWithNonNullableNamedRequestBodyType(\n NonNullableObject\n .builder()\n .id(\"id\")\n .build()\n );\n }\n}\n" + } + }, + { + "example_identifier": "39492db8", + "id": { + "method": "POST", + "path": "/postWithNonNullableNamedRequestBodyType/{id}", + "identifier_override": "endpoint_.postWithNonNullableNamedRequestBodyType" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.Foo;\nimport com.seed.api.requests.NonNullableObject;\n\npublic class Example {\n public static void main(String[] args) {\n Foo client = Foo\n .builder()\n .build();\n\n client.postWithNonNullableNamedRequestBodyType(\n NonNullableObject\n .builder()\n .id(\"id\")\n .nonNullableObjectId(\"id\")\n .name(\"name\")\n .age(1)\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.Foo;\nimport com.seed.api.requests.NonNullableObject;\n\npublic class Example {\n public static void main(String[] args) {\n Foo client = Foo\n .builder()\n .build();\n\n client.postWithNonNullableNamedRequestBodyType(\n NonNullableObject\n .builder()\n .id(\"id\")\n .nonNullableObjectId(\"id\")\n .name(\"name\")\n .age(1)\n .build()\n );\n }\n}\n" + } + } + ], + "types": {} +} \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/AsyncFoo.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/AsyncFoo.java new file mode 100644 index 00000000000..7860a67f04e --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/AsyncFoo.java @@ -0,0 +1,56 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.RequestOptions; +import com.seed.api.requests.NonNullableObject; +import com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest; +import com.seed.api.types.ResponseBody; +import java.util.concurrent.CompletableFuture; + +public class AsyncFoo { + protected final ClientOptions clientOptions; + + private final AsyncRawFoo rawClient; + + public AsyncFoo(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new AsyncRawFoo(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public AsyncRawFoo withRawResponse() { + return this.rawClient; + } + + public CompletableFuture postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest request) { + return this.rawClient.postWithNullableNamedRequestBodyType(request).thenApply(response -> response.body()); + } + + public CompletableFuture postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest request, RequestOptions requestOptions) { + return this.rawClient + .postWithNullableNamedRequestBodyType(request, requestOptions) + .thenApply(response -> response.body()); + } + + public CompletableFuture postWithNonNullableNamedRequestBodyType(NonNullableObject request) { + return this.rawClient.postWithNonNullableNamedRequestBodyType(request).thenApply(response -> response.body()); + } + + public CompletableFuture postWithNonNullableNamedRequestBodyType( + NonNullableObject request, RequestOptions requestOptions) { + return this.rawClient + .postWithNonNullableNamedRequestBodyType(request, requestOptions) + .thenApply(response -> response.body()); + } + + public static AsyncFooBuilder builder() { + return new AsyncFooBuilder(); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/AsyncFooBuilder.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/AsyncFooBuilder.java new file mode 100644 index 00000000000..76ab1a2dd8d --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/AsyncFooBuilder.java @@ -0,0 +1,165 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.Environment; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import okhttp3.OkHttpClient; + +public class AsyncFooBuilder { + private Optional timeout = Optional.empty(); + + private Optional maxRetries = Optional.empty(); + + private final Map customHeaders = new HashMap<>(); + + private Environment environment; + + private OkHttpClient httpClient; + + public AsyncFooBuilder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + /** + * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. + */ + public AsyncFooBuilder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Sets the maximum number of retries for the client. Defaults to 2 retries. + */ + public AsyncFooBuilder maxRetries(int maxRetries) { + this.maxRetries = Optional.of(maxRetries); + return this; + } + + /** + * Sets the underlying OkHttp client + */ + public AsyncFooBuilder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + /** + * Add a custom header to be sent with all requests. + * For headers that need to be computed dynamically or conditionally, use the setAdditional() method override instead. + * + * @param name The header name + * @param value The header value + * @return This builder for method chaining + */ + public AsyncFooBuilder addHeader(String name, String value) { + this.customHeaders.put(name, value); + return this; + } + + protected ClientOptions buildClientOptions() { + ClientOptions.Builder builder = ClientOptions.builder(); + setEnvironment(builder); + setHttpClient(builder); + setTimeouts(builder); + setRetries(builder); + for (Map.Entry header : this.customHeaders.entrySet()) { + builder.addHeader(header.getKey(), header.getValue()); + } + setAdditional(builder); + return builder.build(); + } + + /** + * Sets the environment configuration for the client. + * Override this method to modify URLs or add environment-specific logic. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setEnvironment(ClientOptions.Builder builder) { + builder.environment(this.environment); + } + + /** + * Sets the request timeout configuration. + * Override this method to customize timeout behavior. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setTimeouts(ClientOptions.Builder builder) { + if (this.timeout.isPresent()) { + builder.timeout(this.timeout.get()); + } + } + + /** + * Sets the retry configuration for failed requests. + * Override this method to implement custom retry strategies. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setRetries(ClientOptions.Builder builder) { + if (this.maxRetries.isPresent()) { + builder.maxRetries(this.maxRetries.get()); + } + } + + /** + * Sets the OkHttp client configuration. + * Override this method to customize HTTP client behavior (interceptors, connection pools, etc). + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setHttpClient(ClientOptions.Builder builder) { + if (this.httpClient != null) { + builder.httpClient(this.httpClient); + } + } + + /** + * Override this method to add any additional configuration to the client. + * This method is called at the end of the configuration chain, allowing you to add + * custom headers, modify settings, or perform any other client customization. + * + * @param builder The ClientOptions.Builder to configure + * + * Example: + *
{@code
+     * @Override
+     * protected void setAdditional(ClientOptions.Builder builder) {
+     *     builder.addHeader("X-Request-ID", () -> UUID.randomUUID().toString());
+     *     builder.addHeader("X-Client-Version", "1.0.0");
+     * }
+     * }
+ */ + protected void setAdditional(ClientOptions.Builder builder) {} + + /** + * Override this method to add custom validation logic before the client is built. + * This method is called at the beginning of the build() method to ensure the configuration is valid. + * Throw an exception to prevent client creation if validation fails. + * + * Example: + *
{@code
+     * @Override
+     * protected void validateConfiguration() {
+     *     super.validateConfiguration(); // Run parent validations
+     *     if (tenantId == null || tenantId.isEmpty()) {
+     *         throw new IllegalStateException("tenantId is required");
+     *     }
+     * }
+     * }
+ */ + protected void validateConfiguration() {} + + public AsyncFoo build() { + validateConfiguration(); + return new AsyncFoo(buildClientOptions()); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/AsyncRawFoo.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/AsyncRawFoo.java new file mode 100644 index 00000000000..69ee0d9f1c1 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/AsyncRawFoo.java @@ -0,0 +1,160 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.seed.api.core.ClientOptions; +import com.seed.api.core.FooApiException; +import com.seed.api.core.FooException; +import com.seed.api.core.FooHttpResponse; +import com.seed.api.core.MediaTypes; +import com.seed.api.core.ObjectMappers; +import com.seed.api.core.RequestOptions; +import com.seed.api.requests.NonNullableObject; +import com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest; +import com.seed.api.types.ResponseBody; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.jetbrains.annotations.NotNull; + +public class AsyncRawFoo { + protected final ClientOptions clientOptions; + + public AsyncRawFoo(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + public CompletableFuture> postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest request) { + return postWithNullableNamedRequestBodyType(request, null); + } + + public CompletableFuture> postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("postWithNullableNamedRequestBodyType") + .addPathSegment(request.getId()) + .build(); + RequestBody body; + try { + body = RequestBody.create("", null); + if (request.isPresent()) { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request.getBody()), MediaTypes.APPLICATION_JSON); + } + } catch (JsonProcessingException e) { + throw new FooException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (okhttp3.ResponseBody responseBody = response.body()) { + if (response.isSuccessful()) { + future.complete(new FooHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), ResponseBody.class), + response)); + return; + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + future.completeExceptionally(new FooApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response)); + return; + } catch (IOException e) { + future.completeExceptionally(new FooException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new FooException("Network error executing HTTP request", e)); + } + }); + return future; + } + + public CompletableFuture> postWithNonNullableNamedRequestBodyType( + NonNullableObject request) { + return postWithNonNullableNamedRequestBodyType(request, null); + } + + public CompletableFuture> postWithNonNullableNamedRequestBodyType( + NonNullableObject request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("postWithNonNullableNamedRequestBodyType") + .addPathSegment(request.getId()) + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new FooException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (okhttp3.ResponseBody responseBody = response.body()) { + if (response.isSuccessful()) { + future.complete(new FooHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), ResponseBody.class), + response)); + return; + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + future.completeExceptionally(new FooApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response)); + return; + } catch (IOException e) { + future.completeExceptionally(new FooException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new FooException("Network error executing HTTP request", e)); + } + }); + return future; + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/Foo.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/Foo.java new file mode 100644 index 00000000000..c7763da00d4 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/Foo.java @@ -0,0 +1,54 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.RequestOptions; +import com.seed.api.requests.NonNullableObject; +import com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest; +import com.seed.api.types.ResponseBody; + +public class Foo { + protected final ClientOptions clientOptions; + + private final RawFoo rawClient; + + public Foo(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new RawFoo(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public RawFoo withRawResponse() { + return this.rawClient; + } + + public ResponseBody postWithNullableNamedRequestBodyType(PostWithNullableNamedRequestBodyTypeRequest request) { + return this.rawClient.postWithNullableNamedRequestBodyType(request).body(); + } + + public ResponseBody postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest request, RequestOptions requestOptions) { + return this.rawClient + .postWithNullableNamedRequestBodyType(request, requestOptions) + .body(); + } + + public ResponseBody postWithNonNullableNamedRequestBodyType(NonNullableObject request) { + return this.rawClient.postWithNonNullableNamedRequestBodyType(request).body(); + } + + public ResponseBody postWithNonNullableNamedRequestBodyType( + NonNullableObject request, RequestOptions requestOptions) { + return this.rawClient + .postWithNonNullableNamedRequestBodyType(request, requestOptions) + .body(); + } + + public static FooBuilder builder() { + return new FooBuilder(); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/FooBuilder.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/FooBuilder.java new file mode 100644 index 00000000000..578774bf39b --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/FooBuilder.java @@ -0,0 +1,165 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.Environment; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import okhttp3.OkHttpClient; + +public class FooBuilder { + private Optional timeout = Optional.empty(); + + private Optional maxRetries = Optional.empty(); + + private final Map customHeaders = new HashMap<>(); + + private Environment environment; + + private OkHttpClient httpClient; + + public FooBuilder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + /** + * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. + */ + public FooBuilder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Sets the maximum number of retries for the client. Defaults to 2 retries. + */ + public FooBuilder maxRetries(int maxRetries) { + this.maxRetries = Optional.of(maxRetries); + return this; + } + + /** + * Sets the underlying OkHttp client + */ + public FooBuilder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + /** + * Add a custom header to be sent with all requests. + * For headers that need to be computed dynamically or conditionally, use the setAdditional() method override instead. + * + * @param name The header name + * @param value The header value + * @return This builder for method chaining + */ + public FooBuilder addHeader(String name, String value) { + this.customHeaders.put(name, value); + return this; + } + + protected ClientOptions buildClientOptions() { + ClientOptions.Builder builder = ClientOptions.builder(); + setEnvironment(builder); + setHttpClient(builder); + setTimeouts(builder); + setRetries(builder); + for (Map.Entry header : this.customHeaders.entrySet()) { + builder.addHeader(header.getKey(), header.getValue()); + } + setAdditional(builder); + return builder.build(); + } + + /** + * Sets the environment configuration for the client. + * Override this method to modify URLs or add environment-specific logic. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setEnvironment(ClientOptions.Builder builder) { + builder.environment(this.environment); + } + + /** + * Sets the request timeout configuration. + * Override this method to customize timeout behavior. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setTimeouts(ClientOptions.Builder builder) { + if (this.timeout.isPresent()) { + builder.timeout(this.timeout.get()); + } + } + + /** + * Sets the retry configuration for failed requests. + * Override this method to implement custom retry strategies. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setRetries(ClientOptions.Builder builder) { + if (this.maxRetries.isPresent()) { + builder.maxRetries(this.maxRetries.get()); + } + } + + /** + * Sets the OkHttp client configuration. + * Override this method to customize HTTP client behavior (interceptors, connection pools, etc). + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setHttpClient(ClientOptions.Builder builder) { + if (this.httpClient != null) { + builder.httpClient(this.httpClient); + } + } + + /** + * Override this method to add any additional configuration to the client. + * This method is called at the end of the configuration chain, allowing you to add + * custom headers, modify settings, or perform any other client customization. + * + * @param builder The ClientOptions.Builder to configure + * + * Example: + *
{@code
+     * @Override
+     * protected void setAdditional(ClientOptions.Builder builder) {
+     *     builder.addHeader("X-Request-ID", () -> UUID.randomUUID().toString());
+     *     builder.addHeader("X-Client-Version", "1.0.0");
+     * }
+     * }
+ */ + protected void setAdditional(ClientOptions.Builder builder) {} + + /** + * Override this method to add custom validation logic before the client is built. + * This method is called at the beginning of the build() method to ensure the configuration is valid. + * Throw an exception to prevent client creation if validation fails. + * + * Example: + *
{@code
+     * @Override
+     * protected void validateConfiguration() {
+     *     super.validateConfiguration(); // Run parent validations
+     *     if (tenantId == null || tenantId.isEmpty()) {
+     *         throw new IllegalStateException("tenantId is required");
+     *     }
+     * }
+     * }
+ */ + protected void validateConfiguration() {} + + public Foo build() { + validateConfiguration(); + return new Foo(buildClientOptions()); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/RawFoo.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/RawFoo.java new file mode 100644 index 00000000000..fb2e6f0a065 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/RawFoo.java @@ -0,0 +1,127 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.seed.api.core.ClientOptions; +import com.seed.api.core.FooApiException; +import com.seed.api.core.FooException; +import com.seed.api.core.FooHttpResponse; +import com.seed.api.core.MediaTypes; +import com.seed.api.core.ObjectMappers; +import com.seed.api.core.RequestOptions; +import com.seed.api.requests.NonNullableObject; +import com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest; +import com.seed.api.types.ResponseBody; +import java.io.IOException; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class RawFoo { + protected final ClientOptions clientOptions; + + public RawFoo(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + public FooHttpResponse postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest request) { + return postWithNullableNamedRequestBodyType(request, null); + } + + public FooHttpResponse postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("postWithNullableNamedRequestBodyType") + .addPathSegment(request.getId()) + .build(); + RequestBody body; + try { + body = RequestBody.create("", null); + if (request.isPresent()) { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request.getBody()), MediaTypes.APPLICATION_JSON); + } + } catch (JsonProcessingException e) { + throw new FooException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + okhttp3.ResponseBody responseBody = response.body(); + if (response.isSuccessful()) { + return new FooHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), ResponseBody.class), response); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new FooApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response); + } catch (IOException e) { + throw new FooException("Network error executing HTTP request", e); + } + } + + public FooHttpResponse postWithNonNullableNamedRequestBodyType(NonNullableObject request) { + return postWithNonNullableNamedRequestBodyType(request, null); + } + + public FooHttpResponse postWithNonNullableNamedRequestBodyType( + NonNullableObject request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("postWithNonNullableNamedRequestBodyType") + .addPathSegment(request.getId()) + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new FooException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + okhttp3.ResponseBody responseBody = response.body(); + if (response.isSuccessful()) { + return new FooHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), ResponseBody.class), response); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new FooApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response); + } catch (IOException e) { + throw new FooException("Network error executing HTTP request", e); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/ClientOptions.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/ClientOptions.java new file mode 100644 index 00000000000..cbf9de67067 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/ClientOptions.java @@ -0,0 +1,180 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import okhttp3.OkHttpClient; + +public final class ClientOptions { + private final Environment environment; + + private final Map headers; + + private final Map> headerSuppliers; + + private final OkHttpClient httpClient; + + private final int timeout; + + private ClientOptions( + Environment environment, + Map headers, + Map> headerSuppliers, + OkHttpClient httpClient, + int timeout) { + this.environment = environment; + this.headers = new HashMap<>(); + this.headers.putAll(headers); + this.headers.putAll(new HashMap() { + { + put("User-Agent", "com.fern:java-nullable-named-request-types/0.0.1"); + put("X-Fern-Language", "JAVA"); + } + }); + this.headerSuppliers = headerSuppliers; + this.httpClient = httpClient; + this.timeout = timeout; + } + + public Environment environment() { + return this.environment; + } + + public Map headers(RequestOptions requestOptions) { + Map values = new HashMap<>(this.headers); + headerSuppliers.forEach((key, supplier) -> { + values.put(key, supplier.get()); + }); + if (requestOptions != null) { + values.putAll(requestOptions.getHeaders()); + } + return values; + } + + public int timeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.timeout; + } + return requestOptions.getTimeout().orElse(this.timeout); + } + + public OkHttpClient httpClient() { + return this.httpClient; + } + + public OkHttpClient httpClientWithTimeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.httpClient; + } + return this.httpClient + .newBuilder() + .callTimeout(requestOptions.getTimeout().get(), requestOptions.getTimeoutTimeUnit()) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .build(); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Environment environment; + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + private int maxRetries = 2; + + private Optional timeout = Optional.empty(); + + private OkHttpClient httpClient = null; + + public Builder environment(Environment environment) { + this.environment = environment; + return this; + } + + public Builder addHeader(String key, String value) { + this.headers.put(key, value); + return this; + } + + public Builder addHeader(String key, Supplier value) { + this.headerSuppliers.put(key, value); + return this; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(Optional timeout) { + this.timeout = timeout; + return this; + } + + /** + * Override the maximum number of retries. Defaults to 2 retries. + */ + public Builder maxRetries(int maxRetries) { + this.maxRetries = maxRetries; + return this; + } + + public Builder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + public ClientOptions build() { + OkHttpClient.Builder httpClientBuilder = + this.httpClient != null ? this.httpClient.newBuilder() : new OkHttpClient.Builder(); + + if (this.httpClient != null) { + timeout.ifPresent(timeout -> httpClientBuilder + .callTimeout(timeout, TimeUnit.SECONDS) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS)); + } else { + httpClientBuilder + .callTimeout(this.timeout.orElse(60), TimeUnit.SECONDS) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .addInterceptor(new RetryInterceptor(this.maxRetries)); + } + + this.httpClient = httpClientBuilder.build(); + this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); + + return new ClientOptions(environment, headers, headerSuppliers, httpClient, this.timeout.get()); + } + + /** + * Create a new Builder initialized with values from an existing ClientOptions + */ + public static Builder from(ClientOptions clientOptions) { + Builder builder = new Builder(); + builder.environment = clientOptions.environment(); + builder.timeout = Optional.of(clientOptions.timeout(null)); + builder.httpClient = clientOptions.httpClient(); + return builder; + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/DateTimeDeserializer.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/DateTimeDeserializer.java new file mode 100644 index 00000000000..eac7d50c71a --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/DateTimeDeserializer.java @@ -0,0 +1,55 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; + +/** + * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. + */ +class DateTimeDeserializer extends JsonDeserializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); + } + + /** + * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonToken token = parser.currentToken(); + if (token == JsonToken.VALUE_NUMBER_INT) { + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); + } else { + TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( + parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); + + if (temporal.query(TemporalQueries.offset()) == null) { + return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); + } else { + return OffsetDateTime.from(temporal); + } + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/Environment.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/Environment.java new file mode 100644 index 00000000000..8a286722bb2 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/Environment.java @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +public final class Environment { + private final String url; + + private Environment(String url) { + this.url = url; + } + + public String getUrl() { + return this.url; + } + + public static Environment custom(String url) { + return new Environment(url); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/FileStream.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/FileStream.java new file mode 100644 index 00000000000..a71e7946986 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/FileStream.java @@ -0,0 +1,60 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.InputStream; +import java.util.Objects; +import okhttp3.MediaType; +import okhttp3.RequestBody; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a file stream with associated metadata for file uploads. + */ +public class FileStream { + private final InputStream inputStream; + private final String fileName; + private final MediaType contentType; + + /** + * Constructs a FileStream with the given input stream and optional metadata. + * + * @param inputStream The input stream of the file content. Must not be null. + * @param fileName The name of the file, or null if unknown. + * @param contentType The MIME type of the file content, or null if unknown. + * @throws NullPointerException if inputStream is null + */ + public FileStream(InputStream inputStream, @Nullable String fileName, @Nullable MediaType contentType) { + this.inputStream = Objects.requireNonNull(inputStream, "Input stream cannot be null"); + this.fileName = fileName; + this.contentType = contentType; + } + + public FileStream(InputStream inputStream) { + this(inputStream, null, null); + } + + public InputStream getInputStream() { + return inputStream; + } + + @Nullable + public String getFileName() { + return fileName; + } + + @Nullable + public MediaType getContentType() { + return contentType; + } + + /** + * Creates a RequestBody suitable for use with OkHttp client. + * + * @return A RequestBody instance representing this file stream. + */ + public RequestBody toRequestBody() { + return new InputStreamRequestBody(contentType, inputStream); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/FooApiException.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/FooApiException.java new file mode 100644 index 00000000000..28c07b0bf9e --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/FooApiException.java @@ -0,0 +1,73 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.Response; + +/** + * This exception type will be thrown for any non-2XX API responses. + */ +public class FooApiException extends FooException { + /** + * The error code of the response that triggered the exception. + */ + private final int statusCode; + + /** + * The body of the response that triggered the exception. + */ + private final Object body; + + private final Map> headers; + + public FooApiException(String message, int statusCode, Object body) { + super(message); + this.statusCode = statusCode; + this.body = body; + this.headers = new HashMap<>(); + } + + public FooApiException(String message, int statusCode, Object body, Response rawResponse) { + super(message); + this.statusCode = statusCode; + this.body = body; + this.headers = new HashMap<>(); + rawResponse.headers().forEach(header -> { + String key = header.component1(); + String value = header.component2(); + this.headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); + }); + } + + /** + * @return the statusCode + */ + public int statusCode() { + return this.statusCode; + } + + /** + * @return the body + */ + public Object body() { + return this.body; + } + + /** + * @return the headers + */ + public Map> headers() { + return this.headers; + } + + @java.lang.Override + public String toString() { + return "FooApiException{" + "message: " + getMessage() + ", statusCode: " + statusCode + ", body: " + body + + "}"; + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/FooException.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/FooException.java new file mode 100644 index 00000000000..66d76011161 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/FooException.java @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +/** + * This class serves as the base exception for all errors in the SDK. + */ +public class FooException extends RuntimeException { + public FooException(String message) { + super(message); + } + + public FooException(String message, Exception e) { + super(message, e); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/FooHttpResponse.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/FooHttpResponse.java new file mode 100644 index 00000000000..6943cae3121 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/FooHttpResponse.java @@ -0,0 +1,37 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.Response; + +public final class FooHttpResponse { + + private final T body; + + private final Map> headers; + + public FooHttpResponse(T body, Response rawResponse) { + this.body = body; + + Map> headers = new HashMap<>(); + rawResponse.headers().forEach(header -> { + String key = header.component1(); + String value = header.component2(); + headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); + }); + this.headers = headers; + } + + public T body() { + return this.body; + } + + public Map> headers() { + return headers; + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/InputStreamRequestBody.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/InputStreamRequestBody.java new file mode 100644 index 00000000000..a1e136889aa --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/InputStreamRequestBody.java @@ -0,0 +1,74 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Objects; +import okhttp3.MediaType; +import okhttp3.RequestBody; +import okio.BufferedSink; +import okio.Okio; +import okio.Source; +import org.jetbrains.annotations.Nullable; + +/** + * A custom implementation of OkHttp's RequestBody that wraps an InputStream. + * This class allows streaming of data from an InputStream directly to an HTTP request body, + * which is useful for file uploads or sending large amounts of data without loading it all into memory. + */ +public class InputStreamRequestBody extends RequestBody { + private final InputStream inputStream; + private final MediaType contentType; + + /** + * Constructs an InputStreamRequestBody with the specified content type and input stream. + * + * @param contentType the MediaType of the content, or null if not known + * @param inputStream the InputStream containing the data to be sent + * @throws NullPointerException if inputStream is null + */ + public InputStreamRequestBody(@Nullable MediaType contentType, InputStream inputStream) { + this.contentType = contentType; + this.inputStream = Objects.requireNonNull(inputStream, "inputStream == null"); + } + + /** + * Returns the content type of this request body. + * + * @return the MediaType of the content, or null if not specified + */ + @Nullable + @Override + public MediaType contentType() { + return contentType; + } + + /** + * Returns the content length of this request body, if known. + * This method attempts to determine the length using the InputStream's available() method, + * which may not always accurately reflect the total length of the stream. + * + * @return the content length, or -1 if the length is unknown + * @throws IOException if an I/O error occurs + */ + @Override + public long contentLength() throws IOException { + return inputStream.available() == 0 ? -1 : inputStream.available(); + } + + /** + * Writes the content of the InputStream to the given BufferedSink. + * This method is responsible for transferring the data from the InputStream to the network request. + * + * @param sink the BufferedSink to write the content to + * @throws IOException if an I/O error occurs during writing + */ + @Override + public void writeTo(BufferedSink sink) throws IOException { + try (Source source = Okio.source(inputStream)) { + sink.writeAll(source); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/MediaTypes.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/MediaTypes.java new file mode 100644 index 00000000000..4a8d1cf301d --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/MediaTypes.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import okhttp3.MediaType; + +public final class MediaTypes { + + public static final MediaType APPLICATION_JSON = MediaType.parse("application/json"); + + private MediaTypes() {} +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/Nullable.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/Nullable.java new file mode 100644 index 00000000000..ca50b2d8d50 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/Nullable.java @@ -0,0 +1,140 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.Optional; +import java.util.function.Function; + +public final class Nullable { + + private final Either, Null> value; + + private Nullable() { + this.value = Either.left(Optional.empty()); + } + + private Nullable(T value) { + if (value == null) { + this.value = Either.right(Null.INSTANCE); + } else { + this.value = Either.left(Optional.of(value)); + } + } + + public static Nullable ofNull() { + return new Nullable<>(null); + } + + public static Nullable of(T value) { + return new Nullable<>(value); + } + + public static Nullable empty() { + return new Nullable<>(); + } + + public static Nullable ofOptional(Optional value) { + if (value.isPresent()) { + return of(value.get()); + } else { + return empty(); + } + } + + public boolean isNull() { + return this.value.isRight(); + } + + public boolean isEmpty() { + return this.value.isLeft() && !this.value.getLeft().isPresent(); + } + + public T get() { + if (this.isNull()) { + return null; + } + + return this.value.getLeft().get(); + } + + public Nullable map(Function mapper) { + if (this.isNull()) { + return Nullable.ofNull(); + } + + return Nullable.ofOptional(this.value.getLeft().map(mapper)); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Nullable)) { + return false; + } + + if (((Nullable) other).isNull() && this.isNull()) { + return true; + } + + return this.value.getLeft().equals(((Nullable) other).value.getLeft()); + } + + private static final class Either { + private L left = null; + private R right = null; + + private Either(L left, R right) { + if (left != null && right != null) { + throw new IllegalArgumentException("Left and right argument cannot both be non-null."); + } + + if (left == null && right == null) { + throw new IllegalArgumentException("Left and right argument cannot both be null."); + } + + if (left != null) { + this.left = left; + } + + if (right != null) { + this.right = right; + } + } + + public static Either left(L left) { + return new Either<>(left, null); + } + + public static Either right(R right) { + return new Either<>(null, right); + } + + public boolean isLeft() { + return this.left != null; + } + + public boolean isRight() { + return this.right != null; + } + + public L getLeft() { + if (!this.isLeft()) { + throw new IllegalArgumentException("Cannot get left from right Either."); + } + return this.left; + } + + public R getRight() { + if (!this.isRight()) { + throw new IllegalArgumentException("Cannot get right from left Either."); + } + return this.right; + } + } + + private static final class Null { + private static final Null INSTANCE = new Null(); + + private Null() {} + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/NullableNonemptyFilter.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/NullableNonemptyFilter.java new file mode 100644 index 00000000000..4c5c664eca0 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/NullableNonemptyFilter.java @@ -0,0 +1,19 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.Optional; + +public final class NullableNonemptyFilter { + @Override + public boolean equals(Object o) { + boolean isOptionalEmpty = isOptionalEmpty(o); + + return isOptionalEmpty; + } + + private boolean isOptionalEmpty(Object o) { + return o instanceof Optional && !((Optional) o).isPresent(); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/ObjectMappers.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/ObjectMappers.java new file mode 100644 index 00000000000..0b16d472dca --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/ObjectMappers.java @@ -0,0 +1,36 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; + +public final class ObjectMappers { + public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() + .addModule(new Jdk8Module()) + .addModule(new JavaTimeModule()) + .addModule(DateTimeDeserializer.getModule()) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + private ObjectMappers() {} + + public static String stringify(Object o) { + try { + return JSON_MAPPER + .setSerializationInclusion(JsonInclude.Include.ALWAYS) + .writerWithDefaultPrettyPrinter() + .writeValueAsString(o); + } catch (IOException e) { + return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/QueryStringMapper.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/QueryStringMapper.java new file mode 100644 index 00000000000..3e364e6f3d5 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/QueryStringMapper.java @@ -0,0 +1,142 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import okhttp3.HttpUrl; +import okhttp3.MultipartBody; + +public class QueryStringMapper { + + private static final ObjectMapper MAPPER = ObjectMappers.JSON_MAPPER; + + public static void addQueryParameter(HttpUrl.Builder httpUrl, String key, Object value, boolean arraysAsRepeats) { + JsonNode valueNode = MAPPER.valueToTree(value); + + List> flat; + if (valueNode.isObject()) { + flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); + } else if (valueNode.isArray()) { + flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); + } else { + if (valueNode.isTextual()) { + httpUrl.addQueryParameter(key, valueNode.textValue()); + } else { + httpUrl.addQueryParameter(key, valueNode.toString()); + } + return; + } + + for (Map.Entry field : flat) { + if (field.getValue().isTextual()) { + httpUrl.addQueryParameter(key + field.getKey(), field.getValue().textValue()); + } else { + httpUrl.addQueryParameter(key + field.getKey(), field.getValue().toString()); + } + } + } + + public static void addFormDataPart( + MultipartBody.Builder multipartBody, String key, Object value, boolean arraysAsRepeats) { + JsonNode valueNode = MAPPER.valueToTree(value); + + List> flat; + if (valueNode.isObject()) { + flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); + } else if (valueNode.isArray()) { + flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); + } else { + if (valueNode.isTextual()) { + multipartBody.addFormDataPart(key, valueNode.textValue()); + } else { + multipartBody.addFormDataPart(key, valueNode.toString()); + } + return; + } + + for (Map.Entry field : flat) { + if (field.getValue().isTextual()) { + multipartBody.addFormDataPart( + key + field.getKey(), field.getValue().textValue()); + } else { + multipartBody.addFormDataPart( + key + field.getKey(), field.getValue().toString()); + } + } + } + + public static List> flattenObject(ObjectNode object, boolean arraysAsRepeats) { + List> flat = new ArrayList<>(); + + Iterator> fields = object.fields(); + while (fields.hasNext()) { + Map.Entry field = fields.next(); + + String key = "[" + field.getKey() + "]"; + + if (field.getValue().isObject()) { + List> flatField = + flattenObject((ObjectNode) field.getValue(), arraysAsRepeats); + addAll(flat, flatField, key); + } else if (field.getValue().isArray()) { + List> flatField = + flattenArray((ArrayNode) field.getValue(), key, arraysAsRepeats); + addAll(flat, flatField, ""); + } else { + flat.add(new AbstractMap.SimpleEntry<>(key, field.getValue())); + } + } + + return flat; + } + + private static List> flattenArray( + ArrayNode array, String key, boolean arraysAsRepeats) { + List> flat = new ArrayList<>(); + + Iterator elements = array.elements(); + + int index = 0; + while (elements.hasNext()) { + JsonNode element = elements.next(); + + String indexKey = key + "[" + index + "]"; + + if (arraysAsRepeats) { + indexKey = key; + } + + if (element.isObject()) { + List> flatField = flattenObject((ObjectNode) element, arraysAsRepeats); + addAll(flat, flatField, indexKey); + } else if (element.isArray()) { + List> flatField = flattenArray((ArrayNode) element, "", arraysAsRepeats); + addAll(flat, flatField, indexKey); + } else { + flat.add(new AbstractMap.SimpleEntry<>(indexKey, element)); + } + + index++; + } + + return flat; + } + + private static void addAll( + List> target, List> source, String prefix) { + for (Map.Entry entry : source) { + Map.Entry entryToAdd = + new AbstractMap.SimpleEntry<>(prefix + entry.getKey(), entry.getValue()); + target.add(entryToAdd); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/RequestOptions.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/RequestOptions.java new file mode 100644 index 00000000000..e78b8620b59 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/RequestOptions.java @@ -0,0 +1,87 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +public final class RequestOptions { + private final Optional timeout; + + private final TimeUnit timeoutTimeUnit; + + private final Map headers; + + private final Map> headerSuppliers; + + private RequestOptions( + Optional timeout, + TimeUnit timeoutTimeUnit, + Map headers, + Map> headerSuppliers) { + this.timeout = timeout; + this.timeoutTimeUnit = timeoutTimeUnit; + this.headers = headers; + this.headerSuppliers = headerSuppliers; + } + + public Optional getTimeout() { + return timeout; + } + + public TimeUnit getTimeoutTimeUnit() { + return timeoutTimeUnit; + } + + public Map getHeaders() { + Map headers = new HashMap<>(); + headers.putAll(this.headers); + this.headerSuppliers.forEach((key, supplier) -> { + headers.put(key, supplier.get()); + }); + return headers; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Optional timeout = Optional.empty(); + + private TimeUnit timeoutTimeUnit = TimeUnit.SECONDS; + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + public Builder timeout(Integer timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + public Builder timeout(Integer timeout, TimeUnit timeoutTimeUnit) { + this.timeout = Optional.of(timeout); + this.timeoutTimeUnit = timeoutTimeUnit; + return this; + } + + public Builder addHeader(String key, String value) { + this.headers.put(key, value); + return this; + } + + public Builder addHeader(String key, Supplier value) { + this.headerSuppliers.put(key, value); + return this; + } + + public RequestOptions build() { + return new RequestOptions(timeout, timeoutTimeUnit, headers, headerSuppliers); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/ResponseBodyInputStream.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/ResponseBodyInputStream.java new file mode 100644 index 00000000000..db05d538255 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/ResponseBodyInputStream.java @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.FilterInputStream; +import java.io.IOException; +import okhttp3.Response; + +/** + * A custom InputStream that wraps the InputStream from the OkHttp Response and ensures that the + * OkHttp Response object is properly closed when the stream is closed. + * + * This class extends FilterInputStream and takes an OkHttp Response object as a parameter. + * It retrieves the InputStream from the Response and overrides the close method to close + * both the InputStream and the Response object, ensuring proper resource management and preventing + * premature closure of the underlying HTTP connection. + */ +public class ResponseBodyInputStream extends FilterInputStream { + private final Response response; + + /** + * Constructs a ResponseBodyInputStream that wraps the InputStream from the given OkHttp + * Response object. + * + * @param response the OkHttp Response object from which the InputStream is retrieved + * @throws IOException if an I/O error occurs while retrieving the InputStream + */ + public ResponseBodyInputStream(Response response) throws IOException { + super(response.body().byteStream()); + this.response = response; + } + + /** + * Closes the InputStream and the associated OkHttp Response object. This ensures that the + * underlying HTTP connection is properly closed after the stream is no longer needed. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + super.close(); + response.close(); // Ensure the response is closed when the stream is closed + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/ResponseBodyReader.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/ResponseBodyReader.java new file mode 100644 index 00000000000..97fcf7a0efb --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/ResponseBodyReader.java @@ -0,0 +1,44 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.FilterReader; +import java.io.IOException; +import okhttp3.Response; + +/** + * A custom Reader that wraps the Reader from the OkHttp Response and ensures that the + * OkHttp Response object is properly closed when the reader is closed. + * + * This class extends FilterReader and takes an OkHttp Response object as a parameter. + * It retrieves the Reader from the Response and overrides the close method to close + * both the Reader and the Response object, ensuring proper resource management and preventing + * premature closure of the underlying HTTP connection. + */ +public class ResponseBodyReader extends FilterReader { + private final Response response; + + /** + * Constructs a ResponseBodyReader that wraps the Reader from the given OkHttp Response object. + * + * @param response the OkHttp Response object from which the Reader is retrieved + * @throws IOException if an I/O error occurs while retrieving the Reader + */ + public ResponseBodyReader(Response response) throws IOException { + super(response.body().charStream()); + this.response = response; + } + + /** + * Closes the Reader and the associated OkHttp Response object. This ensures that the + * underlying HTTP connection is properly closed after the reader is no longer needed. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + super.close(); + response.close(); // Ensure the response is closed when the reader is closed + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/RetryInterceptor.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/RetryInterceptor.java new file mode 100644 index 00000000000..728715567d3 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/RetryInterceptor.java @@ -0,0 +1,78 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.IOException; +import java.time.Duration; +import java.util.Optional; +import java.util.Random; +import okhttp3.Interceptor; +import okhttp3.Response; + +public class RetryInterceptor implements Interceptor { + + private static final Duration ONE_SECOND = Duration.ofSeconds(1); + private final ExponentialBackoff backoff; + private final Random random = new Random(); + + public RetryInterceptor(int maxRetries) { + this.backoff = new ExponentialBackoff(maxRetries); + } + + @Override + public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + + if (shouldRetry(response.code())) { + return retryChain(response, chain); + } + + return response; + } + + private Response retryChain(Response response, Chain chain) throws IOException { + Optional nextBackoff = this.backoff.nextBackoff(); + while (nextBackoff.isPresent()) { + try { + Thread.sleep(nextBackoff.get().toMillis()); + } catch (InterruptedException e) { + throw new IOException("Interrupted while trying request", e); + } + response.close(); + response = chain.proceed(chain.request()); + if (shouldRetry(response.code())) { + nextBackoff = this.backoff.nextBackoff(); + } else { + return response; + } + } + + return response; + } + + private static boolean shouldRetry(int statusCode) { + return statusCode == 408 || statusCode == 429 || statusCode >= 500; + } + + private final class ExponentialBackoff { + + private final int maxNumRetries; + + private int retryNumber = 0; + + ExponentialBackoff(int maxNumRetries) { + this.maxNumRetries = maxNumRetries; + } + + public Optional nextBackoff() { + retryNumber += 1; + if (retryNumber > maxNumRetries) { + return Optional.empty(); + } + + int upperBound = (int) Math.pow(2, retryNumber); + return Optional.of(ONE_SECOND.multipliedBy(random.nextInt(upperBound))); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/Stream.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/Stream.java new file mode 100644 index 00000000000..303eaf050fe --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/Stream.java @@ -0,0 +1,272 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.Closeable; +import java.io.IOException; +import java.io.Reader; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Scanner; + +/** + * The {@code Stream} class implements {@link Iterable} to provide a simple mechanism for reading and parsing + * objects of a given type from data streamed via a {@link Reader} using a specified delimiter. + *

+ * {@code Stream} assumes that data is being pushed to the provided {@link Reader} asynchronously and utilizes a + * {@code Scanner} to block during iteration if the next object is not available. + * Iterable stream for parsing JSON and Server-Sent Events (SSE) data. + * Supports both newline-delimited JSON and SSE with optional stream termination. + * + * @param The type of objects in the stream. + */ +public final class Stream implements Iterable, Closeable { + + private static final String NEWLINE = "\n"; + private static final String DATA_PREFIX = "data:"; + + public enum StreamType { + JSON, + SSE + } + + private final Class valueType; + private final Scanner scanner; + private final StreamType streamType; + private final String messageTerminator; + private final String streamTerminator; + private final Reader sseReader; + private boolean isClosed = false; + + /** + * Constructs a new {@code Stream} with the specified value type, reader, and delimiter. + * + * @param valueType The class of the objects in the stream. + * @param reader The reader that provides the streamed data. + * @param delimiter The delimiter used to separate elements in the stream. + */ + public Stream(Class valueType, Reader reader, String delimiter) { + this.valueType = valueType; + this.scanner = new Scanner(reader).useDelimiter(delimiter); + this.streamType = StreamType.JSON; + this.messageTerminator = delimiter; + this.streamTerminator = null; + this.sseReader = null; + } + + private Stream(Class valueType, StreamType type, Reader reader, String terminator) { + this.valueType = valueType; + this.streamType = type; + if (type == StreamType.JSON) { + this.scanner = new Scanner(reader).useDelimiter(terminator); + this.messageTerminator = terminator; + this.streamTerminator = null; + this.sseReader = null; + } else { + this.scanner = null; + this.messageTerminator = NEWLINE; + this.streamTerminator = terminator; + this.sseReader = reader; + } + } + + public static Stream fromJson(Class valueType, Reader reader, String delimiter) { + return new Stream<>(valueType, reader, delimiter); + } + + public static Stream fromJson(Class valueType, Reader reader) { + return new Stream<>(valueType, reader, NEWLINE); + } + + public static Stream fromSse(Class valueType, Reader sseReader) { + return new Stream<>(valueType, StreamType.SSE, sseReader, null); + } + + public static Stream fromSse(Class valueType, Reader sseReader, String streamTerminator) { + return new Stream<>(valueType, StreamType.SSE, sseReader, streamTerminator); + } + + @Override + public void close() throws IOException { + if (!isClosed) { + isClosed = true; + if (scanner != null) { + scanner.close(); + } + if (sseReader != null) { + sseReader.close(); + } + } + } + + private boolean isStreamClosed() { + return isClosed; + } + + /** + * Returns an iterator over the elements in this stream that blocks during iteration when the next object is + * not yet available. + * + * @return An iterator that can be used to traverse the elements in the stream. + */ + @Override + public Iterator iterator() { + if (streamType == StreamType.SSE) { + return new SSEIterator(); + } else { + return new JsonIterator(); + } + } + + private final class JsonIterator implements Iterator { + + /** + * Returns {@code true} if there are more elements in the stream. + *

+ * Will block and wait for input if the stream has not ended and the next object is not yet available. + * + * @return {@code true} if there are more elements, {@code false} otherwise. + */ + @Override + public boolean hasNext() { + if (isStreamClosed()) { + return false; + } + return scanner.hasNext(); + } + + /** + * Returns the next element in the stream. + *

+ * Will block and wait for input if the stream has not ended and the next object is not yet available. + * + * @return The next element in the stream. + * @throws NoSuchElementException If there are no more elements in the stream. + */ + @Override + public T next() { + if (isStreamClosed()) { + throw new NoSuchElementException("Stream is closed"); + } + + if (!scanner.hasNext()) { + throw new NoSuchElementException(); + } else { + try { + T parsedResponse = + ObjectMappers.JSON_MAPPER.readValue(scanner.next().trim(), valueType); + return parsedResponse; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + private final class SSEIterator implements Iterator { + private Scanner sseScanner; + private T nextItem; + private boolean hasNextItem = false; + private boolean endOfStream = false; + private StringBuilder buffer = new StringBuilder(); + private boolean prefixSeen = false; + + private SSEIterator() { + if (sseReader != null && !isStreamClosed()) { + this.sseScanner = new Scanner(sseReader); + } else { + this.endOfStream = true; + } + } + + @Override + public boolean hasNext() { + if (isStreamClosed() || endOfStream) { + return false; + } + + if (hasNextItem) { + return true; + } + + return readNextMessage(); + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException("No more elements in stream"); + } + + T result = nextItem; + nextItem = null; + hasNextItem = false; + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + private boolean readNextMessage() { + if (sseScanner == null || isStreamClosed()) { + endOfStream = true; + return false; + } + + try { + while (sseScanner.hasNextLine()) { + String chunk = sseScanner.nextLine(); + buffer.append(chunk).append(NEWLINE); + + int terminatorIndex; + while ((terminatorIndex = buffer.indexOf(messageTerminator)) >= 0) { + String line = buffer.substring(0, terminatorIndex + messageTerminator.length()); + buffer.delete(0, terminatorIndex + messageTerminator.length()); + + line = line.trim(); + if (line.isEmpty()) { + continue; + } + + if (!prefixSeen && line.startsWith(DATA_PREFIX)) { + prefixSeen = true; + line = line.substring(DATA_PREFIX.length()).trim(); + } else if (!prefixSeen) { + continue; + } + + if (streamTerminator != null && line.contains(streamTerminator)) { + endOfStream = true; + return false; + } + + try { + nextItem = ObjectMappers.JSON_MAPPER.readValue(line, valueType); + hasNextItem = true; + prefixSeen = false; + return true; + } catch (Exception parseEx) { + continue; + } + } + } + + endOfStream = true; + return false; + + } catch (Exception e) { + System.err.println("Failed to parse SSE stream: " + e.getMessage()); + endOfStream = true; + return false; + } + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/Suppliers.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/Suppliers.java new file mode 100644 index 00000000000..a3c24e96857 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/core/Suppliers.java @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +public final class Suppliers { + private Suppliers() {} + + public static Supplier memoize(Supplier delegate) { + AtomicReference value = new AtomicReference<>(); + return () -> { + T val = value.get(); + if (val == null) { + val = value.updateAndGet(cur -> cur == null ? Objects.requireNonNull(delegate.get()) : cur); + } + return val; + }; + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/requests/NonNullableObject.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/requests/NonNullableObject.java new file mode 100644 index 00000000000..c88c0350432 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/requests/NonNullableObject.java @@ -0,0 +1,196 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +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.api.core.ObjectMappers; +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) +@JsonDeserialize(builder = NonNullableObject.Builder.class) +public final class NonNullableObject { + private final String id; + + private final Optional nonNullableObjectId; + + private final Optional name; + + private final Optional age; + + private final Map additionalProperties; + + private NonNullableObject( + String id, + Optional nonNullableObjectId, + Optional name, + Optional age, + Map additionalProperties) { + this.id = id; + this.nonNullableObjectId = nonNullableObjectId; + this.name = name; + this.age = age; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("id") + public Optional getNonNullableObjectId() { + return nonNullableObjectId; + } + + @JsonProperty("name") + public Optional getName() { + return name; + } + + @JsonProperty("age") + public Optional getAge() { + return age; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof NonNullableObject && equalTo((NonNullableObject) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(NonNullableObject other) { + return id.equals(other.id) + && nonNullableObjectId.equals(other.nonNullableObjectId) + && name.equals(other.name) + && age.equals(other.age); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.nonNullableObjectId, this.name, this.age); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + _FinalStage id(@NotNull String id); + + Builder from(NonNullableObject other); + } + + public interface _FinalStage { + NonNullableObject build(); + + _FinalStage nonNullableObjectId(Optional nonNullableObjectId); + + _FinalStage nonNullableObjectId(String nonNullableObjectId); + + _FinalStage name(Optional name); + + _FinalStage name(String name); + + _FinalStage age(Optional age); + + _FinalStage age(Integer age); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements IdStage, _FinalStage { + private String id; + + private Optional age = Optional.empty(); + + private Optional name = Optional.empty(); + + private Optional nonNullableObjectId = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(NonNullableObject other) { + id(other.getId()); + nonNullableObjectId(other.getNonNullableObjectId()); + name(other.getName()); + age(other.getAge()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public _FinalStage id(@NotNull String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage age(Integer age) { + this.age = Optional.ofNullable(age); + return this; + } + + @java.lang.Override + @JsonSetter(value = "age", nulls = Nulls.SKIP) + public _FinalStage age(Optional age) { + this.age = age; + return this; + } + + @java.lang.Override + public _FinalStage name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + @java.lang.Override + @JsonSetter(value = "name", nulls = Nulls.SKIP) + public _FinalStage name(Optional name) { + this.name = name; + return this; + } + + @java.lang.Override + public _FinalStage nonNullableObjectId(String nonNullableObjectId) { + this.nonNullableObjectId = Optional.ofNullable(nonNullableObjectId); + return this; + } + + @java.lang.Override + @JsonSetter(value = "id", nulls = Nulls.SKIP) + public _FinalStage nonNullableObjectId(Optional nonNullableObjectId) { + this.nonNullableObjectId = nonNullableObjectId; + return this; + } + + @java.lang.Override + public NonNullableObject build() { + return new NonNullableObject(id, nonNullableObjectId, name, age, additionalProperties); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/requests/PostWithNullableNamedRequestBodyTypeRequest.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/requests/PostWithNullableNamedRequestBodyTypeRequest.java new file mode 100644 index 00000000000..69007eefb34 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/requests/PostWithNullableNamedRequestBodyTypeRequest.java @@ -0,0 +1,135 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +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.api.core.ObjectMappers; +import com.seed.api.types.NullableObject; +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) +@JsonDeserialize(builder = PostWithNullableNamedRequestBodyTypeRequest.Builder.class) +public final class PostWithNullableNamedRequestBodyTypeRequest { + private final String id; + + private final Optional body; + + private final Map additionalProperties; + + private PostWithNullableNamedRequestBodyTypeRequest( + String id, Optional body, Map additionalProperties) { + this.id = id; + this.body = body; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("body") + public Optional getBody() { + return body; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof PostWithNullableNamedRequestBodyTypeRequest + && equalTo((PostWithNullableNamedRequestBodyTypeRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(PostWithNullableNamedRequestBodyTypeRequest other) { + return id.equals(other.id) && body.equals(other.body); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.body); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + _FinalStage id(@NotNull String id); + + Builder from(PostWithNullableNamedRequestBodyTypeRequest other); + } + + public interface _FinalStage { + PostWithNullableNamedRequestBodyTypeRequest build(); + + _FinalStage body(Optional body); + + _FinalStage body(NullableObject body); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements IdStage, _FinalStage { + private String id; + + private Optional body = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(PostWithNullableNamedRequestBodyTypeRequest other) { + id(other.getId()); + body(other.getBody()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public _FinalStage id(@NotNull String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage body(NullableObject body) { + this.body = Optional.ofNullable(body); + return this; + } + + @java.lang.Override + @JsonSetter(value = "body", nulls = Nulls.SKIP) + public _FinalStage body(Optional body) { + this.body = body; + return this; + } + + @java.lang.Override + public PostWithNullableNamedRequestBodyTypeRequest build() { + return new PostWithNullableNamedRequestBodyTypeRequest(id, body, additionalProperties); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/types/NullableObject.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/types/NullableObject.java new file mode 100644 index 00000000000..00c392cc04a --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/types/NullableObject.java @@ -0,0 +1,143 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +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.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = NullableObject.Builder.class) +public final class NullableObject { + private final Optional id; + + private final Optional name; + + private final Optional age; + + private final Map additionalProperties; + + private NullableObject( + Optional id, + Optional name, + Optional age, + Map additionalProperties) { + this.id = id; + this.name = name; + this.age = age; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("id") + public Optional getId() { + return id; + } + + @JsonProperty("name") + public Optional getName() { + return name; + } + + @JsonProperty("age") + public Optional getAge() { + return age; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof NullableObject && equalTo((NullableObject) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(NullableObject other) { + return id.equals(other.id) && name.equals(other.name) && age.equals(other.age); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.name, this.age); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional id = Optional.empty(); + + private Optional name = Optional.empty(); + + private Optional age = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(NullableObject other) { + id(other.getId()); + name(other.getName()); + age(other.getAge()); + return this; + } + + @JsonSetter(value = "id", nulls = Nulls.SKIP) + public Builder id(Optional id) { + this.id = id; + return this; + } + + public Builder id(String id) { + this.id = Optional.ofNullable(id); + return this; + } + + @JsonSetter(value = "name", nulls = Nulls.SKIP) + public Builder name(Optional name) { + this.name = name; + return this; + } + + public Builder name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + @JsonSetter(value = "age", nulls = Nulls.SKIP) + public Builder age(Optional age) { + this.age = age; + return this; + } + + public Builder age(Integer age) { + this.age = Optional.ofNullable(age); + return this; + } + + public NullableObject build() { + return new NullableObject(id, name, age, additionalProperties); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/types/ResponseBody.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/types/ResponseBody.java new file mode 100644 index 00000000000..83c1f59df5a --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/seed/api/types/ResponseBody.java @@ -0,0 +1,95 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +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.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = ResponseBody.Builder.class) +public final class ResponseBody { + private final Optional success; + + private final Map additionalProperties; + + private ResponseBody(Optional success, Map additionalProperties) { + this.success = success; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("success") + public Optional getSuccess() { + return success; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof ResponseBody && equalTo((ResponseBody) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(ResponseBody other) { + return success.equals(other.success); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.success); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional success = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(ResponseBody other) { + success(other.getSuccess()); + return this; + } + + @JsonSetter(value = "success", nulls = Nulls.SKIP) + public Builder success(Optional success) { + this.success = success; + return this; + } + + public Builder success(Boolean success) { + this.success = Optional.ofNullable(success); + return this; + } + + public ResponseBody build() { + return new ResponseBody(success, additionalProperties); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/snippets/Example0.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/snippets/Example0.java new file mode 100644 index 00000000000..a750eecd7be --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/snippets/Example0.java @@ -0,0 +1,29 @@ +package com.snippets; + +import com.seed.api.Foo; +import com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest; +import com.seed.api.types.NullableObject; + +public class Example0 { + public static void main(String[] args) { + Foo client = Foo + .builder() + .url("https://api.fern.com") + .build(); + + client.postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest + .builder() + .id("id") + .body( + NullableObject + .builder() + .id("id") + .name("name") + .age(1) + .build() + ) + .build() + ); + } +} \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/snippets/Example1.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/snippets/Example1.java new file mode 100644 index 00000000000..5c381f285d3 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/snippets/Example1.java @@ -0,0 +1,20 @@ +package com.snippets; + +import com.seed.api.Foo; +import com.seed.api.requests.NonNullableObject; + +public class Example1 { + public static void main(String[] args) { + Foo client = Foo + .builder() + .url("https://api.fern.com") + .build(); + + client.postWithNonNullableNamedRequestBodyType( + NonNullableObject + .builder() + .id("id") + .build() + ); + } +} \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/snippets/Example2.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/snippets/Example2.java new file mode 100644 index 00000000000..18bbf98e824 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/main/java/com/snippets/Example2.java @@ -0,0 +1,23 @@ +package com.snippets; + +import com.seed.api.Foo; +import com.seed.api.requests.NonNullableObject; + +public class Example2 { + public static void main(String[] args) { + Foo client = Foo + .builder() + .url("https://api.fern.com") + .build(); + + client.postWithNonNullableNamedRequestBodyType( + NonNullableObject + .builder() + .id("id") + .nonNullableObjectId("id") + .name("name") + .age(1) + .build() + ); + } +} \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/test/java/com/seed/api/StreamTest.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/test/java/com/seed/api/StreamTest.java new file mode 100644 index 00000000000..77f138e487f --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/test/java/com/seed/api/StreamTest.java @@ -0,0 +1,86 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import static org.junit.jupiter.api.Assertions.*; + +import com.seed.api.core.ObjectMappers; +import com.seed.api.core.Stream; +import java.io.IOException; +import java.io.StringReader; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +public final class StreamTest { + @Test + public void testJsonStream() { + List messages = List.of(Map.of("message", "hello"), Map.of("message", "world")); + List jsonStrings = messages.stream().map(StreamTest::mapToJson).collect(Collectors.toList()); + String input = String.join("\n", jsonStrings); + StringReader jsonInput = new StringReader(input); + Stream jsonStream = Stream.fromJson(Map.class, jsonInput); + int expectedMessages = 2; + int actualMessages = 0; + for (Map jsonObject : jsonStream) { + actualMessages++; + assertTrue(jsonObject.containsKey("message")); + } + assertEquals(expectedMessages, actualMessages); + } + + @Test + public void testSseStream() { + List events = List.of(Map.of("event", "start"), Map.of("event", "end")); + List sseStrings = events.stream().map(StreamTest::mapToSse).collect(Collectors.toList()); + String input = String.join("\n" + "\n", sseStrings); + StringReader sseInput = new StringReader(input); + Stream sseStream = Stream.fromSse(Map.class, sseInput); + int expectedEvents = 2; + int actualEvents = 0; + for (Map eventData : sseStream) { + actualEvents++; + assertTrue(eventData.containsKey("event")); + } + assertEquals(expectedEvents, actualEvents); + } + + @Test + public void testSseStreamWithTerminator() { + List events = List.of(Map.of("message", "first"), Map.of("message", "second")); + List sseStrings = events.stream().map(StreamTest::mapToSse).collect(Collectors.toList()); + sseStrings.add("data: [DONE]"); + String input = String.join("\n" + "\n", sseStrings); + StringReader sseInput = new StringReader(input); + Stream sseStream = Stream.fromSse(Map.class, sseInput, "[DONE]"); + int expectedEvents = 2; + int actualEvents = 0; + for (Map eventData : sseStream) { + actualEvents++; + assertTrue(eventData.containsKey("message")); + } + assertEquals(expectedEvents, actualEvents); + } + + @Test + public void testStreamResourceManagement() throws IOException { + StringReader testInput = new StringReader("{\"test\":\"data\"}"); + Stream testStream = Stream.fromJson(Map.class, testInput); + testStream.close(); + assertFalse(testStream.iterator().hasNext()); + } + + private static String mapToJson(Map map) { + try { + return ObjectMappers.JSON_MAPPER.writeValueAsString(map); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static String mapToSse(Map map) { + return "data: " + mapToJson(map); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/test/java/com/seed/api/TestClient.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/test/java/com/seed/api/TestClient.java new file mode 100644 index 00000000000..1686cfd803c --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/test/java/com/seed/api/TestClient.java @@ -0,0 +1,11 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +public final class TestClient { + public void test() { + // Add tests here and mark this file in .fernignore + assert true; + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/custom-config/src/test/java/com/seed/api/core/QueryStringMapperTest.java b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/test/java/com/seed/api/core/QueryStringMapperTest.java new file mode 100644 index 00000000000..ead1a49af7a --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/custom-config/src/test/java/com/seed/api/core/QueryStringMapperTest.java @@ -0,0 +1,339 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.HttpUrl; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public final class QueryStringMapperTest { + @Test + public void testObjectWithQuotedString_indexedArrays() { + Map map = new HashMap() { + { + put("hello", "\"world\""); + } + }; + + String expectedQueryString = "withquoted%5Bhello%5D=%22world%22"; + + String actualQueryString = queryString( + new HashMap() { + { + put("withquoted", map); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectWithQuotedString_arraysAsRepeats() { + Map map = new HashMap() { + { + put("hello", "\"world\""); + } + }; + + String expectedQueryString = "withquoted%5Bhello%5D=%22world%22"; + + String actualQueryString = queryString( + new HashMap() { + { + put("withquoted", map); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObject_indexedArrays() { + Map map = new HashMap() { + { + put("foo", "bar"); + put("baz", "qux"); + } + }; + + String expectedQueryString = "metadata%5Bfoo%5D=bar&metadata%5Bbaz%5D=qux"; + + String actualQueryString = queryString( + new HashMap() { + { + put("metadata", map); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObject_arraysAsRepeats() { + Map map = new HashMap() { + { + put("foo", "bar"); + put("baz", "qux"); + } + }; + + String expectedQueryString = "metadata%5Bfoo%5D=bar&metadata%5Bbaz%5D=qux"; + + String actualQueryString = queryString( + new HashMap() { + { + put("metadata", map); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testNestedObject_indexedArrays() { + Map> nestedMap = new HashMap>() { + { + put("mapkey1", new HashMap() { + { + put("mapkey1mapkey1", "mapkey1mapkey1value"); + put("mapkey1mapkey2", "mapkey1mapkey2value"); + } + }); + put("mapkey2", new HashMap() { + { + put("mapkey2mapkey1", "mapkey2mapkey1value"); + } + }); + } + }; + + String expectedQueryString = + "nested%5Bmapkey2%5D%5Bmapkey2mapkey1%5D=mapkey2mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey1" + + "%5D=mapkey1mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey2%5D=mapkey1mapkey2value"; + + String actualQueryString = queryString( + new HashMap() { + { + put("nested", nestedMap); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testNestedObject_arraysAsRepeats() { + Map> nestedMap = new HashMap>() { + { + put("mapkey1", new HashMap() { + { + put("mapkey1mapkey1", "mapkey1mapkey1value"); + put("mapkey1mapkey2", "mapkey1mapkey2value"); + } + }); + put("mapkey2", new HashMap() { + { + put("mapkey2mapkey1", "mapkey2mapkey1value"); + } + }); + } + }; + + String expectedQueryString = + "nested%5Bmapkey2%5D%5Bmapkey2mapkey1%5D=mapkey2mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey1" + + "%5D=mapkey1mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey2%5D=mapkey1mapkey2value"; + + String actualQueryString = queryString( + new HashMap() { + { + put("nested", nestedMap); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testDateTime_indexedArrays() { + OffsetDateTime dateTime = + OffsetDateTime.ofInstant(Instant.ofEpochSecond(1740412107L), ZoneId.of("America/New_York")); + + String expectedQueryString = "datetime=2025-02-24T10%3A48%3A27-05%3A00"; + + String actualQueryString = queryString( + new HashMap() { + { + put("datetime", dateTime); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testDateTime_arraysAsRepeats() { + OffsetDateTime dateTime = + OffsetDateTime.ofInstant(Instant.ofEpochSecond(1740412107L), ZoneId.of("America/New_York")); + + String expectedQueryString = "datetime=2025-02-24T10%3A48%3A27-05%3A00"; + + String actualQueryString = queryString( + new HashMap() { + { + put("datetime", dateTime); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectArray_indexedArrays() { + List> mapArray = new ArrayList>() { + { + add(new HashMap() { + { + put("key", "hello"); + put("value", "world"); + } + }); + add(new HashMap() { + { + put("key", "foo"); + put("value", "bar"); + } + }); + add(new HashMap<>()); + } + }; + + String expectedQueryString = "objects%5B0%5D%5Bvalue%5D=world&objects%5B0%5D%5Bkey%5D=hello&objects%5B1%5D" + + "%5Bvalue%5D=bar&objects%5B1%5D%5Bkey%5D=foo"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objects", mapArray); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectArray_arraysAsRepeats() { + List> mapArray = new ArrayList>() { + { + add(new HashMap() { + { + put("key", "hello"); + put("value", "world"); + } + }); + add(new HashMap() { + { + put("key", "foo"); + put("value", "bar"); + } + }); + add(new HashMap<>()); + } + }; + + String expectedQueryString = + "objects%5Bvalue%5D=world&objects%5Bkey%5D=hello&objects%5Bvalue" + "%5D=bar&objects%5Bkey%5D=foo"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objects", mapArray); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectWithArray_indexedArrays() { + Map objectWithArray = new HashMap() { + { + put("id", "abc123"); + put("contactIds", new ArrayList() { + { + add("id1"); + add("id2"); + add("id3"); + } + }); + } + }; + + String expectedQueryString = + "objectwitharray%5Bid%5D=abc123&objectwitharray%5BcontactIds%5D%5B0%5D=id1&objectwitharray" + + "%5BcontactIds%5D%5B1%5D=id2&objectwitharray%5BcontactIds%5D%5B2%5D=id3"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objectwitharray", objectWithArray); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectWithArray_arraysAsRepeats() { + Map objectWithArray = new HashMap() { + { + put("id", "abc123"); + put("contactIds", new ArrayList() { + { + add("id1"); + add("id2"); + add("id3"); + } + }); + } + }; + + String expectedQueryString = "objectwitharray%5Bid%5D=abc123&objectwitharray%5BcontactIds" + + "%5D=id1&objectwitharray%5BcontactIds%5D=id2&objectwitharray%5BcontactIds%5D=id3"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objectwitharray", objectWithArray); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + private static String queryString(Map params, boolean arraysAsRepeats) { + HttpUrl.Builder httpUrl = HttpUrl.parse("http://www.fakewebsite.com/").newBuilder(); + params.forEach((paramName, paramValue) -> + QueryStringMapper.addQueryParameter(httpUrl, paramName, paramValue, arraysAsRepeats)); + return httpUrl.build().encodedQuery(); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/.github/workflows/ci.yml b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/.github/workflows/ci.yml new file mode 100644 index 00000000000..7bffd9de06d --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Compile + run: ./gradlew compileJava + + test: + needs: [ compile ] + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Test + run: ./gradlew test + publish: + needs: [ compile, test ] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Publish to maven + run: | + ./gradlew publish + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_PUBLISH_REGISTRY_URL: "" diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/.gitignore b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/.gitignore new file mode 100644 index 00000000000..d4199abc2cd --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/.gitignore @@ -0,0 +1,24 @@ +*.class +.project +.gradle +? +.classpath +.checkstyle +.settings +.node +build + +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Eclipse/IntelliJ APT +generated_src/ +generated_testSrc/ +generated/ + +bin +build \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/README.md b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/README.md new file mode 100644 index 00000000000..a09670a3616 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/README.md @@ -0,0 +1,198 @@ +# Seed Java Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FJava) +[![Maven Central](https://img.shields.io/maven-central/v/com.fern/java-nullable-named-request-types)](https://central.sonatype.com/artifact/com.fern/java-nullable-named-request-types) + +The Seed Java library provides convenient access to the Seed APIs from Java. + +## Installation + +### Gradle + +Add the dependency in your `build.gradle` file: + +```groovy +dependencies { + implementation 'com.fern:java-nullable-named-request-types' +} +``` + +### Maven + +Add the dependency in your `pom.xml` file: + +```xml + + com.fern + java-nullable-named-request-types + 0.0.1 + +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```java +package com.example.usage; + +import com.seed.api.Foo; +import com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest; +import com.seed.api.types.NullableObject; + +public class Example { + public static void main(String[] args) { + Foo client = Foo + .builder() + .build(); + + client.postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest + .builder() + .id("id") + .body( + NullableObject + .builder() + .id("id") + .name("name") + .age(1) + .build() + ) + .build() + ); + } +} +``` + +## Base Url + +You can set a custom base URL when constructing the client. + +```java +import com.seed.api.Foo; + +Foo client = Foo + .builder() + .url("https://example.com") + .build(); +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), an API exception will be thrown. + +```java +import com.seed.api.core.SeedApiApiException; + +try { + client.postWithNullableNamedRequestBodyType(...); +} catch (SeedApiApiException e) { + // Do something with the API exception... +} +``` + +## Advanced + +### Custom Client + +This SDK is built to work with any instance of `OkHttpClient`. By default, if no client is provided, the SDK will construct one. +However, you can pass your own client like so: + +```java +import com.seed.api.Foo; +import okhttp3.OkHttpClient; + +OkHttpClient customClient = ...; + +Foo client = Foo + .builder() + .httpClient(customClient) + .build(); +``` + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `maxRetries` client option to configure this behavior. + +```java +import com.seed.api.Foo; + +Foo client = Foo + .builder() + .maxRetries(1) + .build(); +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level. + +```java +import com.seed.api.Foo; +import com.seed.api.core.RequestOptions; + +// Client level +Foo client = Foo + .builder() + .timeout(10) + .build(); + +// Request level +client.postWithNullableNamedRequestBodyType( + ..., + RequestOptions + .builder() + .timeout(10) + .build() +); +``` + +### Custom Headers + +The SDK allows you to add custom headers to requests. You can configure headers at the client level or at the request level. + +```java +import com.seed.api.Foo; +import com.seed.api.core.RequestOptions; + +// Client level +Foo client = Foo + .builder() + .addHeader("X-Custom-Header", "custom-value") + .addHeader("X-Request-Id", "abc-123") + .build(); +; + +// Request level +client.postWithNullableNamedRequestBodyType( + ..., + RequestOptions + .builder() + .addHeader("X-Request-Header", "request-value") + .build() +); +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/build.gradle b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/build.gradle new file mode 100644 index 00000000000..c4f5285166e --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/build.gradle @@ -0,0 +1,102 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'com.diffplug.spotless' version '6.11.0' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + api 'com.squareup.okhttp3:okhttp:4.12.0' + api 'com.fasterxml.jackson.core:jackson-databind:2.17.2' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.17.2' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.2' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2' +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +tasks.withType(Javadoc) { + failOnError false + options.addStringOption('Xdoclint:none', '-quiet') +} + +spotless { + java { + palantirJavaFormat() + } +} + + +java { + withSourcesJar() + withJavadocJar() +} + + +group = 'com.fern' + +version = '0.0.1' + +jar { + dependsOn(":generatePomFileForMavenPublication") + archiveBaseName = "java-nullable-named-request-types" +} + +sourcesJar { + archiveBaseName = "java-nullable-named-request-types" +} + +javadocJar { + archiveBaseName = "java-nullable-named-request-types" +} + +test { + useJUnitPlatform() + testLogging { + showStandardStreams = true + } +} + +publishing { + publications { + maven(MavenPublication) { + groupId = 'com.fern' + artifactId = 'java-nullable-named-request-types' + version = '0.0.1' + from components.java + pom { + licenses { + license { + name = 'The MIT License (MIT)' + url = 'https://mit-license.org/' + } + } + scm { + connection = 'scm:git:git://github.com/java-nullable-named-request-types/fern.git' + developerConnection = 'scm:git:git://github.com/java-nullable-named-request-types/fern.git' + url = 'https://github.com/java-nullable-named-request-types/fern' + } + } + } + } + repositories { + maven { + url "$System.env.MAVEN_PUBLISH_REGISTRY_URL" + credentials { + username "$System.env.MAVEN_USERNAME" + password "$System.env.MAVEN_PASSWORD" + } + } + } +} + diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/reference.md b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/reference.md new file mode 100644 index 00000000000..4c8a43f68f5 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/reference.md @@ -0,0 +1,130 @@ +# Reference +

client.postWithNullableNamedRequestBodyType(id, request) -> ResponseBody +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```java +client.postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest + .builder() + .id("id") + .body( + NullableObject + .builder() + .id("id") + .name("name") + .age(1) + .build() + ) + .build() +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `String` + +
+
+ +
+
+ +**request:** `Optional` + +
+
+
+
+ + +
+
+
+ +
client.postWithNonNullableNamedRequestBodyType(id, request) -> ResponseBody +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```java +client.postWithNonNullableNamedRequestBodyType( + NonNullableObject + .builder() + .id("id") + .build() +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `String` + +
+
+ +
+
+ +**nonNullableObjectId:** `Optional` + +
+
+ +
+
+ +**name:** `Optional` + +
+
+ +
+
+ +**age:** `Optional` + +
+
+
+
+ + +
+
+
diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/sample-app/build.gradle b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/sample-app/build.gradle new file mode 100644 index 00000000000..4ee8f227b7a --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/sample-app/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java-library' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + implementation rootProject +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/sample-app/src/main/java/sample/App.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/sample-app/src/main/java/sample/App.java new file mode 100644 index 00000000000..9b7a9955991 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/sample-app/src/main/java/sample/App.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package sample; + +import java.lang.String; + +public final class App { + public static void main(String[] args) { + // import com.seed.api.AsyncFoo + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/settings.gradle b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/settings.gradle new file mode 100644 index 00000000000..1663c62bb41 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'java-nullable-named-request-types' + +include 'sample-app' \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/snippet-templates.json b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/snippet.json b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/snippet.json new file mode 100644 index 00000000000..56b51668093 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/snippet.json @@ -0,0 +1,44 @@ +{ + "endpoints": [ + { + "example_identifier": "7d78177e", + "id": { + "method": "POST", + "path": "/postWithNullableNamedRequestBodyType/{id}", + "identifier_override": "endpoint_.postWithNullableNamedRequestBodyType" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.Foo;\nimport com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest;\nimport com.seed.api.types.NullableObject;\n\npublic class Example {\n public static void main(String[] args) {\n Foo client = Foo\n .builder()\n .build();\n\n client.postWithNullableNamedRequestBodyType(\n PostWithNullableNamedRequestBodyTypeRequest\n .builder()\n .id(\"id\")\n .body(\n NullableObject\n .builder()\n .id(\"id\")\n .name(\"name\")\n .age(1)\n .build()\n )\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.Foo;\nimport com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest;\nimport com.seed.api.types.NullableObject;\n\npublic class Example {\n public static void main(String[] args) {\n Foo client = Foo\n .builder()\n .build();\n\n client.postWithNullableNamedRequestBodyType(\n PostWithNullableNamedRequestBodyTypeRequest\n .builder()\n .id(\"id\")\n .body(\n NullableObject\n .builder()\n .id(\"id\")\n .name(\"name\")\n .age(1)\n .build()\n )\n .build()\n );\n }\n}\n" + } + }, + { + "example_identifier": "8a8806b8", + "id": { + "method": "POST", + "path": "/postWithNonNullableNamedRequestBodyType/{id}", + "identifier_override": "endpoint_.postWithNonNullableNamedRequestBodyType" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.Foo;\nimport com.seed.api.requests.NonNullableObject;\n\npublic class Example {\n public static void main(String[] args) {\n Foo client = Foo\n .builder()\n .build();\n\n client.postWithNonNullableNamedRequestBodyType(\n NonNullableObject\n .builder()\n .id(\"id\")\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.Foo;\nimport com.seed.api.requests.NonNullableObject;\n\npublic class Example {\n public static void main(String[] args) {\n Foo client = Foo\n .builder()\n .build();\n\n client.postWithNonNullableNamedRequestBodyType(\n NonNullableObject\n .builder()\n .id(\"id\")\n .build()\n );\n }\n}\n" + } + }, + { + "example_identifier": "39492db8", + "id": { + "method": "POST", + "path": "/postWithNonNullableNamedRequestBodyType/{id}", + "identifier_override": "endpoint_.postWithNonNullableNamedRequestBodyType" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.Foo;\nimport com.seed.api.requests.NonNullableObject;\n\npublic class Example {\n public static void main(String[] args) {\n Foo client = Foo\n .builder()\n .build();\n\n client.postWithNonNullableNamedRequestBodyType(\n NonNullableObject\n .builder()\n .id(\"id\")\n .nonNullableObjectId(\"id\")\n .name(\"name\")\n .age(1)\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.Foo;\nimport com.seed.api.requests.NonNullableObject;\n\npublic class Example {\n public static void main(String[] args) {\n Foo client = Foo\n .builder()\n .build();\n\n client.postWithNonNullableNamedRequestBodyType(\n NonNullableObject\n .builder()\n .id(\"id\")\n .nonNullableObjectId(\"id\")\n .name(\"name\")\n .age(1)\n .build()\n );\n }\n}\n" + } + } + ], + "types": {} +} \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/AsyncFoo.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/AsyncFoo.java new file mode 100644 index 00000000000..7860a67f04e --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/AsyncFoo.java @@ -0,0 +1,56 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.RequestOptions; +import com.seed.api.requests.NonNullableObject; +import com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest; +import com.seed.api.types.ResponseBody; +import java.util.concurrent.CompletableFuture; + +public class AsyncFoo { + protected final ClientOptions clientOptions; + + private final AsyncRawFoo rawClient; + + public AsyncFoo(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new AsyncRawFoo(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public AsyncRawFoo withRawResponse() { + return this.rawClient; + } + + public CompletableFuture postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest request) { + return this.rawClient.postWithNullableNamedRequestBodyType(request).thenApply(response -> response.body()); + } + + public CompletableFuture postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest request, RequestOptions requestOptions) { + return this.rawClient + .postWithNullableNamedRequestBodyType(request, requestOptions) + .thenApply(response -> response.body()); + } + + public CompletableFuture postWithNonNullableNamedRequestBodyType(NonNullableObject request) { + return this.rawClient.postWithNonNullableNamedRequestBodyType(request).thenApply(response -> response.body()); + } + + public CompletableFuture postWithNonNullableNamedRequestBodyType( + NonNullableObject request, RequestOptions requestOptions) { + return this.rawClient + .postWithNonNullableNamedRequestBodyType(request, requestOptions) + .thenApply(response -> response.body()); + } + + public static AsyncFooBuilder builder() { + return new AsyncFooBuilder(); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/AsyncFooBuilder.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/AsyncFooBuilder.java new file mode 100644 index 00000000000..76ab1a2dd8d --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/AsyncFooBuilder.java @@ -0,0 +1,165 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.Environment; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import okhttp3.OkHttpClient; + +public class AsyncFooBuilder { + private Optional timeout = Optional.empty(); + + private Optional maxRetries = Optional.empty(); + + private final Map customHeaders = new HashMap<>(); + + private Environment environment; + + private OkHttpClient httpClient; + + public AsyncFooBuilder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + /** + * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. + */ + public AsyncFooBuilder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Sets the maximum number of retries for the client. Defaults to 2 retries. + */ + public AsyncFooBuilder maxRetries(int maxRetries) { + this.maxRetries = Optional.of(maxRetries); + return this; + } + + /** + * Sets the underlying OkHttp client + */ + public AsyncFooBuilder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + /** + * Add a custom header to be sent with all requests. + * For headers that need to be computed dynamically or conditionally, use the setAdditional() method override instead. + * + * @param name The header name + * @param value The header value + * @return This builder for method chaining + */ + public AsyncFooBuilder addHeader(String name, String value) { + this.customHeaders.put(name, value); + return this; + } + + protected ClientOptions buildClientOptions() { + ClientOptions.Builder builder = ClientOptions.builder(); + setEnvironment(builder); + setHttpClient(builder); + setTimeouts(builder); + setRetries(builder); + for (Map.Entry header : this.customHeaders.entrySet()) { + builder.addHeader(header.getKey(), header.getValue()); + } + setAdditional(builder); + return builder.build(); + } + + /** + * Sets the environment configuration for the client. + * Override this method to modify URLs or add environment-specific logic. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setEnvironment(ClientOptions.Builder builder) { + builder.environment(this.environment); + } + + /** + * Sets the request timeout configuration. + * Override this method to customize timeout behavior. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setTimeouts(ClientOptions.Builder builder) { + if (this.timeout.isPresent()) { + builder.timeout(this.timeout.get()); + } + } + + /** + * Sets the retry configuration for failed requests. + * Override this method to implement custom retry strategies. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setRetries(ClientOptions.Builder builder) { + if (this.maxRetries.isPresent()) { + builder.maxRetries(this.maxRetries.get()); + } + } + + /** + * Sets the OkHttp client configuration. + * Override this method to customize HTTP client behavior (interceptors, connection pools, etc). + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setHttpClient(ClientOptions.Builder builder) { + if (this.httpClient != null) { + builder.httpClient(this.httpClient); + } + } + + /** + * Override this method to add any additional configuration to the client. + * This method is called at the end of the configuration chain, allowing you to add + * custom headers, modify settings, or perform any other client customization. + * + * @param builder The ClientOptions.Builder to configure + * + * Example: + *
{@code
+     * @Override
+     * protected void setAdditional(ClientOptions.Builder builder) {
+     *     builder.addHeader("X-Request-ID", () -> UUID.randomUUID().toString());
+     *     builder.addHeader("X-Client-Version", "1.0.0");
+     * }
+     * }
+ */ + protected void setAdditional(ClientOptions.Builder builder) {} + + /** + * Override this method to add custom validation logic before the client is built. + * This method is called at the beginning of the build() method to ensure the configuration is valid. + * Throw an exception to prevent client creation if validation fails. + * + * Example: + *
{@code
+     * @Override
+     * protected void validateConfiguration() {
+     *     super.validateConfiguration(); // Run parent validations
+     *     if (tenantId == null || tenantId.isEmpty()) {
+     *         throw new IllegalStateException("tenantId is required");
+     *     }
+     * }
+     * }
+ */ + protected void validateConfiguration() {} + + public AsyncFoo build() { + validateConfiguration(); + return new AsyncFoo(buildClientOptions()); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/AsyncRawFoo.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/AsyncRawFoo.java new file mode 100644 index 00000000000..69ee0d9f1c1 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/AsyncRawFoo.java @@ -0,0 +1,160 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.seed.api.core.ClientOptions; +import com.seed.api.core.FooApiException; +import com.seed.api.core.FooException; +import com.seed.api.core.FooHttpResponse; +import com.seed.api.core.MediaTypes; +import com.seed.api.core.ObjectMappers; +import com.seed.api.core.RequestOptions; +import com.seed.api.requests.NonNullableObject; +import com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest; +import com.seed.api.types.ResponseBody; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.jetbrains.annotations.NotNull; + +public class AsyncRawFoo { + protected final ClientOptions clientOptions; + + public AsyncRawFoo(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + public CompletableFuture> postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest request) { + return postWithNullableNamedRequestBodyType(request, null); + } + + public CompletableFuture> postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("postWithNullableNamedRequestBodyType") + .addPathSegment(request.getId()) + .build(); + RequestBody body; + try { + body = RequestBody.create("", null); + if (request.isPresent()) { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request.getBody()), MediaTypes.APPLICATION_JSON); + } + } catch (JsonProcessingException e) { + throw new FooException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (okhttp3.ResponseBody responseBody = response.body()) { + if (response.isSuccessful()) { + future.complete(new FooHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), ResponseBody.class), + response)); + return; + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + future.completeExceptionally(new FooApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response)); + return; + } catch (IOException e) { + future.completeExceptionally(new FooException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new FooException("Network error executing HTTP request", e)); + } + }); + return future; + } + + public CompletableFuture> postWithNonNullableNamedRequestBodyType( + NonNullableObject request) { + return postWithNonNullableNamedRequestBodyType(request, null); + } + + public CompletableFuture> postWithNonNullableNamedRequestBodyType( + NonNullableObject request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("postWithNonNullableNamedRequestBodyType") + .addPathSegment(request.getId()) + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new FooException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (okhttp3.ResponseBody responseBody = response.body()) { + if (response.isSuccessful()) { + future.complete(new FooHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), ResponseBody.class), + response)); + return; + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + future.completeExceptionally(new FooApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response)); + return; + } catch (IOException e) { + future.completeExceptionally(new FooException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new FooException("Network error executing HTTP request", e)); + } + }); + return future; + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/Foo.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/Foo.java new file mode 100644 index 00000000000..c7763da00d4 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/Foo.java @@ -0,0 +1,54 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.RequestOptions; +import com.seed.api.requests.NonNullableObject; +import com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest; +import com.seed.api.types.ResponseBody; + +public class Foo { + protected final ClientOptions clientOptions; + + private final RawFoo rawClient; + + public Foo(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new RawFoo(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public RawFoo withRawResponse() { + return this.rawClient; + } + + public ResponseBody postWithNullableNamedRequestBodyType(PostWithNullableNamedRequestBodyTypeRequest request) { + return this.rawClient.postWithNullableNamedRequestBodyType(request).body(); + } + + public ResponseBody postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest request, RequestOptions requestOptions) { + return this.rawClient + .postWithNullableNamedRequestBodyType(request, requestOptions) + .body(); + } + + public ResponseBody postWithNonNullableNamedRequestBodyType(NonNullableObject request) { + return this.rawClient.postWithNonNullableNamedRequestBodyType(request).body(); + } + + public ResponseBody postWithNonNullableNamedRequestBodyType( + NonNullableObject request, RequestOptions requestOptions) { + return this.rawClient + .postWithNonNullableNamedRequestBodyType(request, requestOptions) + .body(); + } + + public static FooBuilder builder() { + return new FooBuilder(); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/FooBuilder.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/FooBuilder.java new file mode 100644 index 00000000000..578774bf39b --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/FooBuilder.java @@ -0,0 +1,165 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.Environment; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import okhttp3.OkHttpClient; + +public class FooBuilder { + private Optional timeout = Optional.empty(); + + private Optional maxRetries = Optional.empty(); + + private final Map customHeaders = new HashMap<>(); + + private Environment environment; + + private OkHttpClient httpClient; + + public FooBuilder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + /** + * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. + */ + public FooBuilder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Sets the maximum number of retries for the client. Defaults to 2 retries. + */ + public FooBuilder maxRetries(int maxRetries) { + this.maxRetries = Optional.of(maxRetries); + return this; + } + + /** + * Sets the underlying OkHttp client + */ + public FooBuilder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + /** + * Add a custom header to be sent with all requests. + * For headers that need to be computed dynamically or conditionally, use the setAdditional() method override instead. + * + * @param name The header name + * @param value The header value + * @return This builder for method chaining + */ + public FooBuilder addHeader(String name, String value) { + this.customHeaders.put(name, value); + return this; + } + + protected ClientOptions buildClientOptions() { + ClientOptions.Builder builder = ClientOptions.builder(); + setEnvironment(builder); + setHttpClient(builder); + setTimeouts(builder); + setRetries(builder); + for (Map.Entry header : this.customHeaders.entrySet()) { + builder.addHeader(header.getKey(), header.getValue()); + } + setAdditional(builder); + return builder.build(); + } + + /** + * Sets the environment configuration for the client. + * Override this method to modify URLs or add environment-specific logic. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setEnvironment(ClientOptions.Builder builder) { + builder.environment(this.environment); + } + + /** + * Sets the request timeout configuration. + * Override this method to customize timeout behavior. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setTimeouts(ClientOptions.Builder builder) { + if (this.timeout.isPresent()) { + builder.timeout(this.timeout.get()); + } + } + + /** + * Sets the retry configuration for failed requests. + * Override this method to implement custom retry strategies. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setRetries(ClientOptions.Builder builder) { + if (this.maxRetries.isPresent()) { + builder.maxRetries(this.maxRetries.get()); + } + } + + /** + * Sets the OkHttp client configuration. + * Override this method to customize HTTP client behavior (interceptors, connection pools, etc). + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setHttpClient(ClientOptions.Builder builder) { + if (this.httpClient != null) { + builder.httpClient(this.httpClient); + } + } + + /** + * Override this method to add any additional configuration to the client. + * This method is called at the end of the configuration chain, allowing you to add + * custom headers, modify settings, or perform any other client customization. + * + * @param builder The ClientOptions.Builder to configure + * + * Example: + *
{@code
+     * @Override
+     * protected void setAdditional(ClientOptions.Builder builder) {
+     *     builder.addHeader("X-Request-ID", () -> UUID.randomUUID().toString());
+     *     builder.addHeader("X-Client-Version", "1.0.0");
+     * }
+     * }
+ */ + protected void setAdditional(ClientOptions.Builder builder) {} + + /** + * Override this method to add custom validation logic before the client is built. + * This method is called at the beginning of the build() method to ensure the configuration is valid. + * Throw an exception to prevent client creation if validation fails. + * + * Example: + *
{@code
+     * @Override
+     * protected void validateConfiguration() {
+     *     super.validateConfiguration(); // Run parent validations
+     *     if (tenantId == null || tenantId.isEmpty()) {
+     *         throw new IllegalStateException("tenantId is required");
+     *     }
+     * }
+     * }
+ */ + protected void validateConfiguration() {} + + public Foo build() { + validateConfiguration(); + return new Foo(buildClientOptions()); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/RawFoo.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/RawFoo.java new file mode 100644 index 00000000000..fb2e6f0a065 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/RawFoo.java @@ -0,0 +1,127 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.seed.api.core.ClientOptions; +import com.seed.api.core.FooApiException; +import com.seed.api.core.FooException; +import com.seed.api.core.FooHttpResponse; +import com.seed.api.core.MediaTypes; +import com.seed.api.core.ObjectMappers; +import com.seed.api.core.RequestOptions; +import com.seed.api.requests.NonNullableObject; +import com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest; +import com.seed.api.types.ResponseBody; +import java.io.IOException; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class RawFoo { + protected final ClientOptions clientOptions; + + public RawFoo(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + public FooHttpResponse postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest request) { + return postWithNullableNamedRequestBodyType(request, null); + } + + public FooHttpResponse postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("postWithNullableNamedRequestBodyType") + .addPathSegment(request.getId()) + .build(); + RequestBody body; + try { + body = RequestBody.create("", null); + if (request.isPresent()) { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request.getBody()), MediaTypes.APPLICATION_JSON); + } + } catch (JsonProcessingException e) { + throw new FooException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + okhttp3.ResponseBody responseBody = response.body(); + if (response.isSuccessful()) { + return new FooHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), ResponseBody.class), response); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new FooApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response); + } catch (IOException e) { + throw new FooException("Network error executing HTTP request", e); + } + } + + public FooHttpResponse postWithNonNullableNamedRequestBodyType(NonNullableObject request) { + return postWithNonNullableNamedRequestBodyType(request, null); + } + + public FooHttpResponse postWithNonNullableNamedRequestBodyType( + NonNullableObject request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("postWithNonNullableNamedRequestBodyType") + .addPathSegment(request.getId()) + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new FooException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + okhttp3.ResponseBody responseBody = response.body(); + if (response.isSuccessful()) { + return new FooHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), ResponseBody.class), response); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new FooApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response); + } catch (IOException e) { + throw new FooException("Network error executing HTTP request", e); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/ClientOptions.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/ClientOptions.java new file mode 100644 index 00000000000..cbf9de67067 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/ClientOptions.java @@ -0,0 +1,180 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import okhttp3.OkHttpClient; + +public final class ClientOptions { + private final Environment environment; + + private final Map headers; + + private final Map> headerSuppliers; + + private final OkHttpClient httpClient; + + private final int timeout; + + private ClientOptions( + Environment environment, + Map headers, + Map> headerSuppliers, + OkHttpClient httpClient, + int timeout) { + this.environment = environment; + this.headers = new HashMap<>(); + this.headers.putAll(headers); + this.headers.putAll(new HashMap() { + { + put("User-Agent", "com.fern:java-nullable-named-request-types/0.0.1"); + put("X-Fern-Language", "JAVA"); + } + }); + this.headerSuppliers = headerSuppliers; + this.httpClient = httpClient; + this.timeout = timeout; + } + + public Environment environment() { + return this.environment; + } + + public Map headers(RequestOptions requestOptions) { + Map values = new HashMap<>(this.headers); + headerSuppliers.forEach((key, supplier) -> { + values.put(key, supplier.get()); + }); + if (requestOptions != null) { + values.putAll(requestOptions.getHeaders()); + } + return values; + } + + public int timeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.timeout; + } + return requestOptions.getTimeout().orElse(this.timeout); + } + + public OkHttpClient httpClient() { + return this.httpClient; + } + + public OkHttpClient httpClientWithTimeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.httpClient; + } + return this.httpClient + .newBuilder() + .callTimeout(requestOptions.getTimeout().get(), requestOptions.getTimeoutTimeUnit()) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .build(); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Environment environment; + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + private int maxRetries = 2; + + private Optional timeout = Optional.empty(); + + private OkHttpClient httpClient = null; + + public Builder environment(Environment environment) { + this.environment = environment; + return this; + } + + public Builder addHeader(String key, String value) { + this.headers.put(key, value); + return this; + } + + public Builder addHeader(String key, Supplier value) { + this.headerSuppliers.put(key, value); + return this; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(Optional timeout) { + this.timeout = timeout; + return this; + } + + /** + * Override the maximum number of retries. Defaults to 2 retries. + */ + public Builder maxRetries(int maxRetries) { + this.maxRetries = maxRetries; + return this; + } + + public Builder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + public ClientOptions build() { + OkHttpClient.Builder httpClientBuilder = + this.httpClient != null ? this.httpClient.newBuilder() : new OkHttpClient.Builder(); + + if (this.httpClient != null) { + timeout.ifPresent(timeout -> httpClientBuilder + .callTimeout(timeout, TimeUnit.SECONDS) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS)); + } else { + httpClientBuilder + .callTimeout(this.timeout.orElse(60), TimeUnit.SECONDS) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .addInterceptor(new RetryInterceptor(this.maxRetries)); + } + + this.httpClient = httpClientBuilder.build(); + this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); + + return new ClientOptions(environment, headers, headerSuppliers, httpClient, this.timeout.get()); + } + + /** + * Create a new Builder initialized with values from an existing ClientOptions + */ + public static Builder from(ClientOptions clientOptions) { + Builder builder = new Builder(); + builder.environment = clientOptions.environment(); + builder.timeout = Optional.of(clientOptions.timeout(null)); + builder.httpClient = clientOptions.httpClient(); + return builder; + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/DateTimeDeserializer.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/DateTimeDeserializer.java new file mode 100644 index 00000000000..eac7d50c71a --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/DateTimeDeserializer.java @@ -0,0 +1,55 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; + +/** + * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. + */ +class DateTimeDeserializer extends JsonDeserializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); + } + + /** + * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonToken token = parser.currentToken(); + if (token == JsonToken.VALUE_NUMBER_INT) { + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); + } else { + TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( + parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); + + if (temporal.query(TemporalQueries.offset()) == null) { + return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); + } else { + return OffsetDateTime.from(temporal); + } + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/Environment.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/Environment.java new file mode 100644 index 00000000000..8a286722bb2 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/Environment.java @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +public final class Environment { + private final String url; + + private Environment(String url) { + this.url = url; + } + + public String getUrl() { + return this.url; + } + + public static Environment custom(String url) { + return new Environment(url); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/FileStream.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/FileStream.java new file mode 100644 index 00000000000..a71e7946986 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/FileStream.java @@ -0,0 +1,60 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.InputStream; +import java.util.Objects; +import okhttp3.MediaType; +import okhttp3.RequestBody; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a file stream with associated metadata for file uploads. + */ +public class FileStream { + private final InputStream inputStream; + private final String fileName; + private final MediaType contentType; + + /** + * Constructs a FileStream with the given input stream and optional metadata. + * + * @param inputStream The input stream of the file content. Must not be null. + * @param fileName The name of the file, or null if unknown. + * @param contentType The MIME type of the file content, or null if unknown. + * @throws NullPointerException if inputStream is null + */ + public FileStream(InputStream inputStream, @Nullable String fileName, @Nullable MediaType contentType) { + this.inputStream = Objects.requireNonNull(inputStream, "Input stream cannot be null"); + this.fileName = fileName; + this.contentType = contentType; + } + + public FileStream(InputStream inputStream) { + this(inputStream, null, null); + } + + public InputStream getInputStream() { + return inputStream; + } + + @Nullable + public String getFileName() { + return fileName; + } + + @Nullable + public MediaType getContentType() { + return contentType; + } + + /** + * Creates a RequestBody suitable for use with OkHttp client. + * + * @return A RequestBody instance representing this file stream. + */ + public RequestBody toRequestBody() { + return new InputStreamRequestBody(contentType, inputStream); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/FooApiException.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/FooApiException.java new file mode 100644 index 00000000000..28c07b0bf9e --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/FooApiException.java @@ -0,0 +1,73 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.Response; + +/** + * This exception type will be thrown for any non-2XX API responses. + */ +public class FooApiException extends FooException { + /** + * The error code of the response that triggered the exception. + */ + private final int statusCode; + + /** + * The body of the response that triggered the exception. + */ + private final Object body; + + private final Map> headers; + + public FooApiException(String message, int statusCode, Object body) { + super(message); + this.statusCode = statusCode; + this.body = body; + this.headers = new HashMap<>(); + } + + public FooApiException(String message, int statusCode, Object body, Response rawResponse) { + super(message); + this.statusCode = statusCode; + this.body = body; + this.headers = new HashMap<>(); + rawResponse.headers().forEach(header -> { + String key = header.component1(); + String value = header.component2(); + this.headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); + }); + } + + /** + * @return the statusCode + */ + public int statusCode() { + return this.statusCode; + } + + /** + * @return the body + */ + public Object body() { + return this.body; + } + + /** + * @return the headers + */ + public Map> headers() { + return this.headers; + } + + @java.lang.Override + public String toString() { + return "FooApiException{" + "message: " + getMessage() + ", statusCode: " + statusCode + ", body: " + body + + "}"; + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/FooException.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/FooException.java new file mode 100644 index 00000000000..66d76011161 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/FooException.java @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +/** + * This class serves as the base exception for all errors in the SDK. + */ +public class FooException extends RuntimeException { + public FooException(String message) { + super(message); + } + + public FooException(String message, Exception e) { + super(message, e); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/FooHttpResponse.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/FooHttpResponse.java new file mode 100644 index 00000000000..6943cae3121 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/FooHttpResponse.java @@ -0,0 +1,37 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.Response; + +public final class FooHttpResponse { + + private final T body; + + private final Map> headers; + + public FooHttpResponse(T body, Response rawResponse) { + this.body = body; + + Map> headers = new HashMap<>(); + rawResponse.headers().forEach(header -> { + String key = header.component1(); + String value = header.component2(); + headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); + }); + this.headers = headers; + } + + public T body() { + return this.body; + } + + public Map> headers() { + return headers; + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/InputStreamRequestBody.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/InputStreamRequestBody.java new file mode 100644 index 00000000000..a1e136889aa --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/InputStreamRequestBody.java @@ -0,0 +1,74 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Objects; +import okhttp3.MediaType; +import okhttp3.RequestBody; +import okio.BufferedSink; +import okio.Okio; +import okio.Source; +import org.jetbrains.annotations.Nullable; + +/** + * A custom implementation of OkHttp's RequestBody that wraps an InputStream. + * This class allows streaming of data from an InputStream directly to an HTTP request body, + * which is useful for file uploads or sending large amounts of data without loading it all into memory. + */ +public class InputStreamRequestBody extends RequestBody { + private final InputStream inputStream; + private final MediaType contentType; + + /** + * Constructs an InputStreamRequestBody with the specified content type and input stream. + * + * @param contentType the MediaType of the content, or null if not known + * @param inputStream the InputStream containing the data to be sent + * @throws NullPointerException if inputStream is null + */ + public InputStreamRequestBody(@Nullable MediaType contentType, InputStream inputStream) { + this.contentType = contentType; + this.inputStream = Objects.requireNonNull(inputStream, "inputStream == null"); + } + + /** + * Returns the content type of this request body. + * + * @return the MediaType of the content, or null if not specified + */ + @Nullable + @Override + public MediaType contentType() { + return contentType; + } + + /** + * Returns the content length of this request body, if known. + * This method attempts to determine the length using the InputStream's available() method, + * which may not always accurately reflect the total length of the stream. + * + * @return the content length, or -1 if the length is unknown + * @throws IOException if an I/O error occurs + */ + @Override + public long contentLength() throws IOException { + return inputStream.available() == 0 ? -1 : inputStream.available(); + } + + /** + * Writes the content of the InputStream to the given BufferedSink. + * This method is responsible for transferring the data from the InputStream to the network request. + * + * @param sink the BufferedSink to write the content to + * @throws IOException if an I/O error occurs during writing + */ + @Override + public void writeTo(BufferedSink sink) throws IOException { + try (Source source = Okio.source(inputStream)) { + sink.writeAll(source); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/MediaTypes.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/MediaTypes.java new file mode 100644 index 00000000000..4a8d1cf301d --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/MediaTypes.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import okhttp3.MediaType; + +public final class MediaTypes { + + public static final MediaType APPLICATION_JSON = MediaType.parse("application/json"); + + private MediaTypes() {} +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/Nullable.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/Nullable.java new file mode 100644 index 00000000000..ca50b2d8d50 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/Nullable.java @@ -0,0 +1,140 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.Optional; +import java.util.function.Function; + +public final class Nullable { + + private final Either, Null> value; + + private Nullable() { + this.value = Either.left(Optional.empty()); + } + + private Nullable(T value) { + if (value == null) { + this.value = Either.right(Null.INSTANCE); + } else { + this.value = Either.left(Optional.of(value)); + } + } + + public static Nullable ofNull() { + return new Nullable<>(null); + } + + public static Nullable of(T value) { + return new Nullable<>(value); + } + + public static Nullable empty() { + return new Nullable<>(); + } + + public static Nullable ofOptional(Optional value) { + if (value.isPresent()) { + return of(value.get()); + } else { + return empty(); + } + } + + public boolean isNull() { + return this.value.isRight(); + } + + public boolean isEmpty() { + return this.value.isLeft() && !this.value.getLeft().isPresent(); + } + + public T get() { + if (this.isNull()) { + return null; + } + + return this.value.getLeft().get(); + } + + public Nullable map(Function mapper) { + if (this.isNull()) { + return Nullable.ofNull(); + } + + return Nullable.ofOptional(this.value.getLeft().map(mapper)); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Nullable)) { + return false; + } + + if (((Nullable) other).isNull() && this.isNull()) { + return true; + } + + return this.value.getLeft().equals(((Nullable) other).value.getLeft()); + } + + private static final class Either { + private L left = null; + private R right = null; + + private Either(L left, R right) { + if (left != null && right != null) { + throw new IllegalArgumentException("Left and right argument cannot both be non-null."); + } + + if (left == null && right == null) { + throw new IllegalArgumentException("Left and right argument cannot both be null."); + } + + if (left != null) { + this.left = left; + } + + if (right != null) { + this.right = right; + } + } + + public static Either left(L left) { + return new Either<>(left, null); + } + + public static Either right(R right) { + return new Either<>(null, right); + } + + public boolean isLeft() { + return this.left != null; + } + + public boolean isRight() { + return this.right != null; + } + + public L getLeft() { + if (!this.isLeft()) { + throw new IllegalArgumentException("Cannot get left from right Either."); + } + return this.left; + } + + public R getRight() { + if (!this.isRight()) { + throw new IllegalArgumentException("Cannot get right from left Either."); + } + return this.right; + } + } + + private static final class Null { + private static final Null INSTANCE = new Null(); + + private Null() {} + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/NullableNonemptyFilter.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/NullableNonemptyFilter.java new file mode 100644 index 00000000000..4c5c664eca0 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/NullableNonemptyFilter.java @@ -0,0 +1,19 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.Optional; + +public final class NullableNonemptyFilter { + @Override + public boolean equals(Object o) { + boolean isOptionalEmpty = isOptionalEmpty(o); + + return isOptionalEmpty; + } + + private boolean isOptionalEmpty(Object o) { + return o instanceof Optional && !((Optional) o).isPresent(); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/ObjectMappers.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/ObjectMappers.java new file mode 100644 index 00000000000..0b16d472dca --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/ObjectMappers.java @@ -0,0 +1,36 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; + +public final class ObjectMappers { + public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() + .addModule(new Jdk8Module()) + .addModule(new JavaTimeModule()) + .addModule(DateTimeDeserializer.getModule()) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + private ObjectMappers() {} + + public static String stringify(Object o) { + try { + return JSON_MAPPER + .setSerializationInclusion(JsonInclude.Include.ALWAYS) + .writerWithDefaultPrettyPrinter() + .writeValueAsString(o); + } catch (IOException e) { + return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/QueryStringMapper.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/QueryStringMapper.java new file mode 100644 index 00000000000..3e364e6f3d5 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/QueryStringMapper.java @@ -0,0 +1,142 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import okhttp3.HttpUrl; +import okhttp3.MultipartBody; + +public class QueryStringMapper { + + private static final ObjectMapper MAPPER = ObjectMappers.JSON_MAPPER; + + public static void addQueryParameter(HttpUrl.Builder httpUrl, String key, Object value, boolean arraysAsRepeats) { + JsonNode valueNode = MAPPER.valueToTree(value); + + List> flat; + if (valueNode.isObject()) { + flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); + } else if (valueNode.isArray()) { + flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); + } else { + if (valueNode.isTextual()) { + httpUrl.addQueryParameter(key, valueNode.textValue()); + } else { + httpUrl.addQueryParameter(key, valueNode.toString()); + } + return; + } + + for (Map.Entry field : flat) { + if (field.getValue().isTextual()) { + httpUrl.addQueryParameter(key + field.getKey(), field.getValue().textValue()); + } else { + httpUrl.addQueryParameter(key + field.getKey(), field.getValue().toString()); + } + } + } + + public static void addFormDataPart( + MultipartBody.Builder multipartBody, String key, Object value, boolean arraysAsRepeats) { + JsonNode valueNode = MAPPER.valueToTree(value); + + List> flat; + if (valueNode.isObject()) { + flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); + } else if (valueNode.isArray()) { + flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); + } else { + if (valueNode.isTextual()) { + multipartBody.addFormDataPart(key, valueNode.textValue()); + } else { + multipartBody.addFormDataPart(key, valueNode.toString()); + } + return; + } + + for (Map.Entry field : flat) { + if (field.getValue().isTextual()) { + multipartBody.addFormDataPart( + key + field.getKey(), field.getValue().textValue()); + } else { + multipartBody.addFormDataPart( + key + field.getKey(), field.getValue().toString()); + } + } + } + + public static List> flattenObject(ObjectNode object, boolean arraysAsRepeats) { + List> flat = new ArrayList<>(); + + Iterator> fields = object.fields(); + while (fields.hasNext()) { + Map.Entry field = fields.next(); + + String key = "[" + field.getKey() + "]"; + + if (field.getValue().isObject()) { + List> flatField = + flattenObject((ObjectNode) field.getValue(), arraysAsRepeats); + addAll(flat, flatField, key); + } else if (field.getValue().isArray()) { + List> flatField = + flattenArray((ArrayNode) field.getValue(), key, arraysAsRepeats); + addAll(flat, flatField, ""); + } else { + flat.add(new AbstractMap.SimpleEntry<>(key, field.getValue())); + } + } + + return flat; + } + + private static List> flattenArray( + ArrayNode array, String key, boolean arraysAsRepeats) { + List> flat = new ArrayList<>(); + + Iterator elements = array.elements(); + + int index = 0; + while (elements.hasNext()) { + JsonNode element = elements.next(); + + String indexKey = key + "[" + index + "]"; + + if (arraysAsRepeats) { + indexKey = key; + } + + if (element.isObject()) { + List> flatField = flattenObject((ObjectNode) element, arraysAsRepeats); + addAll(flat, flatField, indexKey); + } else if (element.isArray()) { + List> flatField = flattenArray((ArrayNode) element, "", arraysAsRepeats); + addAll(flat, flatField, indexKey); + } else { + flat.add(new AbstractMap.SimpleEntry<>(indexKey, element)); + } + + index++; + } + + return flat; + } + + private static void addAll( + List> target, List> source, String prefix) { + for (Map.Entry entry : source) { + Map.Entry entryToAdd = + new AbstractMap.SimpleEntry<>(prefix + entry.getKey(), entry.getValue()); + target.add(entryToAdd); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/RequestOptions.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/RequestOptions.java new file mode 100644 index 00000000000..e78b8620b59 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/RequestOptions.java @@ -0,0 +1,87 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +public final class RequestOptions { + private final Optional timeout; + + private final TimeUnit timeoutTimeUnit; + + private final Map headers; + + private final Map> headerSuppliers; + + private RequestOptions( + Optional timeout, + TimeUnit timeoutTimeUnit, + Map headers, + Map> headerSuppliers) { + this.timeout = timeout; + this.timeoutTimeUnit = timeoutTimeUnit; + this.headers = headers; + this.headerSuppliers = headerSuppliers; + } + + public Optional getTimeout() { + return timeout; + } + + public TimeUnit getTimeoutTimeUnit() { + return timeoutTimeUnit; + } + + public Map getHeaders() { + Map headers = new HashMap<>(); + headers.putAll(this.headers); + this.headerSuppliers.forEach((key, supplier) -> { + headers.put(key, supplier.get()); + }); + return headers; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Optional timeout = Optional.empty(); + + private TimeUnit timeoutTimeUnit = TimeUnit.SECONDS; + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + public Builder timeout(Integer timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + public Builder timeout(Integer timeout, TimeUnit timeoutTimeUnit) { + this.timeout = Optional.of(timeout); + this.timeoutTimeUnit = timeoutTimeUnit; + return this; + } + + public Builder addHeader(String key, String value) { + this.headers.put(key, value); + return this; + } + + public Builder addHeader(String key, Supplier value) { + this.headerSuppliers.put(key, value); + return this; + } + + public RequestOptions build() { + return new RequestOptions(timeout, timeoutTimeUnit, headers, headerSuppliers); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/ResponseBodyInputStream.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/ResponseBodyInputStream.java new file mode 100644 index 00000000000..db05d538255 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/ResponseBodyInputStream.java @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.FilterInputStream; +import java.io.IOException; +import okhttp3.Response; + +/** + * A custom InputStream that wraps the InputStream from the OkHttp Response and ensures that the + * OkHttp Response object is properly closed when the stream is closed. + * + * This class extends FilterInputStream and takes an OkHttp Response object as a parameter. + * It retrieves the InputStream from the Response and overrides the close method to close + * both the InputStream and the Response object, ensuring proper resource management and preventing + * premature closure of the underlying HTTP connection. + */ +public class ResponseBodyInputStream extends FilterInputStream { + private final Response response; + + /** + * Constructs a ResponseBodyInputStream that wraps the InputStream from the given OkHttp + * Response object. + * + * @param response the OkHttp Response object from which the InputStream is retrieved + * @throws IOException if an I/O error occurs while retrieving the InputStream + */ + public ResponseBodyInputStream(Response response) throws IOException { + super(response.body().byteStream()); + this.response = response; + } + + /** + * Closes the InputStream and the associated OkHttp Response object. This ensures that the + * underlying HTTP connection is properly closed after the stream is no longer needed. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + super.close(); + response.close(); // Ensure the response is closed when the stream is closed + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/ResponseBodyReader.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/ResponseBodyReader.java new file mode 100644 index 00000000000..97fcf7a0efb --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/ResponseBodyReader.java @@ -0,0 +1,44 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.FilterReader; +import java.io.IOException; +import okhttp3.Response; + +/** + * A custom Reader that wraps the Reader from the OkHttp Response and ensures that the + * OkHttp Response object is properly closed when the reader is closed. + * + * This class extends FilterReader and takes an OkHttp Response object as a parameter. + * It retrieves the Reader from the Response and overrides the close method to close + * both the Reader and the Response object, ensuring proper resource management and preventing + * premature closure of the underlying HTTP connection. + */ +public class ResponseBodyReader extends FilterReader { + private final Response response; + + /** + * Constructs a ResponseBodyReader that wraps the Reader from the given OkHttp Response object. + * + * @param response the OkHttp Response object from which the Reader is retrieved + * @throws IOException if an I/O error occurs while retrieving the Reader + */ + public ResponseBodyReader(Response response) throws IOException { + super(response.body().charStream()); + this.response = response; + } + + /** + * Closes the Reader and the associated OkHttp Response object. This ensures that the + * underlying HTTP connection is properly closed after the reader is no longer needed. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + super.close(); + response.close(); // Ensure the response is closed when the reader is closed + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/RetryInterceptor.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/RetryInterceptor.java new file mode 100644 index 00000000000..728715567d3 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/RetryInterceptor.java @@ -0,0 +1,78 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.IOException; +import java.time.Duration; +import java.util.Optional; +import java.util.Random; +import okhttp3.Interceptor; +import okhttp3.Response; + +public class RetryInterceptor implements Interceptor { + + private static final Duration ONE_SECOND = Duration.ofSeconds(1); + private final ExponentialBackoff backoff; + private final Random random = new Random(); + + public RetryInterceptor(int maxRetries) { + this.backoff = new ExponentialBackoff(maxRetries); + } + + @Override + public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + + if (shouldRetry(response.code())) { + return retryChain(response, chain); + } + + return response; + } + + private Response retryChain(Response response, Chain chain) throws IOException { + Optional nextBackoff = this.backoff.nextBackoff(); + while (nextBackoff.isPresent()) { + try { + Thread.sleep(nextBackoff.get().toMillis()); + } catch (InterruptedException e) { + throw new IOException("Interrupted while trying request", e); + } + response.close(); + response = chain.proceed(chain.request()); + if (shouldRetry(response.code())) { + nextBackoff = this.backoff.nextBackoff(); + } else { + return response; + } + } + + return response; + } + + private static boolean shouldRetry(int statusCode) { + return statusCode == 408 || statusCode == 429 || statusCode >= 500; + } + + private final class ExponentialBackoff { + + private final int maxNumRetries; + + private int retryNumber = 0; + + ExponentialBackoff(int maxNumRetries) { + this.maxNumRetries = maxNumRetries; + } + + public Optional nextBackoff() { + retryNumber += 1; + if (retryNumber > maxNumRetries) { + return Optional.empty(); + } + + int upperBound = (int) Math.pow(2, retryNumber); + return Optional.of(ONE_SECOND.multipliedBy(random.nextInt(upperBound))); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/Stream.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/Stream.java new file mode 100644 index 00000000000..303eaf050fe --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/Stream.java @@ -0,0 +1,272 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.Closeable; +import java.io.IOException; +import java.io.Reader; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Scanner; + +/** + * The {@code Stream} class implements {@link Iterable} to provide a simple mechanism for reading and parsing + * objects of a given type from data streamed via a {@link Reader} using a specified delimiter. + *

+ * {@code Stream} assumes that data is being pushed to the provided {@link Reader} asynchronously and utilizes a + * {@code Scanner} to block during iteration if the next object is not available. + * Iterable stream for parsing JSON and Server-Sent Events (SSE) data. + * Supports both newline-delimited JSON and SSE with optional stream termination. + * + * @param The type of objects in the stream. + */ +public final class Stream implements Iterable, Closeable { + + private static final String NEWLINE = "\n"; + private static final String DATA_PREFIX = "data:"; + + public enum StreamType { + JSON, + SSE + } + + private final Class valueType; + private final Scanner scanner; + private final StreamType streamType; + private final String messageTerminator; + private final String streamTerminator; + private final Reader sseReader; + private boolean isClosed = false; + + /** + * Constructs a new {@code Stream} with the specified value type, reader, and delimiter. + * + * @param valueType The class of the objects in the stream. + * @param reader The reader that provides the streamed data. + * @param delimiter The delimiter used to separate elements in the stream. + */ + public Stream(Class valueType, Reader reader, String delimiter) { + this.valueType = valueType; + this.scanner = new Scanner(reader).useDelimiter(delimiter); + this.streamType = StreamType.JSON; + this.messageTerminator = delimiter; + this.streamTerminator = null; + this.sseReader = null; + } + + private Stream(Class valueType, StreamType type, Reader reader, String terminator) { + this.valueType = valueType; + this.streamType = type; + if (type == StreamType.JSON) { + this.scanner = new Scanner(reader).useDelimiter(terminator); + this.messageTerminator = terminator; + this.streamTerminator = null; + this.sseReader = null; + } else { + this.scanner = null; + this.messageTerminator = NEWLINE; + this.streamTerminator = terminator; + this.sseReader = reader; + } + } + + public static Stream fromJson(Class valueType, Reader reader, String delimiter) { + return new Stream<>(valueType, reader, delimiter); + } + + public static Stream fromJson(Class valueType, Reader reader) { + return new Stream<>(valueType, reader, NEWLINE); + } + + public static Stream fromSse(Class valueType, Reader sseReader) { + return new Stream<>(valueType, StreamType.SSE, sseReader, null); + } + + public static Stream fromSse(Class valueType, Reader sseReader, String streamTerminator) { + return new Stream<>(valueType, StreamType.SSE, sseReader, streamTerminator); + } + + @Override + public void close() throws IOException { + if (!isClosed) { + isClosed = true; + if (scanner != null) { + scanner.close(); + } + if (sseReader != null) { + sseReader.close(); + } + } + } + + private boolean isStreamClosed() { + return isClosed; + } + + /** + * Returns an iterator over the elements in this stream that blocks during iteration when the next object is + * not yet available. + * + * @return An iterator that can be used to traverse the elements in the stream. + */ + @Override + public Iterator iterator() { + if (streamType == StreamType.SSE) { + return new SSEIterator(); + } else { + return new JsonIterator(); + } + } + + private final class JsonIterator implements Iterator { + + /** + * Returns {@code true} if there are more elements in the stream. + *

+ * Will block and wait for input if the stream has not ended and the next object is not yet available. + * + * @return {@code true} if there are more elements, {@code false} otherwise. + */ + @Override + public boolean hasNext() { + if (isStreamClosed()) { + return false; + } + return scanner.hasNext(); + } + + /** + * Returns the next element in the stream. + *

+ * Will block and wait for input if the stream has not ended and the next object is not yet available. + * + * @return The next element in the stream. + * @throws NoSuchElementException If there are no more elements in the stream. + */ + @Override + public T next() { + if (isStreamClosed()) { + throw new NoSuchElementException("Stream is closed"); + } + + if (!scanner.hasNext()) { + throw new NoSuchElementException(); + } else { + try { + T parsedResponse = + ObjectMappers.JSON_MAPPER.readValue(scanner.next().trim(), valueType); + return parsedResponse; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + private final class SSEIterator implements Iterator { + private Scanner sseScanner; + private T nextItem; + private boolean hasNextItem = false; + private boolean endOfStream = false; + private StringBuilder buffer = new StringBuilder(); + private boolean prefixSeen = false; + + private SSEIterator() { + if (sseReader != null && !isStreamClosed()) { + this.sseScanner = new Scanner(sseReader); + } else { + this.endOfStream = true; + } + } + + @Override + public boolean hasNext() { + if (isStreamClosed() || endOfStream) { + return false; + } + + if (hasNextItem) { + return true; + } + + return readNextMessage(); + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException("No more elements in stream"); + } + + T result = nextItem; + nextItem = null; + hasNextItem = false; + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + private boolean readNextMessage() { + if (sseScanner == null || isStreamClosed()) { + endOfStream = true; + return false; + } + + try { + while (sseScanner.hasNextLine()) { + String chunk = sseScanner.nextLine(); + buffer.append(chunk).append(NEWLINE); + + int terminatorIndex; + while ((terminatorIndex = buffer.indexOf(messageTerminator)) >= 0) { + String line = buffer.substring(0, terminatorIndex + messageTerminator.length()); + buffer.delete(0, terminatorIndex + messageTerminator.length()); + + line = line.trim(); + if (line.isEmpty()) { + continue; + } + + if (!prefixSeen && line.startsWith(DATA_PREFIX)) { + prefixSeen = true; + line = line.substring(DATA_PREFIX.length()).trim(); + } else if (!prefixSeen) { + continue; + } + + if (streamTerminator != null && line.contains(streamTerminator)) { + endOfStream = true; + return false; + } + + try { + nextItem = ObjectMappers.JSON_MAPPER.readValue(line, valueType); + hasNextItem = true; + prefixSeen = false; + return true; + } catch (Exception parseEx) { + continue; + } + } + } + + endOfStream = true; + return false; + + } catch (Exception e) { + System.err.println("Failed to parse SSE stream: " + e.getMessage()); + endOfStream = true; + return false; + } + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/Suppliers.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/Suppliers.java new file mode 100644 index 00000000000..a3c24e96857 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/core/Suppliers.java @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +public final class Suppliers { + private Suppliers() {} + + public static Supplier memoize(Supplier delegate) { + AtomicReference value = new AtomicReference<>(); + return () -> { + T val = value.get(); + if (val == null) { + val = value.updateAndGet(cur -> cur == null ? Objects.requireNonNull(delegate.get()) : cur); + } + return val; + }; + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/requests/NonNullableObject.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/requests/NonNullableObject.java new file mode 100644 index 00000000000..c88c0350432 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/requests/NonNullableObject.java @@ -0,0 +1,196 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +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.api.core.ObjectMappers; +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) +@JsonDeserialize(builder = NonNullableObject.Builder.class) +public final class NonNullableObject { + private final String id; + + private final Optional nonNullableObjectId; + + private final Optional name; + + private final Optional age; + + private final Map additionalProperties; + + private NonNullableObject( + String id, + Optional nonNullableObjectId, + Optional name, + Optional age, + Map additionalProperties) { + this.id = id; + this.nonNullableObjectId = nonNullableObjectId; + this.name = name; + this.age = age; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("id") + public Optional getNonNullableObjectId() { + return nonNullableObjectId; + } + + @JsonProperty("name") + public Optional getName() { + return name; + } + + @JsonProperty("age") + public Optional getAge() { + return age; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof NonNullableObject && equalTo((NonNullableObject) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(NonNullableObject other) { + return id.equals(other.id) + && nonNullableObjectId.equals(other.nonNullableObjectId) + && name.equals(other.name) + && age.equals(other.age); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.nonNullableObjectId, this.name, this.age); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + _FinalStage id(@NotNull String id); + + Builder from(NonNullableObject other); + } + + public interface _FinalStage { + NonNullableObject build(); + + _FinalStage nonNullableObjectId(Optional nonNullableObjectId); + + _FinalStage nonNullableObjectId(String nonNullableObjectId); + + _FinalStage name(Optional name); + + _FinalStage name(String name); + + _FinalStage age(Optional age); + + _FinalStage age(Integer age); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements IdStage, _FinalStage { + private String id; + + private Optional age = Optional.empty(); + + private Optional name = Optional.empty(); + + private Optional nonNullableObjectId = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(NonNullableObject other) { + id(other.getId()); + nonNullableObjectId(other.getNonNullableObjectId()); + name(other.getName()); + age(other.getAge()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public _FinalStage id(@NotNull String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage age(Integer age) { + this.age = Optional.ofNullable(age); + return this; + } + + @java.lang.Override + @JsonSetter(value = "age", nulls = Nulls.SKIP) + public _FinalStage age(Optional age) { + this.age = age; + return this; + } + + @java.lang.Override + public _FinalStage name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + @java.lang.Override + @JsonSetter(value = "name", nulls = Nulls.SKIP) + public _FinalStage name(Optional name) { + this.name = name; + return this; + } + + @java.lang.Override + public _FinalStage nonNullableObjectId(String nonNullableObjectId) { + this.nonNullableObjectId = Optional.ofNullable(nonNullableObjectId); + return this; + } + + @java.lang.Override + @JsonSetter(value = "id", nulls = Nulls.SKIP) + public _FinalStage nonNullableObjectId(Optional nonNullableObjectId) { + this.nonNullableObjectId = nonNullableObjectId; + return this; + } + + @java.lang.Override + public NonNullableObject build() { + return new NonNullableObject(id, nonNullableObjectId, name, age, additionalProperties); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/requests/PostWithNullableNamedRequestBodyTypeRequest.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/requests/PostWithNullableNamedRequestBodyTypeRequest.java new file mode 100644 index 00000000000..69007eefb34 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/requests/PostWithNullableNamedRequestBodyTypeRequest.java @@ -0,0 +1,135 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +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.api.core.ObjectMappers; +import com.seed.api.types.NullableObject; +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) +@JsonDeserialize(builder = PostWithNullableNamedRequestBodyTypeRequest.Builder.class) +public final class PostWithNullableNamedRequestBodyTypeRequest { + private final String id; + + private final Optional body; + + private final Map additionalProperties; + + private PostWithNullableNamedRequestBodyTypeRequest( + String id, Optional body, Map additionalProperties) { + this.id = id; + this.body = body; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("body") + public Optional getBody() { + return body; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof PostWithNullableNamedRequestBodyTypeRequest + && equalTo((PostWithNullableNamedRequestBodyTypeRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(PostWithNullableNamedRequestBodyTypeRequest other) { + return id.equals(other.id) && body.equals(other.body); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.body); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + _FinalStage id(@NotNull String id); + + Builder from(PostWithNullableNamedRequestBodyTypeRequest other); + } + + public interface _FinalStage { + PostWithNullableNamedRequestBodyTypeRequest build(); + + _FinalStage body(Optional body); + + _FinalStage body(NullableObject body); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements IdStage, _FinalStage { + private String id; + + private Optional body = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(PostWithNullableNamedRequestBodyTypeRequest other) { + id(other.getId()); + body(other.getBody()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public _FinalStage id(@NotNull String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage body(NullableObject body) { + this.body = Optional.ofNullable(body); + return this; + } + + @java.lang.Override + @JsonSetter(value = "body", nulls = Nulls.SKIP) + public _FinalStage body(Optional body) { + this.body = body; + return this; + } + + @java.lang.Override + public PostWithNullableNamedRequestBodyTypeRequest build() { + return new PostWithNullableNamedRequestBodyTypeRequest(id, body, additionalProperties); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/types/NullableObject.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/types/NullableObject.java new file mode 100644 index 00000000000..00c392cc04a --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/types/NullableObject.java @@ -0,0 +1,143 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +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.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = NullableObject.Builder.class) +public final class NullableObject { + private final Optional id; + + private final Optional name; + + private final Optional age; + + private final Map additionalProperties; + + private NullableObject( + Optional id, + Optional name, + Optional age, + Map additionalProperties) { + this.id = id; + this.name = name; + this.age = age; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("id") + public Optional getId() { + return id; + } + + @JsonProperty("name") + public Optional getName() { + return name; + } + + @JsonProperty("age") + public Optional getAge() { + return age; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof NullableObject && equalTo((NullableObject) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(NullableObject other) { + return id.equals(other.id) && name.equals(other.name) && age.equals(other.age); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.name, this.age); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional id = Optional.empty(); + + private Optional name = Optional.empty(); + + private Optional age = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(NullableObject other) { + id(other.getId()); + name(other.getName()); + age(other.getAge()); + return this; + } + + @JsonSetter(value = "id", nulls = Nulls.SKIP) + public Builder id(Optional id) { + this.id = id; + return this; + } + + public Builder id(String id) { + this.id = Optional.ofNullable(id); + return this; + } + + @JsonSetter(value = "name", nulls = Nulls.SKIP) + public Builder name(Optional name) { + this.name = name; + return this; + } + + public Builder name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + @JsonSetter(value = "age", nulls = Nulls.SKIP) + public Builder age(Optional age) { + this.age = age; + return this; + } + + public Builder age(Integer age) { + this.age = Optional.ofNullable(age); + return this; + } + + public NullableObject build() { + return new NullableObject(id, name, age, additionalProperties); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/types/ResponseBody.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/types/ResponseBody.java new file mode 100644 index 00000000000..83c1f59df5a --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/seed/api/types/ResponseBody.java @@ -0,0 +1,95 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +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.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = ResponseBody.Builder.class) +public final class ResponseBody { + private final Optional success; + + private final Map additionalProperties; + + private ResponseBody(Optional success, Map additionalProperties) { + this.success = success; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("success") + public Optional getSuccess() { + return success; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof ResponseBody && equalTo((ResponseBody) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(ResponseBody other) { + return success.equals(other.success); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.success); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional success = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(ResponseBody other) { + success(other.getSuccess()); + return this; + } + + @JsonSetter(value = "success", nulls = Nulls.SKIP) + public Builder success(Optional success) { + this.success = success; + return this; + } + + public Builder success(Boolean success) { + this.success = Optional.ofNullable(success); + return this; + } + + public ResponseBody build() { + return new ResponseBody(success, additionalProperties); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/snippets/Example0.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/snippets/Example0.java new file mode 100644 index 00000000000..a750eecd7be --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/snippets/Example0.java @@ -0,0 +1,29 @@ +package com.snippets; + +import com.seed.api.Foo; +import com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest; +import com.seed.api.types.NullableObject; + +public class Example0 { + public static void main(String[] args) { + Foo client = Foo + .builder() + .url("https://api.fern.com") + .build(); + + client.postWithNullableNamedRequestBodyType( + PostWithNullableNamedRequestBodyTypeRequest + .builder() + .id("id") + .body( + NullableObject + .builder() + .id("id") + .name("name") + .age(1) + .build() + ) + .build() + ); + } +} \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/snippets/Example1.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/snippets/Example1.java new file mode 100644 index 00000000000..5c381f285d3 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/snippets/Example1.java @@ -0,0 +1,20 @@ +package com.snippets; + +import com.seed.api.Foo; +import com.seed.api.requests.NonNullableObject; + +public class Example1 { + public static void main(String[] args) { + Foo client = Foo + .builder() + .url("https://api.fern.com") + .build(); + + client.postWithNonNullableNamedRequestBodyType( + NonNullableObject + .builder() + .id("id") + .build() + ); + } +} \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/snippets/Example2.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/snippets/Example2.java new file mode 100644 index 00000000000..18bbf98e824 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/main/java/com/snippets/Example2.java @@ -0,0 +1,23 @@ +package com.snippets; + +import com.seed.api.Foo; +import com.seed.api.requests.NonNullableObject; + +public class Example2 { + public static void main(String[] args) { + Foo client = Foo + .builder() + .url("https://api.fern.com") + .build(); + + client.postWithNonNullableNamedRequestBodyType( + NonNullableObject + .builder() + .id("id") + .nonNullableObjectId("id") + .name("name") + .age(1) + .build() + ); + } +} \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/test/java/com/seed/api/StreamTest.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/test/java/com/seed/api/StreamTest.java new file mode 100644 index 00000000000..77f138e487f --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/test/java/com/seed/api/StreamTest.java @@ -0,0 +1,86 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import static org.junit.jupiter.api.Assertions.*; + +import com.seed.api.core.ObjectMappers; +import com.seed.api.core.Stream; +import java.io.IOException; +import java.io.StringReader; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +public final class StreamTest { + @Test + public void testJsonStream() { + List messages = List.of(Map.of("message", "hello"), Map.of("message", "world")); + List jsonStrings = messages.stream().map(StreamTest::mapToJson).collect(Collectors.toList()); + String input = String.join("\n", jsonStrings); + StringReader jsonInput = new StringReader(input); + Stream jsonStream = Stream.fromJson(Map.class, jsonInput); + int expectedMessages = 2; + int actualMessages = 0; + for (Map jsonObject : jsonStream) { + actualMessages++; + assertTrue(jsonObject.containsKey("message")); + } + assertEquals(expectedMessages, actualMessages); + } + + @Test + public void testSseStream() { + List events = List.of(Map.of("event", "start"), Map.of("event", "end")); + List sseStrings = events.stream().map(StreamTest::mapToSse).collect(Collectors.toList()); + String input = String.join("\n" + "\n", sseStrings); + StringReader sseInput = new StringReader(input); + Stream sseStream = Stream.fromSse(Map.class, sseInput); + int expectedEvents = 2; + int actualEvents = 0; + for (Map eventData : sseStream) { + actualEvents++; + assertTrue(eventData.containsKey("event")); + } + assertEquals(expectedEvents, actualEvents); + } + + @Test + public void testSseStreamWithTerminator() { + List events = List.of(Map.of("message", "first"), Map.of("message", "second")); + List sseStrings = events.stream().map(StreamTest::mapToSse).collect(Collectors.toList()); + sseStrings.add("data: [DONE]"); + String input = String.join("\n" + "\n", sseStrings); + StringReader sseInput = new StringReader(input); + Stream sseStream = Stream.fromSse(Map.class, sseInput, "[DONE]"); + int expectedEvents = 2; + int actualEvents = 0; + for (Map eventData : sseStream) { + actualEvents++; + assertTrue(eventData.containsKey("message")); + } + assertEquals(expectedEvents, actualEvents); + } + + @Test + public void testStreamResourceManagement() throws IOException { + StringReader testInput = new StringReader("{\"test\":\"data\"}"); + Stream testStream = Stream.fromJson(Map.class, testInput); + testStream.close(); + assertFalse(testStream.iterator().hasNext()); + } + + private static String mapToJson(Map map) { + try { + return ObjectMappers.JSON_MAPPER.writeValueAsString(map); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static String mapToSse(Map map) { + return "data: " + mapToJson(map); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/test/java/com/seed/api/TestClient.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/test/java/com/seed/api/TestClient.java new file mode 100644 index 00000000000..1686cfd803c --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/test/java/com/seed/api/TestClient.java @@ -0,0 +1,11 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +public final class TestClient { + public void test() { + // Add tests here and mark this file in .fernignore + assert true; + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/test/java/com/seed/api/core/QueryStringMapperTest.java b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/test/java/com/seed/api/core/QueryStringMapperTest.java new file mode 100644 index 00000000000..ead1a49af7a --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/found-custom-config/src/test/java/com/seed/api/core/QueryStringMapperTest.java @@ -0,0 +1,339 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.HttpUrl; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public final class QueryStringMapperTest { + @Test + public void testObjectWithQuotedString_indexedArrays() { + Map map = new HashMap() { + { + put("hello", "\"world\""); + } + }; + + String expectedQueryString = "withquoted%5Bhello%5D=%22world%22"; + + String actualQueryString = queryString( + new HashMap() { + { + put("withquoted", map); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectWithQuotedString_arraysAsRepeats() { + Map map = new HashMap() { + { + put("hello", "\"world\""); + } + }; + + String expectedQueryString = "withquoted%5Bhello%5D=%22world%22"; + + String actualQueryString = queryString( + new HashMap() { + { + put("withquoted", map); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObject_indexedArrays() { + Map map = new HashMap() { + { + put("foo", "bar"); + put("baz", "qux"); + } + }; + + String expectedQueryString = "metadata%5Bfoo%5D=bar&metadata%5Bbaz%5D=qux"; + + String actualQueryString = queryString( + new HashMap() { + { + put("metadata", map); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObject_arraysAsRepeats() { + Map map = new HashMap() { + { + put("foo", "bar"); + put("baz", "qux"); + } + }; + + String expectedQueryString = "metadata%5Bfoo%5D=bar&metadata%5Bbaz%5D=qux"; + + String actualQueryString = queryString( + new HashMap() { + { + put("metadata", map); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testNestedObject_indexedArrays() { + Map> nestedMap = new HashMap>() { + { + put("mapkey1", new HashMap() { + { + put("mapkey1mapkey1", "mapkey1mapkey1value"); + put("mapkey1mapkey2", "mapkey1mapkey2value"); + } + }); + put("mapkey2", new HashMap() { + { + put("mapkey2mapkey1", "mapkey2mapkey1value"); + } + }); + } + }; + + String expectedQueryString = + "nested%5Bmapkey2%5D%5Bmapkey2mapkey1%5D=mapkey2mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey1" + + "%5D=mapkey1mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey2%5D=mapkey1mapkey2value"; + + String actualQueryString = queryString( + new HashMap() { + { + put("nested", nestedMap); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testNestedObject_arraysAsRepeats() { + Map> nestedMap = new HashMap>() { + { + put("mapkey1", new HashMap() { + { + put("mapkey1mapkey1", "mapkey1mapkey1value"); + put("mapkey1mapkey2", "mapkey1mapkey2value"); + } + }); + put("mapkey2", new HashMap() { + { + put("mapkey2mapkey1", "mapkey2mapkey1value"); + } + }); + } + }; + + String expectedQueryString = + "nested%5Bmapkey2%5D%5Bmapkey2mapkey1%5D=mapkey2mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey1" + + "%5D=mapkey1mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey2%5D=mapkey1mapkey2value"; + + String actualQueryString = queryString( + new HashMap() { + { + put("nested", nestedMap); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testDateTime_indexedArrays() { + OffsetDateTime dateTime = + OffsetDateTime.ofInstant(Instant.ofEpochSecond(1740412107L), ZoneId.of("America/New_York")); + + String expectedQueryString = "datetime=2025-02-24T10%3A48%3A27-05%3A00"; + + String actualQueryString = queryString( + new HashMap() { + { + put("datetime", dateTime); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testDateTime_arraysAsRepeats() { + OffsetDateTime dateTime = + OffsetDateTime.ofInstant(Instant.ofEpochSecond(1740412107L), ZoneId.of("America/New_York")); + + String expectedQueryString = "datetime=2025-02-24T10%3A48%3A27-05%3A00"; + + String actualQueryString = queryString( + new HashMap() { + { + put("datetime", dateTime); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectArray_indexedArrays() { + List> mapArray = new ArrayList>() { + { + add(new HashMap() { + { + put("key", "hello"); + put("value", "world"); + } + }); + add(new HashMap() { + { + put("key", "foo"); + put("value", "bar"); + } + }); + add(new HashMap<>()); + } + }; + + String expectedQueryString = "objects%5B0%5D%5Bvalue%5D=world&objects%5B0%5D%5Bkey%5D=hello&objects%5B1%5D" + + "%5Bvalue%5D=bar&objects%5B1%5D%5Bkey%5D=foo"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objects", mapArray); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectArray_arraysAsRepeats() { + List> mapArray = new ArrayList>() { + { + add(new HashMap() { + { + put("key", "hello"); + put("value", "world"); + } + }); + add(new HashMap() { + { + put("key", "foo"); + put("value", "bar"); + } + }); + add(new HashMap<>()); + } + }; + + String expectedQueryString = + "objects%5Bvalue%5D=world&objects%5Bkey%5D=hello&objects%5Bvalue" + "%5D=bar&objects%5Bkey%5D=foo"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objects", mapArray); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectWithArray_indexedArrays() { + Map objectWithArray = new HashMap() { + { + put("id", "abc123"); + put("contactIds", new ArrayList() { + { + add("id1"); + add("id2"); + add("id3"); + } + }); + } + }; + + String expectedQueryString = + "objectwitharray%5Bid%5D=abc123&objectwitharray%5BcontactIds%5D%5B0%5D=id1&objectwitharray" + + "%5BcontactIds%5D%5B1%5D=id2&objectwitharray%5BcontactIds%5D%5B2%5D=id3"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objectwitharray", objectWithArray); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectWithArray_arraysAsRepeats() { + Map objectWithArray = new HashMap() { + { + put("id", "abc123"); + put("contactIds", new ArrayList() { + { + add("id1"); + add("id2"); + add("id3"); + } + }); + } + }; + + String expectedQueryString = "objectwitharray%5Bid%5D=abc123&objectwitharray%5BcontactIds" + + "%5D=id1&objectwitharray%5BcontactIds%5D=id2&objectwitharray%5BcontactIds%5D=id3"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objectwitharray", objectWithArray); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + private static String queryString(Map params, boolean arraysAsRepeats) { + HttpUrl.Builder httpUrl = HttpUrl.parse("http://www.fakewebsite.com/").newBuilder(); + params.forEach((paramName, paramValue) -> + QueryStringMapper.addQueryParameter(httpUrl, paramName, paramValue, arraysAsRepeats)); + return httpUrl.build().encodedQuery(); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/.github/workflows/ci.yml b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/.github/workflows/ci.yml new file mode 100644 index 00000000000..7bffd9de06d --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Compile + run: ./gradlew compileJava + + test: + needs: [ compile ] + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Test + run: ./gradlew test + publish: + needs: [ compile, test ] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Publish to maven + run: | + ./gradlew publish + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_PUBLISH_REGISTRY_URL: "" diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/.gitignore b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/.gitignore new file mode 100644 index 00000000000..d4199abc2cd --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/.gitignore @@ -0,0 +1,24 @@ +*.class +.project +.gradle +? +.classpath +.checkstyle +.settings +.node +build + +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Eclipse/IntelliJ APT +generated_src/ +generated_testSrc/ +generated/ + +bin +build \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/README.md b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/README.md new file mode 100644 index 00000000000..3c4f31b2247 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/README.md @@ -0,0 +1,198 @@ +# Seed Java Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FJava) +[![Maven Central](https://img.shields.io/maven-central/v/com.fern/java-nullable-named-request-types)](https://central.sonatype.com/artifact/com.fern/java-nullable-named-request-types) + +The Seed Java library provides convenient access to the Seed APIs from Java. + +## Installation + +### Gradle + +Add the dependency in your `build.gradle` file: + +```groovy +dependencies { + implementation 'com.fern:java-nullable-named-request-types' +} +``` + +### Maven + +Add the dependency in your `pom.xml` file: + +```xml + + com.fern + java-nullable-named-request-types + 0.0.1 + +``` + +## Reference + +A full reference for this library is available [here](./reference.md). + +## Usage + +Instantiate and use the client with the following: + +```java +package com.example.usage; + +import com.seed.api.SeedApiClient; +import com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest; +import com.seed.api.types.NullableObject; + +public class Example { + public static void main(String[] args) { + SeedApiClient client = SeedApiClient + .builder() + .build(); + + client.postWithNullableNamedRequestBodyType( + "id", + PostWithNullableNamedRequestBodyTypeRequest + .builder() + .body( + NullableObject + .builder() + .id("id") + .name("name") + .age(1) + .build() + ) + .build() + ); + } +} +``` + +## Base Url + +You can set a custom base URL when constructing the client. + +```java +import com.seed.api.SeedApiClient; + +SeedApiClient client = SeedApiClient + .builder() + .url("https://example.com") + .build(); +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), an API exception will be thrown. + +```java +import com.seed.api.core.SeedApiApiException; + +try { + client.postWithNullableNamedRequestBodyType(...); +} catch (SeedApiApiException e) { + // Do something with the API exception... +} +``` + +## Advanced + +### Custom Client + +This SDK is built to work with any instance of `OkHttpClient`. By default, if no client is provided, the SDK will construct one. +However, you can pass your own client like so: + +```java +import com.seed.api.SeedApiClient; +import okhttp3.OkHttpClient; + +OkHttpClient customClient = ...; + +SeedApiClient client = SeedApiClient + .builder() + .httpClient(customClient) + .build(); +``` + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retryable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retryable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `maxRetries` client option to configure this behavior. + +```java +import com.seed.api.SeedApiClient; + +SeedApiClient client = SeedApiClient + .builder() + .maxRetries(1) + .build(); +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level. + +```java +import com.seed.api.SeedApiClient; +import com.seed.api.core.RequestOptions; + +// Client level +SeedApiClient client = SeedApiClient + .builder() + .timeout(10) + .build(); + +// Request level +client.postWithNullableNamedRequestBodyType( + ..., + RequestOptions + .builder() + .timeout(10) + .build() +); +``` + +### Custom Headers + +The SDK allows you to add custom headers to requests. You can configure headers at the client level or at the request level. + +```java +import com.seed.api.SeedApiClient; +import com.seed.api.core.RequestOptions; + +// Client level +SeedApiClient client = SeedApiClient + .builder() + .addHeader("X-Custom-Header", "custom-value") + .addHeader("X-Request-Id", "abc-123") + .build(); +; + +// Request level +client.postWithNullableNamedRequestBodyType( + ..., + RequestOptions + .builder() + .addHeader("X-Request-Header", "request-value") + .build() +); +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/build.gradle b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/build.gradle new file mode 100644 index 00000000000..c4f5285166e --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/build.gradle @@ -0,0 +1,102 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'com.diffplug.spotless' version '6.11.0' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + api 'com.squareup.okhttp3:okhttp:4.12.0' + api 'com.fasterxml.jackson.core:jackson-databind:2.17.2' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.17.2' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.2' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2' +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +tasks.withType(Javadoc) { + failOnError false + options.addStringOption('Xdoclint:none', '-quiet') +} + +spotless { + java { + palantirJavaFormat() + } +} + + +java { + withSourcesJar() + withJavadocJar() +} + + +group = 'com.fern' + +version = '0.0.1' + +jar { + dependsOn(":generatePomFileForMavenPublication") + archiveBaseName = "java-nullable-named-request-types" +} + +sourcesJar { + archiveBaseName = "java-nullable-named-request-types" +} + +javadocJar { + archiveBaseName = "java-nullable-named-request-types" +} + +test { + useJUnitPlatform() + testLogging { + showStandardStreams = true + } +} + +publishing { + publications { + maven(MavenPublication) { + groupId = 'com.fern' + artifactId = 'java-nullable-named-request-types' + version = '0.0.1' + from components.java + pom { + licenses { + license { + name = 'The MIT License (MIT)' + url = 'https://mit-license.org/' + } + } + scm { + connection = 'scm:git:git://github.com/java-nullable-named-request-types/fern.git' + developerConnection = 'scm:git:git://github.com/java-nullable-named-request-types/fern.git' + url = 'https://github.com/java-nullable-named-request-types/fern' + } + } + } + } + repositories { + maven { + url "$System.env.MAVEN_PUBLISH_REGISTRY_URL" + credentials { + username "$System.env.MAVEN_USERNAME" + password "$System.env.MAVEN_PASSWORD" + } + } + } +} + diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/reference.md b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/reference.md new file mode 100644 index 00000000000..4098865d5cb --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/reference.md @@ -0,0 +1,130 @@ +# Reference +

client.postWithNullableNamedRequestBodyType(id, request) -> ResponseBody +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```java +client.postWithNullableNamedRequestBodyType( + "id", + PostWithNullableNamedRequestBodyTypeRequest + .builder() + .body( + NullableObject + .builder() + .id("id") + .name("name") + .age(1) + .build() + ) + .build() +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `String` + +
+
+ +
+
+ +**request:** `Optional` + +
+
+
+
+ + +
+
+
+ +
client.postWithNonNullableNamedRequestBodyType(id, request) -> ResponseBody +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```java +client.postWithNonNullableNamedRequestBodyType( + "id", + NonNullableObject + .builder() + .build() +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**id:** `String` + +
+
+ +
+
+ +**nonNullableObjectId:** `Optional` + +
+
+ +
+
+ +**name:** `Optional` + +
+
+ +
+
+ +**age:** `Optional` + +
+
+
+
+ + +
+
+
diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/sample-app/build.gradle b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/sample-app/build.gradle new file mode 100644 index 00000000000..4ee8f227b7a --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/sample-app/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java-library' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + implementation rootProject +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/sample-app/src/main/java/sample/App.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/sample-app/src/main/java/sample/App.java new file mode 100644 index 00000000000..8d293789008 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/sample-app/src/main/java/sample/App.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package sample; + +import java.lang.String; + +public final class App { + public static void main(String[] args) { + // import com.seed.api.AsyncSeedApiClient + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/settings.gradle b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/settings.gradle new file mode 100644 index 00000000000..1663c62bb41 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'java-nullable-named-request-types' + +include 'sample-app' \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/snippet-templates.json b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/snippet.json b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/snippet.json new file mode 100644 index 00000000000..0152619d35e --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/snippet.json @@ -0,0 +1,44 @@ +{ + "endpoints": [ + { + "example_identifier": "7d78177e", + "id": { + "method": "POST", + "path": "/postWithNullableNamedRequestBodyType/{id}", + "identifier_override": "endpoint_.postWithNullableNamedRequestBodyType" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest;\nimport com.seed.api.types.NullableObject;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.postWithNullableNamedRequestBodyType(\n \"id\",\n PostWithNullableNamedRequestBodyTypeRequest\n .builder()\n .body(\n NullableObject\n .builder()\n .id(\"id\")\n .name(\"name\")\n .age(1)\n .build()\n )\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest;\nimport com.seed.api.types.NullableObject;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.postWithNullableNamedRequestBodyType(\n \"id\",\n PostWithNullableNamedRequestBodyTypeRequest\n .builder()\n .body(\n NullableObject\n .builder()\n .id(\"id\")\n .name(\"name\")\n .age(1)\n .build()\n )\n .build()\n );\n }\n}\n" + } + }, + { + "example_identifier": "8a8806b8", + "id": { + "method": "POST", + "path": "/postWithNonNullableNamedRequestBodyType/{id}", + "identifier_override": "endpoint_.postWithNonNullableNamedRequestBodyType" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.NonNullableObject;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.postWithNonNullableNamedRequestBodyType(\n \"id\",\n NonNullableObject\n .builder()\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.NonNullableObject;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.postWithNonNullableNamedRequestBodyType(\n \"id\",\n NonNullableObject\n .builder()\n .build()\n );\n }\n}\n" + } + }, + { + "example_identifier": "39492db8", + "id": { + "method": "POST", + "path": "/postWithNonNullableNamedRequestBodyType/{id}", + "identifier_override": "endpoint_.postWithNonNullableNamedRequestBodyType" + }, + "snippet": { + "type": "java", + "sync_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.NonNullableObject;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.postWithNonNullableNamedRequestBodyType(\n \"id\",\n NonNullableObject\n .builder()\n .nonNullableObjectId(\"id\")\n .name(\"name\")\n .age(1)\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.api.SeedApiClient;\nimport com.seed.api.requests.NonNullableObject;\n\npublic class Example {\n public static void main(String[] args) {\n SeedApiClient client = SeedApiClient\n .builder()\n .build();\n\n client.postWithNonNullableNamedRequestBodyType(\n \"id\",\n NonNullableObject\n .builder()\n .nonNullableObjectId(\"id\")\n .name(\"name\")\n .age(1)\n .build()\n );\n }\n}\n" + } + } + ], + "types": {} +} \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/AsyncRawSeedApiClient.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/AsyncRawSeedApiClient.java new file mode 100644 index 00000000000..814102a5284 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/AsyncRawSeedApiClient.java @@ -0,0 +1,170 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.seed.api.core.ClientOptions; +import com.seed.api.core.MediaTypes; +import com.seed.api.core.ObjectMappers; +import com.seed.api.core.RequestOptions; +import com.seed.api.core.SeedApiApiException; +import com.seed.api.core.SeedApiException; +import com.seed.api.core.SeedApiHttpResponse; +import com.seed.api.requests.NonNullableObject; +import com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest; +import com.seed.api.types.ResponseBody; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.jetbrains.annotations.NotNull; + +public class AsyncRawSeedApiClient { + protected final ClientOptions clientOptions; + + public AsyncRawSeedApiClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + public CompletableFuture> postWithNullableNamedRequestBodyType(String id) { + return postWithNullableNamedRequestBodyType( + id, PostWithNullableNamedRequestBodyTypeRequest.builder().build()); + } + + public CompletableFuture> postWithNullableNamedRequestBodyType( + String id, PostWithNullableNamedRequestBodyTypeRequest request) { + return postWithNullableNamedRequestBodyType(id, request, null); + } + + public CompletableFuture> postWithNullableNamedRequestBodyType( + String id, PostWithNullableNamedRequestBodyTypeRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("postWithNullableNamedRequestBodyType") + .addPathSegment(id) + .build(); + RequestBody body; + try { + body = RequestBody.create("", null); + if (request.isPresent()) { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } + } catch (JsonProcessingException e) { + throw new SeedApiException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (okhttp3.ResponseBody responseBody = response.body()) { + if (response.isSuccessful()) { + future.complete(new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), ResponseBody.class), + response)); + return; + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + future.completeExceptionally(new SeedApiApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response)); + return; + } catch (IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + }); + return future; + } + + public CompletableFuture> postWithNonNullableNamedRequestBodyType(String id) { + return postWithNonNullableNamedRequestBodyType( + id, NonNullableObject.builder().build()); + } + + public CompletableFuture> postWithNonNullableNamedRequestBodyType( + String id, NonNullableObject request) { + return postWithNonNullableNamedRequestBodyType(id, request, null); + } + + public CompletableFuture> postWithNonNullableNamedRequestBodyType( + String id, NonNullableObject request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("postWithNonNullableNamedRequestBodyType") + .addPathSegment(id) + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new SeedApiException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (okhttp3.ResponseBody responseBody = response.body()) { + if (response.isSuccessful()) { + future.complete(new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), ResponseBody.class), + response)); + return; + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + future.completeExceptionally(new SeedApiApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response)); + return; + } catch (IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new SeedApiException("Network error executing HTTP request", e)); + } + }); + return future; + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/AsyncSeedApiClient.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/AsyncSeedApiClient.java new file mode 100644 index 00000000000..069c3162115 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/AsyncSeedApiClient.java @@ -0,0 +1,67 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.RequestOptions; +import com.seed.api.requests.NonNullableObject; +import com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest; +import com.seed.api.types.ResponseBody; +import java.util.concurrent.CompletableFuture; + +public class AsyncSeedApiClient { + protected final ClientOptions clientOptions; + + private final AsyncRawSeedApiClient rawClient; + + public AsyncSeedApiClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new AsyncRawSeedApiClient(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public AsyncRawSeedApiClient withRawResponse() { + return this.rawClient; + } + + public CompletableFuture postWithNullableNamedRequestBodyType(String id) { + return this.rawClient.postWithNullableNamedRequestBodyType(id).thenApply(response -> response.body()); + } + + public CompletableFuture postWithNullableNamedRequestBodyType( + String id, PostWithNullableNamedRequestBodyTypeRequest request) { + return this.rawClient.postWithNullableNamedRequestBodyType(id, request).thenApply(response -> response.body()); + } + + public CompletableFuture postWithNullableNamedRequestBodyType( + String id, PostWithNullableNamedRequestBodyTypeRequest request, RequestOptions requestOptions) { + return this.rawClient + .postWithNullableNamedRequestBodyType(id, request, requestOptions) + .thenApply(response -> response.body()); + } + + public CompletableFuture postWithNonNullableNamedRequestBodyType(String id) { + return this.rawClient.postWithNonNullableNamedRequestBodyType(id).thenApply(response -> response.body()); + } + + public CompletableFuture postWithNonNullableNamedRequestBodyType( + String id, NonNullableObject request) { + return this.rawClient + .postWithNonNullableNamedRequestBodyType(id, request) + .thenApply(response -> response.body()); + } + + public CompletableFuture postWithNonNullableNamedRequestBodyType( + String id, NonNullableObject request, RequestOptions requestOptions) { + return this.rawClient + .postWithNonNullableNamedRequestBodyType(id, request, requestOptions) + .thenApply(response -> response.body()); + } + + public static AsyncSeedApiClientBuilder builder() { + return new AsyncSeedApiClientBuilder(); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/AsyncSeedApiClientBuilder.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/AsyncSeedApiClientBuilder.java new file mode 100644 index 00000000000..b3d452ec563 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/AsyncSeedApiClientBuilder.java @@ -0,0 +1,165 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.Environment; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import okhttp3.OkHttpClient; + +public class AsyncSeedApiClientBuilder { + private Optional timeout = Optional.empty(); + + private Optional maxRetries = Optional.empty(); + + private final Map customHeaders = new HashMap<>(); + + private Environment environment; + + private OkHttpClient httpClient; + + public AsyncSeedApiClientBuilder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + /** + * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. + */ + public AsyncSeedApiClientBuilder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Sets the maximum number of retries for the client. Defaults to 2 retries. + */ + public AsyncSeedApiClientBuilder maxRetries(int maxRetries) { + this.maxRetries = Optional.of(maxRetries); + return this; + } + + /** + * Sets the underlying OkHttp client + */ + public AsyncSeedApiClientBuilder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + /** + * Add a custom header to be sent with all requests. + * For headers that need to be computed dynamically or conditionally, use the setAdditional() method override instead. + * + * @param name The header name + * @param value The header value + * @return This builder for method chaining + */ + public AsyncSeedApiClientBuilder addHeader(String name, String value) { + this.customHeaders.put(name, value); + return this; + } + + protected ClientOptions buildClientOptions() { + ClientOptions.Builder builder = ClientOptions.builder(); + setEnvironment(builder); + setHttpClient(builder); + setTimeouts(builder); + setRetries(builder); + for (Map.Entry header : this.customHeaders.entrySet()) { + builder.addHeader(header.getKey(), header.getValue()); + } + setAdditional(builder); + return builder.build(); + } + + /** + * Sets the environment configuration for the client. + * Override this method to modify URLs or add environment-specific logic. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setEnvironment(ClientOptions.Builder builder) { + builder.environment(this.environment); + } + + /** + * Sets the request timeout configuration. + * Override this method to customize timeout behavior. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setTimeouts(ClientOptions.Builder builder) { + if (this.timeout.isPresent()) { + builder.timeout(this.timeout.get()); + } + } + + /** + * Sets the retry configuration for failed requests. + * Override this method to implement custom retry strategies. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setRetries(ClientOptions.Builder builder) { + if (this.maxRetries.isPresent()) { + builder.maxRetries(this.maxRetries.get()); + } + } + + /** + * Sets the OkHttp client configuration. + * Override this method to customize HTTP client behavior (interceptors, connection pools, etc). + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setHttpClient(ClientOptions.Builder builder) { + if (this.httpClient != null) { + builder.httpClient(this.httpClient); + } + } + + /** + * Override this method to add any additional configuration to the client. + * This method is called at the end of the configuration chain, allowing you to add + * custom headers, modify settings, or perform any other client customization. + * + * @param builder The ClientOptions.Builder to configure + * + * Example: + *
{@code
+     * @Override
+     * protected void setAdditional(ClientOptions.Builder builder) {
+     *     builder.addHeader("X-Request-ID", () -> UUID.randomUUID().toString());
+     *     builder.addHeader("X-Client-Version", "1.0.0");
+     * }
+     * }
+ */ + protected void setAdditional(ClientOptions.Builder builder) {} + + /** + * Override this method to add custom validation logic before the client is built. + * This method is called at the beginning of the build() method to ensure the configuration is valid. + * Throw an exception to prevent client creation if validation fails. + * + * Example: + *
{@code
+     * @Override
+     * protected void validateConfiguration() {
+     *     super.validateConfiguration(); // Run parent validations
+     *     if (tenantId == null || tenantId.isEmpty()) {
+     *         throw new IllegalStateException("tenantId is required");
+     *     }
+     * }
+     * }
+ */ + protected void validateConfiguration() {} + + public AsyncSeedApiClient build() { + validateConfiguration(); + return new AsyncSeedApiClient(buildClientOptions()); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/RawSeedApiClient.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/RawSeedApiClient.java new file mode 100644 index 00000000000..a6d643723ee --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/RawSeedApiClient.java @@ -0,0 +1,138 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.seed.api.core.ClientOptions; +import com.seed.api.core.MediaTypes; +import com.seed.api.core.ObjectMappers; +import com.seed.api.core.RequestOptions; +import com.seed.api.core.SeedApiApiException; +import com.seed.api.core.SeedApiException; +import com.seed.api.core.SeedApiHttpResponse; +import com.seed.api.requests.NonNullableObject; +import com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest; +import com.seed.api.types.ResponseBody; +import java.io.IOException; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class RawSeedApiClient { + protected final ClientOptions clientOptions; + + public RawSeedApiClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + public SeedApiHttpResponse postWithNullableNamedRequestBodyType(String id) { + return postWithNullableNamedRequestBodyType( + id, PostWithNullableNamedRequestBodyTypeRequest.builder().build()); + } + + public SeedApiHttpResponse postWithNullableNamedRequestBodyType( + String id, PostWithNullableNamedRequestBodyTypeRequest request) { + return postWithNullableNamedRequestBodyType(id, request, null); + } + + public SeedApiHttpResponse postWithNullableNamedRequestBodyType( + String id, PostWithNullableNamedRequestBodyTypeRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("postWithNullableNamedRequestBodyType") + .addPathSegment(id) + .build(); + RequestBody body; + try { + body = RequestBody.create("", null); + if (request.isPresent()) { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } + } catch (JsonProcessingException e) { + throw new SeedApiException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + okhttp3.ResponseBody responseBody = response.body(); + if (response.isSuccessful()) { + return new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), ResponseBody.class), response); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new SeedApiApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response); + } catch (IOException e) { + throw new SeedApiException("Network error executing HTTP request", e); + } + } + + public SeedApiHttpResponse postWithNonNullableNamedRequestBodyType(String id) { + return postWithNonNullableNamedRequestBodyType( + id, NonNullableObject.builder().build()); + } + + public SeedApiHttpResponse postWithNonNullableNamedRequestBodyType( + String id, NonNullableObject request) { + return postWithNonNullableNamedRequestBodyType(id, request, null); + } + + public SeedApiHttpResponse postWithNonNullableNamedRequestBodyType( + String id, NonNullableObject request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("postWithNonNullableNamedRequestBodyType") + .addPathSegment(id) + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new SeedApiException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + okhttp3.ResponseBody responseBody = response.body(); + if (response.isSuccessful()) { + return new SeedApiHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), ResponseBody.class), response); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new SeedApiApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response); + } catch (IOException e) { + throw new SeedApiException("Network error executing HTTP request", e); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/SeedApiClient.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/SeedApiClient.java new file mode 100644 index 00000000000..3d09111304e --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/SeedApiClient.java @@ -0,0 +1,65 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.RequestOptions; +import com.seed.api.requests.NonNullableObject; +import com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest; +import com.seed.api.types.ResponseBody; + +public class SeedApiClient { + protected final ClientOptions clientOptions; + + private final RawSeedApiClient rawClient; + + public SeedApiClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new RawSeedApiClient(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public RawSeedApiClient withRawResponse() { + return this.rawClient; + } + + public ResponseBody postWithNullableNamedRequestBodyType(String id) { + return this.rawClient.postWithNullableNamedRequestBodyType(id).body(); + } + + public ResponseBody postWithNullableNamedRequestBodyType( + String id, PostWithNullableNamedRequestBodyTypeRequest request) { + return this.rawClient.postWithNullableNamedRequestBodyType(id, request).body(); + } + + public ResponseBody postWithNullableNamedRequestBodyType( + String id, PostWithNullableNamedRequestBodyTypeRequest request, RequestOptions requestOptions) { + return this.rawClient + .postWithNullableNamedRequestBodyType(id, request, requestOptions) + .body(); + } + + public ResponseBody postWithNonNullableNamedRequestBodyType(String id) { + return this.rawClient.postWithNonNullableNamedRequestBodyType(id).body(); + } + + public ResponseBody postWithNonNullableNamedRequestBodyType(String id, NonNullableObject request) { + return this.rawClient + .postWithNonNullableNamedRequestBodyType(id, request) + .body(); + } + + public ResponseBody postWithNonNullableNamedRequestBodyType( + String id, NonNullableObject request, RequestOptions requestOptions) { + return this.rawClient + .postWithNonNullableNamedRequestBodyType(id, request, requestOptions) + .body(); + } + + public static SeedApiClientBuilder builder() { + return new SeedApiClientBuilder(); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/SeedApiClientBuilder.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/SeedApiClientBuilder.java new file mode 100644 index 00000000000..19abfb35b77 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/SeedApiClientBuilder.java @@ -0,0 +1,165 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.Environment; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import okhttp3.OkHttpClient; + +public class SeedApiClientBuilder { + private Optional timeout = Optional.empty(); + + private Optional maxRetries = Optional.empty(); + + private final Map customHeaders = new HashMap<>(); + + private Environment environment; + + private OkHttpClient httpClient; + + public SeedApiClientBuilder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + /** + * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. + */ + public SeedApiClientBuilder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Sets the maximum number of retries for the client. Defaults to 2 retries. + */ + public SeedApiClientBuilder maxRetries(int maxRetries) { + this.maxRetries = Optional.of(maxRetries); + return this; + } + + /** + * Sets the underlying OkHttp client + */ + public SeedApiClientBuilder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + /** + * Add a custom header to be sent with all requests. + * For headers that need to be computed dynamically or conditionally, use the setAdditional() method override instead. + * + * @param name The header name + * @param value The header value + * @return This builder for method chaining + */ + public SeedApiClientBuilder addHeader(String name, String value) { + this.customHeaders.put(name, value); + return this; + } + + protected ClientOptions buildClientOptions() { + ClientOptions.Builder builder = ClientOptions.builder(); + setEnvironment(builder); + setHttpClient(builder); + setTimeouts(builder); + setRetries(builder); + for (Map.Entry header : this.customHeaders.entrySet()) { + builder.addHeader(header.getKey(), header.getValue()); + } + setAdditional(builder); + return builder.build(); + } + + /** + * Sets the environment configuration for the client. + * Override this method to modify URLs or add environment-specific logic. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setEnvironment(ClientOptions.Builder builder) { + builder.environment(this.environment); + } + + /** + * Sets the request timeout configuration. + * Override this method to customize timeout behavior. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setTimeouts(ClientOptions.Builder builder) { + if (this.timeout.isPresent()) { + builder.timeout(this.timeout.get()); + } + } + + /** + * Sets the retry configuration for failed requests. + * Override this method to implement custom retry strategies. + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setRetries(ClientOptions.Builder builder) { + if (this.maxRetries.isPresent()) { + builder.maxRetries(this.maxRetries.get()); + } + } + + /** + * Sets the OkHttp client configuration. + * Override this method to customize HTTP client behavior (interceptors, connection pools, etc). + * + * @param builder The ClientOptions.Builder to configure + */ + protected void setHttpClient(ClientOptions.Builder builder) { + if (this.httpClient != null) { + builder.httpClient(this.httpClient); + } + } + + /** + * Override this method to add any additional configuration to the client. + * This method is called at the end of the configuration chain, allowing you to add + * custom headers, modify settings, or perform any other client customization. + * + * @param builder The ClientOptions.Builder to configure + * + * Example: + *
{@code
+     * @Override
+     * protected void setAdditional(ClientOptions.Builder builder) {
+     *     builder.addHeader("X-Request-ID", () -> UUID.randomUUID().toString());
+     *     builder.addHeader("X-Client-Version", "1.0.0");
+     * }
+     * }
+ */ + protected void setAdditional(ClientOptions.Builder builder) {} + + /** + * Override this method to add custom validation logic before the client is built. + * This method is called at the beginning of the build() method to ensure the configuration is valid. + * Throw an exception to prevent client creation if validation fails. + * + * Example: + *
{@code
+     * @Override
+     * protected void validateConfiguration() {
+     *     super.validateConfiguration(); // Run parent validations
+     *     if (tenantId == null || tenantId.isEmpty()) {
+     *         throw new IllegalStateException("tenantId is required");
+     *     }
+     * }
+     * }
+ */ + protected void validateConfiguration() {} + + public SeedApiClient build() { + validateConfiguration(); + return new SeedApiClient(buildClientOptions()); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/ClientOptions.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/ClientOptions.java new file mode 100644 index 00000000000..cbf9de67067 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/ClientOptions.java @@ -0,0 +1,180 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import okhttp3.OkHttpClient; + +public final class ClientOptions { + private final Environment environment; + + private final Map headers; + + private final Map> headerSuppliers; + + private final OkHttpClient httpClient; + + private final int timeout; + + private ClientOptions( + Environment environment, + Map headers, + Map> headerSuppliers, + OkHttpClient httpClient, + int timeout) { + this.environment = environment; + this.headers = new HashMap<>(); + this.headers.putAll(headers); + this.headers.putAll(new HashMap() { + { + put("User-Agent", "com.fern:java-nullable-named-request-types/0.0.1"); + put("X-Fern-Language", "JAVA"); + } + }); + this.headerSuppliers = headerSuppliers; + this.httpClient = httpClient; + this.timeout = timeout; + } + + public Environment environment() { + return this.environment; + } + + public Map headers(RequestOptions requestOptions) { + Map values = new HashMap<>(this.headers); + headerSuppliers.forEach((key, supplier) -> { + values.put(key, supplier.get()); + }); + if (requestOptions != null) { + values.putAll(requestOptions.getHeaders()); + } + return values; + } + + public int timeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.timeout; + } + return requestOptions.getTimeout().orElse(this.timeout); + } + + public OkHttpClient httpClient() { + return this.httpClient; + } + + public OkHttpClient httpClientWithTimeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.httpClient; + } + return this.httpClient + .newBuilder() + .callTimeout(requestOptions.getTimeout().get(), requestOptions.getTimeoutTimeUnit()) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .build(); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Environment environment; + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + private int maxRetries = 2; + + private Optional timeout = Optional.empty(); + + private OkHttpClient httpClient = null; + + public Builder environment(Environment environment) { + this.environment = environment; + return this; + } + + public Builder addHeader(String key, String value) { + this.headers.put(key, value); + return this; + } + + public Builder addHeader(String key, Supplier value) { + this.headerSuppliers.put(key, value); + return this; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(Optional timeout) { + this.timeout = timeout; + return this; + } + + /** + * Override the maximum number of retries. Defaults to 2 retries. + */ + public Builder maxRetries(int maxRetries) { + this.maxRetries = maxRetries; + return this; + } + + public Builder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + public ClientOptions build() { + OkHttpClient.Builder httpClientBuilder = + this.httpClient != null ? this.httpClient.newBuilder() : new OkHttpClient.Builder(); + + if (this.httpClient != null) { + timeout.ifPresent(timeout -> httpClientBuilder + .callTimeout(timeout, TimeUnit.SECONDS) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS)); + } else { + httpClientBuilder + .callTimeout(this.timeout.orElse(60), TimeUnit.SECONDS) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .addInterceptor(new RetryInterceptor(this.maxRetries)); + } + + this.httpClient = httpClientBuilder.build(); + this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); + + return new ClientOptions(environment, headers, headerSuppliers, httpClient, this.timeout.get()); + } + + /** + * Create a new Builder initialized with values from an existing ClientOptions + */ + public static Builder from(ClientOptions clientOptions) { + Builder builder = new Builder(); + builder.environment = clientOptions.environment(); + builder.timeout = Optional.of(clientOptions.timeout(null)); + builder.httpClient = clientOptions.httpClient(); + return builder; + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/DateTimeDeserializer.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/DateTimeDeserializer.java new file mode 100644 index 00000000000..eac7d50c71a --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/DateTimeDeserializer.java @@ -0,0 +1,55 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; + +/** + * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. + */ +class DateTimeDeserializer extends JsonDeserializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); + } + + /** + * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonToken token = parser.currentToken(); + if (token == JsonToken.VALUE_NUMBER_INT) { + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); + } else { + TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( + parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); + + if (temporal.query(TemporalQueries.offset()) == null) { + return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); + } else { + return OffsetDateTime.from(temporal); + } + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/Environment.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/Environment.java new file mode 100644 index 00000000000..8a286722bb2 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/Environment.java @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +public final class Environment { + private final String url; + + private Environment(String url) { + this.url = url; + } + + public String getUrl() { + return this.url; + } + + public static Environment custom(String url) { + return new Environment(url); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/FileStream.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/FileStream.java new file mode 100644 index 00000000000..a71e7946986 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/FileStream.java @@ -0,0 +1,60 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.InputStream; +import java.util.Objects; +import okhttp3.MediaType; +import okhttp3.RequestBody; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a file stream with associated metadata for file uploads. + */ +public class FileStream { + private final InputStream inputStream; + private final String fileName; + private final MediaType contentType; + + /** + * Constructs a FileStream with the given input stream and optional metadata. + * + * @param inputStream The input stream of the file content. Must not be null. + * @param fileName The name of the file, or null if unknown. + * @param contentType The MIME type of the file content, or null if unknown. + * @throws NullPointerException if inputStream is null + */ + public FileStream(InputStream inputStream, @Nullable String fileName, @Nullable MediaType contentType) { + this.inputStream = Objects.requireNonNull(inputStream, "Input stream cannot be null"); + this.fileName = fileName; + this.contentType = contentType; + } + + public FileStream(InputStream inputStream) { + this(inputStream, null, null); + } + + public InputStream getInputStream() { + return inputStream; + } + + @Nullable + public String getFileName() { + return fileName; + } + + @Nullable + public MediaType getContentType() { + return contentType; + } + + /** + * Creates a RequestBody suitable for use with OkHttp client. + * + * @return A RequestBody instance representing this file stream. + */ + public RequestBody toRequestBody() { + return new InputStreamRequestBody(contentType, inputStream); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/InputStreamRequestBody.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/InputStreamRequestBody.java new file mode 100644 index 00000000000..a1e136889aa --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/InputStreamRequestBody.java @@ -0,0 +1,74 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Objects; +import okhttp3.MediaType; +import okhttp3.RequestBody; +import okio.BufferedSink; +import okio.Okio; +import okio.Source; +import org.jetbrains.annotations.Nullable; + +/** + * A custom implementation of OkHttp's RequestBody that wraps an InputStream. + * This class allows streaming of data from an InputStream directly to an HTTP request body, + * which is useful for file uploads or sending large amounts of data without loading it all into memory. + */ +public class InputStreamRequestBody extends RequestBody { + private final InputStream inputStream; + private final MediaType contentType; + + /** + * Constructs an InputStreamRequestBody with the specified content type and input stream. + * + * @param contentType the MediaType of the content, or null if not known + * @param inputStream the InputStream containing the data to be sent + * @throws NullPointerException if inputStream is null + */ + public InputStreamRequestBody(@Nullable MediaType contentType, InputStream inputStream) { + this.contentType = contentType; + this.inputStream = Objects.requireNonNull(inputStream, "inputStream == null"); + } + + /** + * Returns the content type of this request body. + * + * @return the MediaType of the content, or null if not specified + */ + @Nullable + @Override + public MediaType contentType() { + return contentType; + } + + /** + * Returns the content length of this request body, if known. + * This method attempts to determine the length using the InputStream's available() method, + * which may not always accurately reflect the total length of the stream. + * + * @return the content length, or -1 if the length is unknown + * @throws IOException if an I/O error occurs + */ + @Override + public long contentLength() throws IOException { + return inputStream.available() == 0 ? -1 : inputStream.available(); + } + + /** + * Writes the content of the InputStream to the given BufferedSink. + * This method is responsible for transferring the data from the InputStream to the network request. + * + * @param sink the BufferedSink to write the content to + * @throws IOException if an I/O error occurs during writing + */ + @Override + public void writeTo(BufferedSink sink) throws IOException { + try (Source source = Okio.source(inputStream)) { + sink.writeAll(source); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/MediaTypes.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/MediaTypes.java new file mode 100644 index 00000000000..4a8d1cf301d --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/MediaTypes.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import okhttp3.MediaType; + +public final class MediaTypes { + + public static final MediaType APPLICATION_JSON = MediaType.parse("application/json"); + + private MediaTypes() {} +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/Nullable.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/Nullable.java new file mode 100644 index 00000000000..ca50b2d8d50 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/Nullable.java @@ -0,0 +1,140 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.Optional; +import java.util.function.Function; + +public final class Nullable { + + private final Either, Null> value; + + private Nullable() { + this.value = Either.left(Optional.empty()); + } + + private Nullable(T value) { + if (value == null) { + this.value = Either.right(Null.INSTANCE); + } else { + this.value = Either.left(Optional.of(value)); + } + } + + public static Nullable ofNull() { + return new Nullable<>(null); + } + + public static Nullable of(T value) { + return new Nullable<>(value); + } + + public static Nullable empty() { + return new Nullable<>(); + } + + public static Nullable ofOptional(Optional value) { + if (value.isPresent()) { + return of(value.get()); + } else { + return empty(); + } + } + + public boolean isNull() { + return this.value.isRight(); + } + + public boolean isEmpty() { + return this.value.isLeft() && !this.value.getLeft().isPresent(); + } + + public T get() { + if (this.isNull()) { + return null; + } + + return this.value.getLeft().get(); + } + + public Nullable map(Function mapper) { + if (this.isNull()) { + return Nullable.ofNull(); + } + + return Nullable.ofOptional(this.value.getLeft().map(mapper)); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Nullable)) { + return false; + } + + if (((Nullable) other).isNull() && this.isNull()) { + return true; + } + + return this.value.getLeft().equals(((Nullable) other).value.getLeft()); + } + + private static final class Either { + private L left = null; + private R right = null; + + private Either(L left, R right) { + if (left != null && right != null) { + throw new IllegalArgumentException("Left and right argument cannot both be non-null."); + } + + if (left == null && right == null) { + throw new IllegalArgumentException("Left and right argument cannot both be null."); + } + + if (left != null) { + this.left = left; + } + + if (right != null) { + this.right = right; + } + } + + public static Either left(L left) { + return new Either<>(left, null); + } + + public static Either right(R right) { + return new Either<>(null, right); + } + + public boolean isLeft() { + return this.left != null; + } + + public boolean isRight() { + return this.right != null; + } + + public L getLeft() { + if (!this.isLeft()) { + throw new IllegalArgumentException("Cannot get left from right Either."); + } + return this.left; + } + + public R getRight() { + if (!this.isRight()) { + throw new IllegalArgumentException("Cannot get right from left Either."); + } + return this.right; + } + } + + private static final class Null { + private static final Null INSTANCE = new Null(); + + private Null() {} + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/NullableNonemptyFilter.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/NullableNonemptyFilter.java new file mode 100644 index 00000000000..4c5c664eca0 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/NullableNonemptyFilter.java @@ -0,0 +1,19 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.Optional; + +public final class NullableNonemptyFilter { + @Override + public boolean equals(Object o) { + boolean isOptionalEmpty = isOptionalEmpty(o); + + return isOptionalEmpty; + } + + private boolean isOptionalEmpty(Object o) { + return o instanceof Optional && !((Optional) o).isPresent(); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/ObjectMappers.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/ObjectMappers.java new file mode 100644 index 00000000000..0b16d472dca --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/ObjectMappers.java @@ -0,0 +1,36 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; + +public final class ObjectMappers { + public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() + .addModule(new Jdk8Module()) + .addModule(new JavaTimeModule()) + .addModule(DateTimeDeserializer.getModule()) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + private ObjectMappers() {} + + public static String stringify(Object o) { + try { + return JSON_MAPPER + .setSerializationInclusion(JsonInclude.Include.ALWAYS) + .writerWithDefaultPrettyPrinter() + .writeValueAsString(o); + } catch (IOException e) { + return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/QueryStringMapper.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/QueryStringMapper.java new file mode 100644 index 00000000000..3e364e6f3d5 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/QueryStringMapper.java @@ -0,0 +1,142 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import okhttp3.HttpUrl; +import okhttp3.MultipartBody; + +public class QueryStringMapper { + + private static final ObjectMapper MAPPER = ObjectMappers.JSON_MAPPER; + + public static void addQueryParameter(HttpUrl.Builder httpUrl, String key, Object value, boolean arraysAsRepeats) { + JsonNode valueNode = MAPPER.valueToTree(value); + + List> flat; + if (valueNode.isObject()) { + flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); + } else if (valueNode.isArray()) { + flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); + } else { + if (valueNode.isTextual()) { + httpUrl.addQueryParameter(key, valueNode.textValue()); + } else { + httpUrl.addQueryParameter(key, valueNode.toString()); + } + return; + } + + for (Map.Entry field : flat) { + if (field.getValue().isTextual()) { + httpUrl.addQueryParameter(key + field.getKey(), field.getValue().textValue()); + } else { + httpUrl.addQueryParameter(key + field.getKey(), field.getValue().toString()); + } + } + } + + public static void addFormDataPart( + MultipartBody.Builder multipartBody, String key, Object value, boolean arraysAsRepeats) { + JsonNode valueNode = MAPPER.valueToTree(value); + + List> flat; + if (valueNode.isObject()) { + flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); + } else if (valueNode.isArray()) { + flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); + } else { + if (valueNode.isTextual()) { + multipartBody.addFormDataPart(key, valueNode.textValue()); + } else { + multipartBody.addFormDataPart(key, valueNode.toString()); + } + return; + } + + for (Map.Entry field : flat) { + if (field.getValue().isTextual()) { + multipartBody.addFormDataPart( + key + field.getKey(), field.getValue().textValue()); + } else { + multipartBody.addFormDataPart( + key + field.getKey(), field.getValue().toString()); + } + } + } + + public static List> flattenObject(ObjectNode object, boolean arraysAsRepeats) { + List> flat = new ArrayList<>(); + + Iterator> fields = object.fields(); + while (fields.hasNext()) { + Map.Entry field = fields.next(); + + String key = "[" + field.getKey() + "]"; + + if (field.getValue().isObject()) { + List> flatField = + flattenObject((ObjectNode) field.getValue(), arraysAsRepeats); + addAll(flat, flatField, key); + } else if (field.getValue().isArray()) { + List> flatField = + flattenArray((ArrayNode) field.getValue(), key, arraysAsRepeats); + addAll(flat, flatField, ""); + } else { + flat.add(new AbstractMap.SimpleEntry<>(key, field.getValue())); + } + } + + return flat; + } + + private static List> flattenArray( + ArrayNode array, String key, boolean arraysAsRepeats) { + List> flat = new ArrayList<>(); + + Iterator elements = array.elements(); + + int index = 0; + while (elements.hasNext()) { + JsonNode element = elements.next(); + + String indexKey = key + "[" + index + "]"; + + if (arraysAsRepeats) { + indexKey = key; + } + + if (element.isObject()) { + List> flatField = flattenObject((ObjectNode) element, arraysAsRepeats); + addAll(flat, flatField, indexKey); + } else if (element.isArray()) { + List> flatField = flattenArray((ArrayNode) element, "", arraysAsRepeats); + addAll(flat, flatField, indexKey); + } else { + flat.add(new AbstractMap.SimpleEntry<>(indexKey, element)); + } + + index++; + } + + return flat; + } + + private static void addAll( + List> target, List> source, String prefix) { + for (Map.Entry entry : source) { + Map.Entry entryToAdd = + new AbstractMap.SimpleEntry<>(prefix + entry.getKey(), entry.getValue()); + target.add(entryToAdd); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/RequestOptions.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/RequestOptions.java new file mode 100644 index 00000000000..e78b8620b59 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/RequestOptions.java @@ -0,0 +1,87 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +public final class RequestOptions { + private final Optional timeout; + + private final TimeUnit timeoutTimeUnit; + + private final Map headers; + + private final Map> headerSuppliers; + + private RequestOptions( + Optional timeout, + TimeUnit timeoutTimeUnit, + Map headers, + Map> headerSuppliers) { + this.timeout = timeout; + this.timeoutTimeUnit = timeoutTimeUnit; + this.headers = headers; + this.headerSuppliers = headerSuppliers; + } + + public Optional getTimeout() { + return timeout; + } + + public TimeUnit getTimeoutTimeUnit() { + return timeoutTimeUnit; + } + + public Map getHeaders() { + Map headers = new HashMap<>(); + headers.putAll(this.headers); + this.headerSuppliers.forEach((key, supplier) -> { + headers.put(key, supplier.get()); + }); + return headers; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Optional timeout = Optional.empty(); + + private TimeUnit timeoutTimeUnit = TimeUnit.SECONDS; + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + public Builder timeout(Integer timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + public Builder timeout(Integer timeout, TimeUnit timeoutTimeUnit) { + this.timeout = Optional.of(timeout); + this.timeoutTimeUnit = timeoutTimeUnit; + return this; + } + + public Builder addHeader(String key, String value) { + this.headers.put(key, value); + return this; + } + + public Builder addHeader(String key, Supplier value) { + this.headerSuppliers.put(key, value); + return this; + } + + public RequestOptions build() { + return new RequestOptions(timeout, timeoutTimeUnit, headers, headerSuppliers); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/ResponseBodyInputStream.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/ResponseBodyInputStream.java new file mode 100644 index 00000000000..db05d538255 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/ResponseBodyInputStream.java @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.FilterInputStream; +import java.io.IOException; +import okhttp3.Response; + +/** + * A custom InputStream that wraps the InputStream from the OkHttp Response and ensures that the + * OkHttp Response object is properly closed when the stream is closed. + * + * This class extends FilterInputStream and takes an OkHttp Response object as a parameter. + * It retrieves the InputStream from the Response and overrides the close method to close + * both the InputStream and the Response object, ensuring proper resource management and preventing + * premature closure of the underlying HTTP connection. + */ +public class ResponseBodyInputStream extends FilterInputStream { + private final Response response; + + /** + * Constructs a ResponseBodyInputStream that wraps the InputStream from the given OkHttp + * Response object. + * + * @param response the OkHttp Response object from which the InputStream is retrieved + * @throws IOException if an I/O error occurs while retrieving the InputStream + */ + public ResponseBodyInputStream(Response response) throws IOException { + super(response.body().byteStream()); + this.response = response; + } + + /** + * Closes the InputStream and the associated OkHttp Response object. This ensures that the + * underlying HTTP connection is properly closed after the stream is no longer needed. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + super.close(); + response.close(); // Ensure the response is closed when the stream is closed + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/ResponseBodyReader.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/ResponseBodyReader.java new file mode 100644 index 00000000000..97fcf7a0efb --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/ResponseBodyReader.java @@ -0,0 +1,44 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.FilterReader; +import java.io.IOException; +import okhttp3.Response; + +/** + * A custom Reader that wraps the Reader from the OkHttp Response and ensures that the + * OkHttp Response object is properly closed when the reader is closed. + * + * This class extends FilterReader and takes an OkHttp Response object as a parameter. + * It retrieves the Reader from the Response and overrides the close method to close + * both the Reader and the Response object, ensuring proper resource management and preventing + * premature closure of the underlying HTTP connection. + */ +public class ResponseBodyReader extends FilterReader { + private final Response response; + + /** + * Constructs a ResponseBodyReader that wraps the Reader from the given OkHttp Response object. + * + * @param response the OkHttp Response object from which the Reader is retrieved + * @throws IOException if an I/O error occurs while retrieving the Reader + */ + public ResponseBodyReader(Response response) throws IOException { + super(response.body().charStream()); + this.response = response; + } + + /** + * Closes the Reader and the associated OkHttp Response object. This ensures that the + * underlying HTTP connection is properly closed after the reader is no longer needed. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + super.close(); + response.close(); // Ensure the response is closed when the reader is closed + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/RetryInterceptor.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/RetryInterceptor.java new file mode 100644 index 00000000000..728715567d3 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/RetryInterceptor.java @@ -0,0 +1,78 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.IOException; +import java.time.Duration; +import java.util.Optional; +import java.util.Random; +import okhttp3.Interceptor; +import okhttp3.Response; + +public class RetryInterceptor implements Interceptor { + + private static final Duration ONE_SECOND = Duration.ofSeconds(1); + private final ExponentialBackoff backoff; + private final Random random = new Random(); + + public RetryInterceptor(int maxRetries) { + this.backoff = new ExponentialBackoff(maxRetries); + } + + @Override + public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + + if (shouldRetry(response.code())) { + return retryChain(response, chain); + } + + return response; + } + + private Response retryChain(Response response, Chain chain) throws IOException { + Optional nextBackoff = this.backoff.nextBackoff(); + while (nextBackoff.isPresent()) { + try { + Thread.sleep(nextBackoff.get().toMillis()); + } catch (InterruptedException e) { + throw new IOException("Interrupted while trying request", e); + } + response.close(); + response = chain.proceed(chain.request()); + if (shouldRetry(response.code())) { + nextBackoff = this.backoff.nextBackoff(); + } else { + return response; + } + } + + return response; + } + + private static boolean shouldRetry(int statusCode) { + return statusCode == 408 || statusCode == 429 || statusCode >= 500; + } + + private final class ExponentialBackoff { + + private final int maxNumRetries; + + private int retryNumber = 0; + + ExponentialBackoff(int maxNumRetries) { + this.maxNumRetries = maxNumRetries; + } + + public Optional nextBackoff() { + retryNumber += 1; + if (retryNumber > maxNumRetries) { + return Optional.empty(); + } + + int upperBound = (int) Math.pow(2, retryNumber); + return Optional.of(ONE_SECOND.multipliedBy(random.nextInt(upperBound))); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/SeedApiApiException.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/SeedApiApiException.java new file mode 100644 index 00000000000..3c5b986e003 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/SeedApiApiException.java @@ -0,0 +1,73 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.Response; + +/** + * This exception type will be thrown for any non-2XX API responses. + */ +public class SeedApiApiException extends SeedApiException { + /** + * The error code of the response that triggered the exception. + */ + private final int statusCode; + + /** + * The body of the response that triggered the exception. + */ + private final Object body; + + private final Map> headers; + + public SeedApiApiException(String message, int statusCode, Object body) { + super(message); + this.statusCode = statusCode; + this.body = body; + this.headers = new HashMap<>(); + } + + public SeedApiApiException(String message, int statusCode, Object body, Response rawResponse) { + super(message); + this.statusCode = statusCode; + this.body = body; + this.headers = new HashMap<>(); + rawResponse.headers().forEach(header -> { + String key = header.component1(); + String value = header.component2(); + this.headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); + }); + } + + /** + * @return the statusCode + */ + public int statusCode() { + return this.statusCode; + } + + /** + * @return the body + */ + public Object body() { + return this.body; + } + + /** + * @return the headers + */ + public Map> headers() { + return this.headers; + } + + @java.lang.Override + public String toString() { + return "SeedApiApiException{" + "message: " + getMessage() + ", statusCode: " + statusCode + ", body: " + body + + "}"; + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/SeedApiException.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/SeedApiException.java new file mode 100644 index 00000000000..cf823606667 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/SeedApiException.java @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +/** + * This class serves as the base exception for all errors in the SDK. + */ +public class SeedApiException extends RuntimeException { + public SeedApiException(String message) { + super(message); + } + + public SeedApiException(String message, Exception e) { + super(message, e); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/SeedApiHttpResponse.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/SeedApiHttpResponse.java new file mode 100644 index 00000000000..814561a79e0 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/SeedApiHttpResponse.java @@ -0,0 +1,37 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.Response; + +public final class SeedApiHttpResponse { + + private final T body; + + private final Map> headers; + + public SeedApiHttpResponse(T body, Response rawResponse) { + this.body = body; + + Map> headers = new HashMap<>(); + rawResponse.headers().forEach(header -> { + String key = header.component1(); + String value = header.component2(); + headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); + }); + this.headers = headers; + } + + public T body() { + return this.body; + } + + public Map> headers() { + return headers; + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/Stream.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/Stream.java new file mode 100644 index 00000000000..303eaf050fe --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/Stream.java @@ -0,0 +1,272 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.Closeable; +import java.io.IOException; +import java.io.Reader; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Scanner; + +/** + * The {@code Stream} class implements {@link Iterable} to provide a simple mechanism for reading and parsing + * objects of a given type from data streamed via a {@link Reader} using a specified delimiter. + *

+ * {@code Stream} assumes that data is being pushed to the provided {@link Reader} asynchronously and utilizes a + * {@code Scanner} to block during iteration if the next object is not available. + * Iterable stream for parsing JSON and Server-Sent Events (SSE) data. + * Supports both newline-delimited JSON and SSE with optional stream termination. + * + * @param The type of objects in the stream. + */ +public final class Stream implements Iterable, Closeable { + + private static final String NEWLINE = "\n"; + private static final String DATA_PREFIX = "data:"; + + public enum StreamType { + JSON, + SSE + } + + private final Class valueType; + private final Scanner scanner; + private final StreamType streamType; + private final String messageTerminator; + private final String streamTerminator; + private final Reader sseReader; + private boolean isClosed = false; + + /** + * Constructs a new {@code Stream} with the specified value type, reader, and delimiter. + * + * @param valueType The class of the objects in the stream. + * @param reader The reader that provides the streamed data. + * @param delimiter The delimiter used to separate elements in the stream. + */ + public Stream(Class valueType, Reader reader, String delimiter) { + this.valueType = valueType; + this.scanner = new Scanner(reader).useDelimiter(delimiter); + this.streamType = StreamType.JSON; + this.messageTerminator = delimiter; + this.streamTerminator = null; + this.sseReader = null; + } + + private Stream(Class valueType, StreamType type, Reader reader, String terminator) { + this.valueType = valueType; + this.streamType = type; + if (type == StreamType.JSON) { + this.scanner = new Scanner(reader).useDelimiter(terminator); + this.messageTerminator = terminator; + this.streamTerminator = null; + this.sseReader = null; + } else { + this.scanner = null; + this.messageTerminator = NEWLINE; + this.streamTerminator = terminator; + this.sseReader = reader; + } + } + + public static Stream fromJson(Class valueType, Reader reader, String delimiter) { + return new Stream<>(valueType, reader, delimiter); + } + + public static Stream fromJson(Class valueType, Reader reader) { + return new Stream<>(valueType, reader, NEWLINE); + } + + public static Stream fromSse(Class valueType, Reader sseReader) { + return new Stream<>(valueType, StreamType.SSE, sseReader, null); + } + + public static Stream fromSse(Class valueType, Reader sseReader, String streamTerminator) { + return new Stream<>(valueType, StreamType.SSE, sseReader, streamTerminator); + } + + @Override + public void close() throws IOException { + if (!isClosed) { + isClosed = true; + if (scanner != null) { + scanner.close(); + } + if (sseReader != null) { + sseReader.close(); + } + } + } + + private boolean isStreamClosed() { + return isClosed; + } + + /** + * Returns an iterator over the elements in this stream that blocks during iteration when the next object is + * not yet available. + * + * @return An iterator that can be used to traverse the elements in the stream. + */ + @Override + public Iterator iterator() { + if (streamType == StreamType.SSE) { + return new SSEIterator(); + } else { + return new JsonIterator(); + } + } + + private final class JsonIterator implements Iterator { + + /** + * Returns {@code true} if there are more elements in the stream. + *

+ * Will block and wait for input if the stream has not ended and the next object is not yet available. + * + * @return {@code true} if there are more elements, {@code false} otherwise. + */ + @Override + public boolean hasNext() { + if (isStreamClosed()) { + return false; + } + return scanner.hasNext(); + } + + /** + * Returns the next element in the stream. + *

+ * Will block and wait for input if the stream has not ended and the next object is not yet available. + * + * @return The next element in the stream. + * @throws NoSuchElementException If there are no more elements in the stream. + */ + @Override + public T next() { + if (isStreamClosed()) { + throw new NoSuchElementException("Stream is closed"); + } + + if (!scanner.hasNext()) { + throw new NoSuchElementException(); + } else { + try { + T parsedResponse = + ObjectMappers.JSON_MAPPER.readValue(scanner.next().trim(), valueType); + return parsedResponse; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + private final class SSEIterator implements Iterator { + private Scanner sseScanner; + private T nextItem; + private boolean hasNextItem = false; + private boolean endOfStream = false; + private StringBuilder buffer = new StringBuilder(); + private boolean prefixSeen = false; + + private SSEIterator() { + if (sseReader != null && !isStreamClosed()) { + this.sseScanner = new Scanner(sseReader); + } else { + this.endOfStream = true; + } + } + + @Override + public boolean hasNext() { + if (isStreamClosed() || endOfStream) { + return false; + } + + if (hasNextItem) { + return true; + } + + return readNextMessage(); + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException("No more elements in stream"); + } + + T result = nextItem; + nextItem = null; + hasNextItem = false; + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + private boolean readNextMessage() { + if (sseScanner == null || isStreamClosed()) { + endOfStream = true; + return false; + } + + try { + while (sseScanner.hasNextLine()) { + String chunk = sseScanner.nextLine(); + buffer.append(chunk).append(NEWLINE); + + int terminatorIndex; + while ((terminatorIndex = buffer.indexOf(messageTerminator)) >= 0) { + String line = buffer.substring(0, terminatorIndex + messageTerminator.length()); + buffer.delete(0, terminatorIndex + messageTerminator.length()); + + line = line.trim(); + if (line.isEmpty()) { + continue; + } + + if (!prefixSeen && line.startsWith(DATA_PREFIX)) { + prefixSeen = true; + line = line.substring(DATA_PREFIX.length()).trim(); + } else if (!prefixSeen) { + continue; + } + + if (streamTerminator != null && line.contains(streamTerminator)) { + endOfStream = true; + return false; + } + + try { + nextItem = ObjectMappers.JSON_MAPPER.readValue(line, valueType); + hasNextItem = true; + prefixSeen = false; + return true; + } catch (Exception parseEx) { + continue; + } + } + } + + endOfStream = true; + return false; + + } catch (Exception e) { + System.err.println("Failed to parse SSE stream: " + e.getMessage()); + endOfStream = true; + return false; + } + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/Suppliers.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/Suppliers.java new file mode 100644 index 00000000000..a3c24e96857 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/core/Suppliers.java @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +public final class Suppliers { + private Suppliers() {} + + public static Supplier memoize(Supplier delegate) { + AtomicReference value = new AtomicReference<>(); + return () -> { + T val = value.get(); + if (val == null) { + val = value.updateAndGet(cur -> cur == null ? Objects.requireNonNull(delegate.get()) : cur); + } + return val; + }; + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/requests/NonNullableObject.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/requests/NonNullableObject.java new file mode 100644 index 00000000000..59fb2031f2d --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/requests/NonNullableObject.java @@ -0,0 +1,145 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +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.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = NonNullableObject.Builder.class) +public final class NonNullableObject { + private final Optional nonNullableObjectId; + + private final Optional name; + + private final Optional age; + + private final Map additionalProperties; + + private NonNullableObject( + Optional nonNullableObjectId, + Optional name, + Optional age, + Map additionalProperties) { + this.nonNullableObjectId = nonNullableObjectId; + this.name = name; + this.age = age; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("id") + public Optional getNonNullableObjectId() { + return nonNullableObjectId; + } + + @JsonProperty("name") + public Optional getName() { + return name; + } + + @JsonProperty("age") + public Optional getAge() { + return age; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof NonNullableObject && equalTo((NonNullableObject) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(NonNullableObject other) { + return nonNullableObjectId.equals(other.nonNullableObjectId) + && name.equals(other.name) + && age.equals(other.age); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.nonNullableObjectId, this.name, this.age); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional nonNullableObjectId = Optional.empty(); + + private Optional name = Optional.empty(); + + private Optional age = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(NonNullableObject other) { + nonNullableObjectId(other.getNonNullableObjectId()); + name(other.getName()); + age(other.getAge()); + return this; + } + + @JsonSetter(value = "id", nulls = Nulls.SKIP) + public Builder nonNullableObjectId(Optional nonNullableObjectId) { + this.nonNullableObjectId = nonNullableObjectId; + return this; + } + + public Builder nonNullableObjectId(String nonNullableObjectId) { + this.nonNullableObjectId = Optional.ofNullable(nonNullableObjectId); + return this; + } + + @JsonSetter(value = "name", nulls = Nulls.SKIP) + public Builder name(Optional name) { + this.name = name; + return this; + } + + public Builder name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + @JsonSetter(value = "age", nulls = Nulls.SKIP) + public Builder age(Optional age) { + this.age = age; + return this; + } + + public Builder age(Integer age) { + this.age = Optional.ofNullable(age); + return this; + } + + public NonNullableObject build() { + return new NonNullableObject(nonNullableObjectId, name, age, additionalProperties); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/requests/PostWithNullableNamedRequestBodyTypeRequest.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/requests/PostWithNullableNamedRequestBodyTypeRequest.java new file mode 100644 index 00000000000..3aa75bf3fe0 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/requests/PostWithNullableNamedRequestBodyTypeRequest.java @@ -0,0 +1,98 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +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.api.core.ObjectMappers; +import com.seed.api.types.NullableObject; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = PostWithNullableNamedRequestBodyTypeRequest.Builder.class) +public final class PostWithNullableNamedRequestBodyTypeRequest { + private final Optional body; + + private final Map additionalProperties; + + private PostWithNullableNamedRequestBodyTypeRequest( + Optional body, Map additionalProperties) { + this.body = body; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("body") + public Optional getBody() { + return body; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof PostWithNullableNamedRequestBodyTypeRequest + && equalTo((PostWithNullableNamedRequestBodyTypeRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(PostWithNullableNamedRequestBodyTypeRequest other) { + return body.equals(other.body); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.body); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional body = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(PostWithNullableNamedRequestBodyTypeRequest other) { + body(other.getBody()); + return this; + } + + @JsonSetter(value = "body", nulls = Nulls.SKIP) + public Builder body(Optional body) { + this.body = body; + return this; + } + + public Builder body(NullableObject body) { + this.body = Optional.ofNullable(body); + return this; + } + + public PostWithNullableNamedRequestBodyTypeRequest build() { + return new PostWithNullableNamedRequestBodyTypeRequest(body, additionalProperties); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/types/NullableObject.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/types/NullableObject.java new file mode 100644 index 00000000000..00c392cc04a --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/types/NullableObject.java @@ -0,0 +1,143 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +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.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = NullableObject.Builder.class) +public final class NullableObject { + private final Optional id; + + private final Optional name; + + private final Optional age; + + private final Map additionalProperties; + + private NullableObject( + Optional id, + Optional name, + Optional age, + Map additionalProperties) { + this.id = id; + this.name = name; + this.age = age; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("id") + public Optional getId() { + return id; + } + + @JsonProperty("name") + public Optional getName() { + return name; + } + + @JsonProperty("age") + public Optional getAge() { + return age; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof NullableObject && equalTo((NullableObject) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(NullableObject other) { + return id.equals(other.id) && name.equals(other.name) && age.equals(other.age); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.name, this.age); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional id = Optional.empty(); + + private Optional name = Optional.empty(); + + private Optional age = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(NullableObject other) { + id(other.getId()); + name(other.getName()); + age(other.getAge()); + return this; + } + + @JsonSetter(value = "id", nulls = Nulls.SKIP) + public Builder id(Optional id) { + this.id = id; + return this; + } + + public Builder id(String id) { + this.id = Optional.ofNullable(id); + return this; + } + + @JsonSetter(value = "name", nulls = Nulls.SKIP) + public Builder name(Optional name) { + this.name = name; + return this; + } + + public Builder name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + @JsonSetter(value = "age", nulls = Nulls.SKIP) + public Builder age(Optional age) { + this.age = age; + return this; + } + + public Builder age(Integer age) { + this.age = Optional.ofNullable(age); + return this; + } + + public NullableObject build() { + return new NullableObject(id, name, age, additionalProperties); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/types/ResponseBody.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/types/ResponseBody.java new file mode 100644 index 00000000000..83c1f59df5a --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/seed/api/types/ResponseBody.java @@ -0,0 +1,95 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +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.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = ResponseBody.Builder.class) +public final class ResponseBody { + private final Optional success; + + private final Map additionalProperties; + + private ResponseBody(Optional success, Map additionalProperties) { + this.success = success; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("success") + public Optional getSuccess() { + return success; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof ResponseBody && equalTo((ResponseBody) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(ResponseBody other) { + return success.equals(other.success); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.success); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional success = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(ResponseBody other) { + success(other.getSuccess()); + return this; + } + + @JsonSetter(value = "success", nulls = Nulls.SKIP) + public Builder success(Optional success) { + this.success = success; + return this; + } + + public Builder success(Boolean success) { + this.success = Optional.ofNullable(success); + return this; + } + + public ResponseBody build() { + return new ResponseBody(success, additionalProperties); + } + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/snippets/Example0.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/snippets/Example0.java new file mode 100644 index 00000000000..ecdb54011f2 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/snippets/Example0.java @@ -0,0 +1,29 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; +import com.seed.api.requests.PostWithNullableNamedRequestBodyTypeRequest; +import com.seed.api.types.NullableObject; + +public class Example0 { + public static void main(String[] args) { + SeedApiClient client = SeedApiClient + .builder() + .url("https://api.fern.com") + .build(); + + client.postWithNullableNamedRequestBodyType( + "id", + PostWithNullableNamedRequestBodyTypeRequest + .builder() + .body( + NullableObject + .builder() + .id("id") + .name("name") + .age(1) + .build() + ) + .build() + ); + } +} \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/snippets/Example1.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/snippets/Example1.java new file mode 100644 index 00000000000..8ebe57cd081 --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/snippets/Example1.java @@ -0,0 +1,20 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; +import com.seed.api.requests.NonNullableObject; + +public class Example1 { + public static void main(String[] args) { + SeedApiClient client = SeedApiClient + .builder() + .url("https://api.fern.com") + .build(); + + client.postWithNonNullableNamedRequestBodyType( + "id", + NonNullableObject + .builder() + .build() + ); + } +} \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/snippets/Example2.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/snippets/Example2.java new file mode 100644 index 00000000000..31dce71b82e --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/main/java/com/snippets/Example2.java @@ -0,0 +1,23 @@ +package com.snippets; + +import com.seed.api.SeedApiClient; +import com.seed.api.requests.NonNullableObject; + +public class Example2 { + public static void main(String[] args) { + SeedApiClient client = SeedApiClient + .builder() + .url("https://api.fern.com") + .build(); + + client.postWithNonNullableNamedRequestBodyType( + "id", + NonNullableObject + .builder() + .nonNullableObjectId("id") + .name("name") + .age(1) + .build() + ); + } +} \ No newline at end of file diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/test/java/com/seed/api/StreamTest.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/test/java/com/seed/api/StreamTest.java new file mode 100644 index 00000000000..77f138e487f --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/test/java/com/seed/api/StreamTest.java @@ -0,0 +1,86 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import static org.junit.jupiter.api.Assertions.*; + +import com.seed.api.core.ObjectMappers; +import com.seed.api.core.Stream; +import java.io.IOException; +import java.io.StringReader; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +public final class StreamTest { + @Test + public void testJsonStream() { + List messages = List.of(Map.of("message", "hello"), Map.of("message", "world")); + List jsonStrings = messages.stream().map(StreamTest::mapToJson).collect(Collectors.toList()); + String input = String.join("\n", jsonStrings); + StringReader jsonInput = new StringReader(input); + Stream jsonStream = Stream.fromJson(Map.class, jsonInput); + int expectedMessages = 2; + int actualMessages = 0; + for (Map jsonObject : jsonStream) { + actualMessages++; + assertTrue(jsonObject.containsKey("message")); + } + assertEquals(expectedMessages, actualMessages); + } + + @Test + public void testSseStream() { + List events = List.of(Map.of("event", "start"), Map.of("event", "end")); + List sseStrings = events.stream().map(StreamTest::mapToSse).collect(Collectors.toList()); + String input = String.join("\n" + "\n", sseStrings); + StringReader sseInput = new StringReader(input); + Stream sseStream = Stream.fromSse(Map.class, sseInput); + int expectedEvents = 2; + int actualEvents = 0; + for (Map eventData : sseStream) { + actualEvents++; + assertTrue(eventData.containsKey("event")); + } + assertEquals(expectedEvents, actualEvents); + } + + @Test + public void testSseStreamWithTerminator() { + List events = List.of(Map.of("message", "first"), Map.of("message", "second")); + List sseStrings = events.stream().map(StreamTest::mapToSse).collect(Collectors.toList()); + sseStrings.add("data: [DONE]"); + String input = String.join("\n" + "\n", sseStrings); + StringReader sseInput = new StringReader(input); + Stream sseStream = Stream.fromSse(Map.class, sseInput, "[DONE]"); + int expectedEvents = 2; + int actualEvents = 0; + for (Map eventData : sseStream) { + actualEvents++; + assertTrue(eventData.containsKey("message")); + } + assertEquals(expectedEvents, actualEvents); + } + + @Test + public void testStreamResourceManagement() throws IOException { + StringReader testInput = new StringReader("{\"test\":\"data\"}"); + Stream testStream = Stream.fromJson(Map.class, testInput); + testStream.close(); + assertFalse(testStream.iterator().hasNext()); + } + + private static String mapToJson(Map map) { + try { + return ObjectMappers.JSON_MAPPER.writeValueAsString(map); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static String mapToSse(Map map) { + return "data: " + mapToJson(map); + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/test/java/com/seed/api/TestClient.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/test/java/com/seed/api/TestClient.java new file mode 100644 index 00000000000..1686cfd803c --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/test/java/com/seed/api/TestClient.java @@ -0,0 +1,11 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +public final class TestClient { + public void test() { + // Add tests here and mark this file in .fernignore + assert true; + } +} diff --git a/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/test/java/com/seed/api/core/QueryStringMapperTest.java b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/test/java/com/seed/api/core/QueryStringMapperTest.java new file mode 100644 index 00000000000..ead1a49af7a --- /dev/null +++ b/seed/java-sdk/java-nullable-named-request-types/no-custom-config/src/test/java/com/seed/api/core/QueryStringMapperTest.java @@ -0,0 +1,339 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.HttpUrl; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public final class QueryStringMapperTest { + @Test + public void testObjectWithQuotedString_indexedArrays() { + Map map = new HashMap() { + { + put("hello", "\"world\""); + } + }; + + String expectedQueryString = "withquoted%5Bhello%5D=%22world%22"; + + String actualQueryString = queryString( + new HashMap() { + { + put("withquoted", map); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectWithQuotedString_arraysAsRepeats() { + Map map = new HashMap() { + { + put("hello", "\"world\""); + } + }; + + String expectedQueryString = "withquoted%5Bhello%5D=%22world%22"; + + String actualQueryString = queryString( + new HashMap() { + { + put("withquoted", map); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObject_indexedArrays() { + Map map = new HashMap() { + { + put("foo", "bar"); + put("baz", "qux"); + } + }; + + String expectedQueryString = "metadata%5Bfoo%5D=bar&metadata%5Bbaz%5D=qux"; + + String actualQueryString = queryString( + new HashMap() { + { + put("metadata", map); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObject_arraysAsRepeats() { + Map map = new HashMap() { + { + put("foo", "bar"); + put("baz", "qux"); + } + }; + + String expectedQueryString = "metadata%5Bfoo%5D=bar&metadata%5Bbaz%5D=qux"; + + String actualQueryString = queryString( + new HashMap() { + { + put("metadata", map); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testNestedObject_indexedArrays() { + Map> nestedMap = new HashMap>() { + { + put("mapkey1", new HashMap() { + { + put("mapkey1mapkey1", "mapkey1mapkey1value"); + put("mapkey1mapkey2", "mapkey1mapkey2value"); + } + }); + put("mapkey2", new HashMap() { + { + put("mapkey2mapkey1", "mapkey2mapkey1value"); + } + }); + } + }; + + String expectedQueryString = + "nested%5Bmapkey2%5D%5Bmapkey2mapkey1%5D=mapkey2mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey1" + + "%5D=mapkey1mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey2%5D=mapkey1mapkey2value"; + + String actualQueryString = queryString( + new HashMap() { + { + put("nested", nestedMap); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testNestedObject_arraysAsRepeats() { + Map> nestedMap = new HashMap>() { + { + put("mapkey1", new HashMap() { + { + put("mapkey1mapkey1", "mapkey1mapkey1value"); + put("mapkey1mapkey2", "mapkey1mapkey2value"); + } + }); + put("mapkey2", new HashMap() { + { + put("mapkey2mapkey1", "mapkey2mapkey1value"); + } + }); + } + }; + + String expectedQueryString = + "nested%5Bmapkey2%5D%5Bmapkey2mapkey1%5D=mapkey2mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey1" + + "%5D=mapkey1mapkey1value&nested%5Bmapkey1%5D%5Bmapkey1mapkey2%5D=mapkey1mapkey2value"; + + String actualQueryString = queryString( + new HashMap() { + { + put("nested", nestedMap); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testDateTime_indexedArrays() { + OffsetDateTime dateTime = + OffsetDateTime.ofInstant(Instant.ofEpochSecond(1740412107L), ZoneId.of("America/New_York")); + + String expectedQueryString = "datetime=2025-02-24T10%3A48%3A27-05%3A00"; + + String actualQueryString = queryString( + new HashMap() { + { + put("datetime", dateTime); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testDateTime_arraysAsRepeats() { + OffsetDateTime dateTime = + OffsetDateTime.ofInstant(Instant.ofEpochSecond(1740412107L), ZoneId.of("America/New_York")); + + String expectedQueryString = "datetime=2025-02-24T10%3A48%3A27-05%3A00"; + + String actualQueryString = queryString( + new HashMap() { + { + put("datetime", dateTime); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectArray_indexedArrays() { + List> mapArray = new ArrayList>() { + { + add(new HashMap() { + { + put("key", "hello"); + put("value", "world"); + } + }); + add(new HashMap() { + { + put("key", "foo"); + put("value", "bar"); + } + }); + add(new HashMap<>()); + } + }; + + String expectedQueryString = "objects%5B0%5D%5Bvalue%5D=world&objects%5B0%5D%5Bkey%5D=hello&objects%5B1%5D" + + "%5Bvalue%5D=bar&objects%5B1%5D%5Bkey%5D=foo"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objects", mapArray); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectArray_arraysAsRepeats() { + List> mapArray = new ArrayList>() { + { + add(new HashMap() { + { + put("key", "hello"); + put("value", "world"); + } + }); + add(new HashMap() { + { + put("key", "foo"); + put("value", "bar"); + } + }); + add(new HashMap<>()); + } + }; + + String expectedQueryString = + "objects%5Bvalue%5D=world&objects%5Bkey%5D=hello&objects%5Bvalue" + "%5D=bar&objects%5Bkey%5D=foo"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objects", mapArray); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectWithArray_indexedArrays() { + Map objectWithArray = new HashMap() { + { + put("id", "abc123"); + put("contactIds", new ArrayList() { + { + add("id1"); + add("id2"); + add("id3"); + } + }); + } + }; + + String expectedQueryString = + "objectwitharray%5Bid%5D=abc123&objectwitharray%5BcontactIds%5D%5B0%5D=id1&objectwitharray" + + "%5BcontactIds%5D%5B1%5D=id2&objectwitharray%5BcontactIds%5D%5B2%5D=id3"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objectwitharray", objectWithArray); + } + }, + false); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + @Test + public void testObjectWithArray_arraysAsRepeats() { + Map objectWithArray = new HashMap() { + { + put("id", "abc123"); + put("contactIds", new ArrayList() { + { + add("id1"); + add("id2"); + add("id3"); + } + }); + } + }; + + String expectedQueryString = "objectwitharray%5Bid%5D=abc123&objectwitharray%5BcontactIds" + + "%5D=id1&objectwitharray%5BcontactIds%5D=id2&objectwitharray%5BcontactIds%5D=id3"; + + String actualQueryString = queryString( + new HashMap() { + { + put("objectwitharray", objectWithArray); + } + }, + true); + + Assertions.assertEquals(expectedQueryString, actualQueryString); + } + + private static String queryString(Map params, boolean arraysAsRepeats) { + HttpUrl.Builder httpUrl = HttpUrl.parse("http://www.fakewebsite.com/").newBuilder(); + params.forEach((paramName, paramValue) -> + QueryStringMapper.addQueryParameter(httpUrl, paramName, paramValue, arraysAsRepeats)); + return httpUrl.build().encodedQuery(); + } +} diff --git a/seed/java-sdk/seed.yml b/seed/java-sdk/seed.yml index 8caf2f9e4ac..2e0339e981e 100644 --- a/seed/java-sdk/seed.yml +++ b/seed/java-sdk/seed.yml @@ -125,6 +125,15 @@ fixtures: wrapped-aliases: false enable-forward-compatible-enums: true outputFolder: enable-forward-compatible-enums + java-nullable-named-request-types: + - customConfig: null + outputFolder: no-custom-config + - customConfig: + enable-inline-types: true + client-class-name: Foo + inline-path-parameters: true + enable-forward-compatible-enums: true + outputFolder: found-custom-config imdb: - customConfig: disable-required-property-builder-checks: true @@ -235,6 +244,8 @@ allowedFailures: - java-inline-types - java-inline-types:no-wrapped-aliases - java-pagination-deep-cursor-path + - java-nullable-named-request-types:no-custom-config + - java-nullable-named-request-types:found-custom-config - literal - literals-unions - nullable-optional:with-nullable-annotation diff --git a/test-definitions/fern/apis/java-nullable-named-request-types/generators.yml b/test-definitions/fern/apis/java-nullable-named-request-types/generators.yml new file mode 100644 index 00000000000..79a36ca7a20 --- /dev/null +++ b/test-definitions/fern/apis/java-nullable-named-request-types/generators.yml @@ -0,0 +1,9 @@ +# yaml-language-server: $schema=https://schema.buildwithfern.dev/generators-yml.json +api: + specs: + - openapi: ./openapi.yml + settings: + title-as-schema-name: false + inline-path-parameters: true + respect-nullable-schemas: true + idiomatic-request-names: true diff --git a/test-definitions/fern/apis/java-nullable-named-request-types/openapi.yml b/test-definitions/fern/apis/java-nullable-named-request-types/openapi.yml new file mode 100644 index 00000000000..5318f8705a4 --- /dev/null +++ b/test-definitions/fern/apis/java-nullable-named-request-types/openapi.yml @@ -0,0 +1,78 @@ +openapi: 3.0.1 +info: + title: Java Nullable Named Request Types API + version: 1.0.0 +paths: + /postWithNullableNamedRequestBodyType/{id}: + post: + operationId: postWithNullableNamedRequestBodyType + parameters: + - name: id + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/NullableObject" + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + $ref: "#/components/schemas/ResponseBody" + + /postWithNonNullableNamedRequestBodyType/{id}: + post: + parameters: + - name: id + in: path + required: true + schema: + type: string + operationId: postWithNonNullableNamedRequestBodyType + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/NonNullableObject" + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + $ref: "#/components/schemas/ResponseBody" + +components: + schemas: + NullableObject: + type: object + nullable: true + properties: + id: + type: string + name: + type: string + age: + type: integer + NonNullableObject: + type: object + nullable: false + properties: + id: + type: string + name: + type: string + age: + type: integer + ResponseBody: + type: object + properties: + success: + type: boolean