diff --git a/fern/pages/changelogs/java-sdk/2024-12-10.mdx b/fern/pages/changelogs/java-sdk/2024-12-10.mdx new file mode 100644 index 00000000000..7a36665e135 --- /dev/null +++ b/fern/pages/changelogs/java-sdk/2024-12-10.mdx @@ -0,0 +1,5 @@ +## 2.4.0 +**`(feat):`** We now support overriding sdk package prefixes by adding a "package-prefix" key under the java-sdk generator +configuration. + + diff --git a/generators/java/generator-utils/src/main/java/com/fern/java/ICustomConfig.java b/generators/java/generator-utils/src/main/java/com/fern/java/ICustomConfig.java index 724d305faaa..c1021bbecd9 100644 --- a/generators/java/generator-utils/src/main/java/com/fern/java/ICustomConfig.java +++ b/generators/java/generator-utils/src/main/java/com/fern/java/ICustomConfig.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Optional; import org.immutables.value.Value; public interface ICustomConfig { @@ -58,6 +59,9 @@ default Boolean disableRequiredPropertyBuilderChecks() { return false; } + @JsonProperty("package-prefix") + Optional packagePrefix(); + enum JsonInclude { NON_EMPTY("non-empty"), NON_ABSENT("non-absent"); diff --git a/generators/java/sdk/src/main/java/com/fern/java/client/Cli.java b/generators/java/sdk/src/main/java/com/fern/java/client/Cli.java index 0fb72f3aee2..a7c250fbdb3 100644 --- a/generators/java/sdk/src/main/java/com/fern/java/client/Cli.java +++ b/generators/java/sdk/src/main/java/com/fern/java/client/Cli.java @@ -119,8 +119,12 @@ public void runInGithubModeHook( IntermediateRepresentation ir, JavaSdkCustomConfig customConfig, GithubOutputMode githubOutputMode) { - ClientPoetClassNameFactory clientPoetClassNameFactory = new ClientPoetClassNameFactory( - AbstractPoetClassNameFactory.getPackagePrefixWithOrgAndApiName(ir, generatorConfig.getOrganization())); + List packagePrefixTokens = customConfig + .packagePrefix() + .map(List::of) + .orElseGet(() -> AbstractPoetClassNameFactory.getPackagePrefixWithOrgAndApiName( + ir, generatorConfig.getOrganization())); + ClientPoetClassNameFactory clientPoetClassNameFactory = new ClientPoetClassNameFactory(packagePrefixTokens); List resolvedAuthSchemes = new FeatureResolver(ir, generatorConfig, generatorExecClient).getResolvedAuthSchemes(); ClientGeneratorContext context = new ClientGeneratorContext( @@ -152,8 +156,12 @@ public void runInPublishModeHook( IntermediateRepresentation ir, JavaSdkCustomConfig customConfig, GeneratorPublishConfig publishOutputMode) { - ClientPoetClassNameFactory clientPoetClassNameFactory = new ClientPoetClassNameFactory( - AbstractPoetClassNameFactory.getPackagePrefixWithOrgAndApiName(ir, generatorConfig.getOrganization())); + List packagePrefixTokens = customConfig + .packagePrefix() + .map(List::of) + .orElseGet(() -> AbstractPoetClassNameFactory.getPackagePrefixWithOrgAndApiName( + ir, generatorConfig.getOrganization())); + ClientPoetClassNameFactory clientPoetClassNameFactory = new ClientPoetClassNameFactory(packagePrefixTokens); List resolvedAuthSchemes = new FeatureResolver(ir, generatorConfig, generatorExecClient).getResolvedAuthSchemes(); ClientGeneratorContext context = new ClientGeneratorContext( diff --git a/generators/java/sdk/versions.yml b/generators/java/sdk/versions.yml index 0c266867c8f..db36e854b84 100644 --- a/generators/java/sdk/versions.yml +++ b/generators/java/sdk/versions.yml @@ -1,3 +1,12 @@ +- changelogEntry: + - summary: | + We now support overriding sdk package prefixes by adding a "package-prefix" key under the java-sdk generator + configuration. + type: feat + createdAt: '2024-12-10' + irVersion: 46 + version: 2.4.0 + - changelogEntry: - summary: | The rootProject.name is now set in settings.gradle and ci.yml uses ./gradlew sonatypeCentralUpload for publishing. diff --git a/packages/cli/ete-tests/src/tests/update-api-v2/__snapshots__/update-api.test.ts.snap b/packages/cli/ete-tests/src/tests/update-api-v2/__snapshots__/update-api.test.ts.snap index d1df439a677..b90172bda23 100644 --- a/packages/cli/ete-tests/src/tests/update-api-v2/__snapshots__/update-api.test.ts.snap +++ b/packages/cli/ete-tests/src/tests/update-api-v2/__snapshots__/update-api.test.ts.snap @@ -25,13 +25,13 @@ exports[`fern api update unioned > fern api update unioned 1`] = ` }, "servers": [ { - "url": "https://api.example.com", - "description": "Production", + "url": "https://try.microcks.io/rest/Train+Travel+API/1.0.0", + "description": "Mock Server", "x-internal": false }, { - "url": "https://try.microcks.io/rest/Train+Travel+API/1.0.0", - "description": "Mock Server", + "url": "https://api.example.com", + "description": "Production", "x-internal": false } ], diff --git a/packages/cli/ete-tests/src/tests/update-api/__snapshots__/update-api.test.ts.snap b/packages/cli/ete-tests/src/tests/update-api/__snapshots__/update-api.test.ts.snap index 7f8805c1c11..33948a99afb 100644 --- a/packages/cli/ete-tests/src/tests/update-api/__snapshots__/update-api.test.ts.snap +++ b/packages/cli/ete-tests/src/tests/update-api/__snapshots__/update-api.test.ts.snap @@ -25,13 +25,13 @@ exports[`fern api update > fern api update 1`] = ` }, "servers": [ { - "url": "https://api.example.com", - "description": "Production", + "url": "https://try.microcks.io/rest/Train+Travel+API/1.0.0", + "description": "Mock Server", "x-internal": false }, { - "url": "https://try.microcks.io/rest/Train+Travel+API/1.0.0", - "description": "Mock Server", + "url": "https://api.example.com", + "description": "Production", "x-internal": false } ], diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/java-custom-package-prefix/type_imdb_CreateMovieRequest.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/java-custom-package-prefix/type_imdb_CreateMovieRequest.json new file mode 100644 index 00000000000..eef0d7ac8aa --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/java-custom-package-prefix/type_imdb_CreateMovieRequest.json @@ -0,0 +1,17 @@ +{ + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "rating": { + "type": "number" + } + }, + "required": [ + "title", + "rating" + ], + "additionalProperties": false, + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/java-custom-package-prefix/type_imdb_Movie.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/java-custom-package-prefix/type_imdb_Movie.json new file mode 100644 index 00000000000..7efcc3b44b0 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/java-custom-package-prefix/type_imdb_Movie.json @@ -0,0 +1,25 @@ +{ + "type": "object", + "properties": { + "id": { + "$ref": "#/definitions/imdb.MovieId" + }, + "title": { + "type": "string" + }, + "rating": { + "type": "number" + } + }, + "required": [ + "id", + "title", + "rating" + ], + "additionalProperties": false, + "definitions": { + "imdb.MovieId": { + "type": "string" + } + } +} \ No newline at end of file diff --git a/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/java-custom-package-prefix/type_imdb_MovieId.json b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/java-custom-package-prefix/type_imdb_MovieId.json new file mode 100644 index 00000000000..cdb8c887b62 --- /dev/null +++ b/packages/cli/fern-definition/ir-to-jsonschema/src/__test__/__snapshots__/java-custom-package-prefix/type_imdb_MovieId.json @@ -0,0 +1,4 @@ +{ + "type": "string", + "definitions": {} +} \ No newline at end of file diff --git a/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/java-custom-package-prefix.json b/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/java-custom-package-prefix.json new file mode 100644 index 00000000000..67ddd958a7d --- /dev/null +++ b/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/java-custom-package-prefix.json @@ -0,0 +1,592 @@ +{ + "version": "1.0.0", + "types": { + "type_imdb:MovieId": { + "type": "alias", + "declaration": { + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + } + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + } + }, + "type_imdb:Movie": { + "type": "object", + "declaration": { + "name": { + "originalName": "Movie", + "camelCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "snakeCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE", + "safeName": "MOVIE" + }, + "pascalCase": { + "unsafeName": "Movie", + "safeName": "Movie" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + } + }, + "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": "named", + "value": "type_imdb:MovieId" + } + }, + { + "name": { + "name": { + "originalName": "title", + "camelCase": { + "unsafeName": "title", + "safeName": "title" + }, + "snakeCase": { + "unsafeName": "title", + "safeName": "title" + }, + "screamingSnakeCase": { + "unsafeName": "TITLE", + "safeName": "TITLE" + }, + "pascalCase": { + "unsafeName": "Title", + "safeName": "Title" + } + }, + "wireValue": "title" + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + } + }, + { + "name": { + "name": { + "originalName": "rating", + "camelCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "snakeCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "screamingSnakeCase": { + "unsafeName": "RATING", + "safeName": "RATING" + }, + "pascalCase": { + "unsafeName": "Rating", + "safeName": "Rating" + } + }, + "wireValue": "rating" + }, + "typeReference": { + "type": "primitive", + "value": "DOUBLE" + } + } + ] + }, + "type_imdb:CreateMovieRequest": { + "type": "object", + "declaration": { + "name": { + "originalName": "CreateMovieRequest", + "camelCase": { + "unsafeName": "createMovieRequest", + "safeName": "createMovieRequest" + }, + "snakeCase": { + "unsafeName": "create_movie_request", + "safeName": "create_movie_request" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_MOVIE_REQUEST", + "safeName": "CREATE_MOVIE_REQUEST" + }, + "pascalCase": { + "unsafeName": "CreateMovieRequest", + "safeName": "CreateMovieRequest" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + } + }, + "properties": [ + { + "name": { + "name": { + "originalName": "title", + "camelCase": { + "unsafeName": "title", + "safeName": "title" + }, + "snakeCase": { + "unsafeName": "title", + "safeName": "title" + }, + "screamingSnakeCase": { + "unsafeName": "TITLE", + "safeName": "TITLE" + }, + "pascalCase": { + "unsafeName": "Title", + "safeName": "Title" + } + }, + "wireValue": "title" + }, + "typeReference": { + "type": "primitive", + "value": "STRING" + } + }, + { + "name": { + "name": { + "originalName": "rating", + "camelCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "snakeCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "screamingSnakeCase": { + "unsafeName": "RATING", + "safeName": "RATING" + }, + "pascalCase": { + "unsafeName": "Rating", + "safeName": "Rating" + } + }, + "wireValue": "rating" + }, + "typeReference": { + "type": "primitive", + "value": "DOUBLE" + } + } + ] + } + }, + "headers": [], + "endpoints": { + "endpoint_imdb.createMovie": { + "auth": { + "type": "bearer", + "token": { + "originalName": "token", + "camelCase": { + "unsafeName": "token", + "safeName": "token" + }, + "snakeCase": { + "unsafeName": "token", + "safeName": "token" + }, + "screamingSnakeCase": { + "unsafeName": "TOKEN", + "safeName": "TOKEN" + }, + "pascalCase": { + "unsafeName": "Token", + "safeName": "Token" + } + } + }, + "declaration": { + "name": { + "originalName": "createMovie", + "camelCase": { + "unsafeName": "createMovie", + "safeName": "createMovie" + }, + "snakeCase": { + "unsafeName": "create_movie", + "safeName": "create_movie" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_MOVIE", + "safeName": "CREATE_MOVIE" + }, + "pascalCase": { + "unsafeName": "CreateMovie", + "safeName": "CreateMovie" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + } + }, + "location": { + "method": "POST", + "path": "/movies/create-movie" + }, + "request": { + "type": "body", + "pathParameters": [], + "body": { + "type": "typeReference", + "value": { + "type": "named", + "value": "type_imdb:CreateMovieRequest" + } + } + }, + "response": { + "type": "json" + } + }, + "endpoint_imdb.getMovie": { + "auth": { + "type": "bearer", + "token": { + "originalName": "token", + "camelCase": { + "unsafeName": "token", + "safeName": "token" + }, + "snakeCase": { + "unsafeName": "token", + "safeName": "token" + }, + "screamingSnakeCase": { + "unsafeName": "TOKEN", + "safeName": "TOKEN" + }, + "pascalCase": { + "unsafeName": "Token", + "safeName": "Token" + } + } + }, + "declaration": { + "name": { + "originalName": "getMovie", + "camelCase": { + "unsafeName": "getMovie", + "safeName": "getMovie" + }, + "snakeCase": { + "unsafeName": "get_movie", + "safeName": "get_movie" + }, + "screamingSnakeCase": { + "unsafeName": "GET_MOVIE", + "safeName": "GET_MOVIE" + }, + "pascalCase": { + "unsafeName": "GetMovie", + "safeName": "GetMovie" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + } + }, + "location": { + "method": "GET", + "path": "/movies/{movieId}" + }, + "request": { + "type": "body", + "pathParameters": [ + { + "name": { + "name": { + "originalName": "movieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + }, + "wireValue": "movieId" + }, + "typeReference": { + "type": "named", + "value": "type_imdb:MovieId" + } + } + ], + "body": null + }, + "response": { + "type": "json" + } + } + } +} \ No newline at end of file diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/java-custom-package-prefix.json b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/java-custom-package-prefix.json new file mode 100644 index 00000000000..26a4be39886 --- /dev/null +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/java-custom-package-prefix.json @@ -0,0 +1,274 @@ +{ + "types": { + "type_imdb:MovieId": { + "name": "MovieId", + "shape": { + "type": "alias", + "value": { + "type": "primitive", + "value": { + "type": "string" + } + } + } + }, + "type_imdb:Movie": { + "name": "Movie", + "shape": { + "type": "object", + "extends": [], + "properties": [ + { + "key": "id", + "valueType": { + "type": "id", + "value": "type_imdb:MovieId" + } + }, + { + "key": "title", + "valueType": { + "type": "primitive", + "value": { + "type": "string" + } + } + }, + { + "description": "The rating scale is one to five stars", + "key": "rating", + "valueType": { + "type": "primitive", + "value": { + "type": "double" + } + } + } + ], + "extraProperties": { + "type": "unknown" + } + } + }, + "type_imdb:CreateMovieRequest": { + "name": "CreateMovieRequest", + "shape": { + "type": "object", + "extends": [], + "properties": [ + { + "key": "title", + "valueType": { + "type": "primitive", + "value": { + "type": "string" + } + } + }, + { + "key": "rating", + "valueType": { + "type": "primitive", + "value": { + "type": "double" + } + } + } + ], + "extraProperties": { + "type": "unknown" + } + } + } + }, + "subpackages": { + "subpackage_imdb": { + "subpackageId": "subpackage_imdb", + "name": "imdb", + "endpoints": [ + { + "auth": false, + "description": "Add a movie to the database", + "method": "POST", + "id": "createMovie", + "originalEndpointId": "endpoint_imdb.createMovie", + "name": "Create Movie", + "path": { + "pathParameters": [], + "parts": [ + { + "type": "literal", + "value": "/movies" + }, + { + "type": "literal", + "value": "/create-movie" + } + ] + }, + "queryParameters": [], + "headers": [], + "request": { + "type": { + "type": "json", + "contentType": "application/json", + "shape": { + "type": "reference", + "value": { + "type": "id", + "value": "type_imdb:CreateMovieRequest" + } + } + } + }, + "response": { + "type": { + "type": "reference", + "value": { + "type": "id", + "value": "type_imdb:MovieId" + } + }, + "statusCode": 201 + }, + "errorsV2": [], + "examples": [ + { + "path": "/movies/create-movie", + "pathParameters": {}, + "queryParameters": {}, + "headers": {}, + "requestBody": { + "title": "title", + "rating": 1.1 + }, + "requestBodyV3": { + "type": "json", + "value": { + "title": "title", + "rating": 1.1 + } + }, + "responseStatusCode": 200, + "responseBody": "string", + "responseBodyV3": { + "type": "json", + "value": "string" + }, + "codeSamples": [] + } + ] + }, + { + "auth": false, + "method": "GET", + "id": "getMovie", + "originalEndpointId": "endpoint_imdb.getMovie", + "name": "Get Movie", + "path": { + "pathParameters": [ + { + "key": "movieId", + "type": { + "type": "id", + "value": "type_imdb:MovieId" + } + } + ], + "parts": [ + { + "type": "literal", + "value": "/movies" + }, + { + "type": "literal", + "value": "/" + }, + { + "type": "pathParameter", + "value": "movieId" + }, + { + "type": "literal", + "value": "" + } + ] + }, + "queryParameters": [], + "headers": [], + "response": { + "type": { + "type": "reference", + "value": { + "type": "id", + "value": "type_imdb:Movie" + } + } + }, + "errorsV2": [ + { + "type": { + "type": "alias", + "value": { + "type": "id", + "value": "type_imdb:MovieId" + } + }, + "statusCode": 404, + "name": "MovieDoesNotExistError", + "examples": [] + } + ], + "examples": [ + { + "path": "/movies/movieId", + "pathParameters": { + "movieId": "movieId" + }, + "queryParameters": {}, + "headers": {}, + "responseStatusCode": 200, + "responseBody": { + "id": "id", + "title": "title", + "rating": 1.1 + }, + "responseBodyV3": { + "type": "json", + "value": { + "id": "id", + "title": "title", + "rating": 1.1 + } + }, + "codeSamples": [] + } + ] + } + ], + "webhooks": [], + "websockets": [], + "types": [ + "type_imdb:MovieId", + "type_imdb:Movie", + "type_imdb:CreateMovieRequest" + ], + "subpackages": [] + } + }, + "rootPackage": { + "endpoints": [], + "webhooks": [], + "websockets": [], + "types": [], + "subpackages": [ + "subpackage_imdb" + ] + }, + "auth": { + "type": "bearerAuth", + "tokenName": "token" + }, + "snippetsConfiguration": {}, + "globalHeaders": [] +} \ No newline at end of file diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/.github/workflows/ci.yml b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/.github/workflows/ci.yml new file mode 100644 index 00000000000..9910fd269c2 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/.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@v3 + + - 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@v3 + + - 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@v3 + + - 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-custom-package-prefix/java-custom-package-prefix/.gitignore b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/.gitignore new file mode 100644 index 00000000000..d4199abc2cd --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/.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-custom-package-prefix/java-custom-package-prefix/.mock/definition/api.yml b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/.mock/definition/api.yml new file mode 100644 index 00000000000..c437dc0ab29 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/.mock/definition/api.yml @@ -0,0 +1,4 @@ +name: api +error-discrimination: + strategy: status-code +auth: bearer diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/.mock/definition/imdb.yml b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/.mock/definition/imdb.yml new file mode 100644 index 00000000000..4f013d969e8 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/.mock/definition/imdb.yml @@ -0,0 +1,42 @@ +types: + MovieId: string + + Movie: + properties: + id: MovieId + title: string + rating: + type: double + docs: The rating scale is one to five stars + + CreateMovieRequest: + properties: + title: string + rating: double + +service: + auth: false + base-path: /movies + endpoints: + createMovie: + docs: Add a movie to the database + method: POST + path: /create-movie + request: CreateMovieRequest + response: + type: MovieId + status-code: 201 + + getMovie: + method: GET + path: /{movieId} + path-parameters: + movieId: MovieId + response: Movie + errors: + - MovieDoesNotExistError + +errors: + MovieDoesNotExistError: + status-code: 404 + type: MovieId diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/.mock/definition/service.yml b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/.mock/definition/service.yml new file mode 100644 index 00000000000..a253b468847 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/.mock/definition/service.yml @@ -0,0 +1,11 @@ +service: + auth: false + base-path: /test + endpoints: + test: + docs: A test endpoint + method: GET + path: /endpoint + response: + type: string + status-code: 200 \ No newline at end of file diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/.mock/fern.config.json b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/.mock/generators.yml b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/build.gradle b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/build.gradle new file mode 100644 index 00000000000..325687305c1 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/build.gradle @@ -0,0 +1,101 @@ +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' +} + + +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-custom-package-prefix" +} + +sourcesJar { + archiveBaseName = "java-custom-package-prefix" +} + +javadocJar { + archiveBaseName = "java-custom-package-prefix" +} + +test { + useJUnitPlatform() + testLogging { + showStandardStreams = true + } +} + +publishing { + publications { + maven(MavenPublication) { + groupId = 'com.fern' + artifactId = 'java-custom-package-prefix' + 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-custom-package-prefix/fern.git' + developerConnection = 'scm:git:git://github.com/java-custom-package-prefix/fern.git' + url = 'https://github.com/java-custom-package-prefix/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-custom-package-prefix/java-custom-package-prefix/sample-app/build.gradle b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/sample-app/build.gradle new file mode 100644 index 00000000000..4ee8f227b7a --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/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-custom-package-prefix/java-custom-package-prefix/sample-app/src/main/java/sample/App.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/sample-app/src/main/java/sample/App.java new file mode 100644 index 00000000000..625e24c65cf --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/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.customprefix.SeedApiClient + } +} diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/settings.gradle b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/settings.gradle new file mode 100644 index 00000000000..565887d9744 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'java-custom-package-prefix' + +include 'sample-app' \ No newline at end of file diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/snippet-templates.json b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/snippet.json b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/SeedApiClient.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/SeedApiClient.java new file mode 100644 index 00000000000..7a2604ab6f2 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/SeedApiClient.java @@ -0,0 +1,36 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix; + +import com.customprefix.core.ClientOptions; +import com.customprefix.core.Suppliers; +import com.customprefix.resources.imdb.ImdbClient; +import com.customprefix.resources.service.ServiceClient; +import java.util.function.Supplier; + +public class SeedApiClient { + protected final ClientOptions clientOptions; + + protected final Supplier imdbClient; + + protected final Supplier serviceClient; + + public SeedApiClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.imdbClient = Suppliers.memoize(() -> new ImdbClient(clientOptions)); + this.serviceClient = Suppliers.memoize(() -> new ServiceClient(clientOptions)); + } + + public ImdbClient imdb() { + return this.imdbClient.get(); + } + + public ServiceClient service() { + return this.serviceClient.get(); + } + + public static SeedApiClientBuilder builder() { + return new SeedApiClientBuilder(); + } +} diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/SeedApiClientBuilder.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/SeedApiClientBuilder.java new file mode 100644 index 00000000000..7b9cf78329f --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/SeedApiClientBuilder.java @@ -0,0 +1,34 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix; + +import com.customprefix.core.ClientOptions; +import com.customprefix.core.Environment; + +public final class SeedApiClientBuilder { + private ClientOptions.Builder clientOptionsBuilder = ClientOptions.builder(); + + private String token = null; + + private Environment environment; + + /** + * Sets token + */ + public SeedApiClientBuilder token(String token) { + this.token = token; + return this; + } + + public SeedApiClientBuilder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + public SeedApiClient build() { + this.clientOptionsBuilder.addHeader("Authorization", "Bearer " + this.token); + clientOptionsBuilder.environment(this.environment); + return new SeedApiClient(clientOptionsBuilder.build()); + } +} diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/ClientOptions.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/ClientOptions.java new file mode 100644 index 00000000000..9ab83ca8065 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/ClientOptions.java @@ -0,0 +1,103 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.core; + +import java.util.HashMap; +import java.util.Map; +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 ClientOptions( + Environment environment, + Map headers, + Map> headerSuppliers, + OkHttpClient httpClient) { + this.environment = environment; + this.headers = new HashMap<>(); + this.headers.putAll(headers); + this.headers.putAll(new HashMap() { + { + put("X-Fern-Language", "JAVA"); + } + }); + this.headerSuppliers = headerSuppliers; + this.httpClient = httpClient; + } + + 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 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 final class Builder { + private Environment environment; + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + 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; + } + + public ClientOptions build() { + OkHttpClient okhttpClient = new OkHttpClient.Builder() + .addInterceptor(new RetryInterceptor(3)) + .build(); + return new ClientOptions(environment, headers, headerSuppliers, okhttpClient); + } + } +} diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/DateTimeDeserializer.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/DateTimeDeserializer.java new file mode 100644 index 00000000000..7ec50181466 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/DateTimeDeserializer.java @@ -0,0 +1,55 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.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-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/Environment.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/Environment.java new file mode 100644 index 00000000000..8a7a2a0c750 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/Environment.java @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.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-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/FileStream.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/FileStream.java new file mode 100644 index 00000000000..c334ce346f7 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/FileStream.java @@ -0,0 +1,60 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.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-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/InputStreamRequestBody.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/InputStreamRequestBody.java new file mode 100644 index 00000000000..cba65e17bff --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/InputStreamRequestBody.java @@ -0,0 +1,79 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.core; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Objects; +import okhttp3.MediaType; +import okhttp3.RequestBody; +import okhttp3.internal.Util; +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 { + Source source = null; + try { + source = Okio.source(inputStream); + sink.writeAll(source); + } finally { + Util.closeQuietly(Objects.requireNonNull(source)); + } + } +} diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/MediaTypes.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/MediaTypes.java new file mode 100644 index 00000000000..fcf1b8a8bfa --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/MediaTypes.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.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-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/ObjectMappers.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/ObjectMappers.java new file mode 100644 index 00000000000..f5d87112b73 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/ObjectMappers.java @@ -0,0 +1,36 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.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-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/RequestOptions.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/RequestOptions.java new file mode 100644 index 00000000000..56d1336f2be --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/RequestOptions.java @@ -0,0 +1,71 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +public final class RequestOptions { + private final String token; + + private final Optional timeout; + + private final TimeUnit timeoutTimeUnit; + + private RequestOptions(String token, Optional timeout, TimeUnit timeoutTimeUnit) { + this.token = token; + this.timeout = timeout; + this.timeoutTimeUnit = timeoutTimeUnit; + } + + public Optional getTimeout() { + return timeout; + } + + public TimeUnit getTimeoutTimeUnit() { + return timeoutTimeUnit; + } + + public Map getHeaders() { + Map headers = new HashMap<>(); + if (this.token != null) { + headers.put("Authorization", "Bearer " + this.token); + } + return headers; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String token = null; + + private Optional timeout = Optional.empty(); + + private TimeUnit timeoutTimeUnit = TimeUnit.SECONDS; + + public Builder token(String token) { + this.token = token; + return this; + } + + 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 RequestOptions build() { + return new RequestOptions(token, timeout, timeoutTimeUnit); + } + } +} diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/ResponseBodyInputStream.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/ResponseBodyInputStream.java new file mode 100644 index 00000000000..7ec3762ce4b --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/ResponseBodyInputStream.java @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.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-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/ResponseBodyReader.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/ResponseBodyReader.java new file mode 100644 index 00000000000..8d943224aa6 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/ResponseBodyReader.java @@ -0,0 +1,44 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.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-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/RetryInterceptor.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/RetryInterceptor.java new file mode 100644 index 00000000000..023ab819ec2 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/RetryInterceptor.java @@ -0,0 +1,78 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.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 == 409 || 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-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/SeedApiApiException.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/SeedApiApiException.java new file mode 100644 index 00000000000..d5cb9f62ba6 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/SeedApiApiException.java @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.core; + +/** + * 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; + + public SeedApiApiException(String message, int statusCode, Object body) { + super(message); + this.statusCode = statusCode; + this.body = body; + } + + /** + * @return the statusCode + */ + public int statusCode() { + return this.statusCode; + } + + /** + * @return the body + */ + public Object body() { + return this.body; + } + + @java.lang.Override + public String toString() { + return "SeedApiApiException{" + "message: " + getMessage() + ", statusCode: " + statusCode + ", body: " + body + + "}"; + } +} diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/SeedApiException.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/SeedApiException.java new file mode 100644 index 00000000000..4f9f1c4e3f1 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/SeedApiException.java @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.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-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/Stream.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/Stream.java new file mode 100644 index 00000000000..c43a685ee2d --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/Stream.java @@ -0,0 +1,97 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.core; + +import java.io.Reader; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Scanner; + +/** + * The {@code Stream} class implmenets {@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. + * + * @param The type of objects in the stream. + */ +public final class Stream implements Iterable { + /** + * The {@link Class} of the objects in the stream. + */ + private final Class valueType; + /** + * The {@link Scanner} used for reading from the input stream and blocking when neede during iteration. + */ + private final Scanner scanner; + + /** + * 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.scanner = new Scanner(reader).useDelimiter(delimiter); + this.valueType = valueType; + } + + /** + * 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() { + return new 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() { + 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 (!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); + } + } + } + + /** + * Removing elements from {@code Stream} is not supported. + * + * @throws UnsupportedOperationException Always, as removal is not supported. + */ + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } +} diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/Suppliers.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/Suppliers.java new file mode 100644 index 00000000000..aedc1c31f7c --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/core/Suppliers.java @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.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-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/resources/imdb/ImdbClient.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/resources/imdb/ImdbClient.java new file mode 100644 index 00000000000..79ef86992a0 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/resources/imdb/ImdbClient.java @@ -0,0 +1,122 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.resources.imdb; + +import com.customprefix.core.ClientOptions; +import com.customprefix.core.MediaTypes; +import com.customprefix.core.ObjectMappers; +import com.customprefix.core.RequestOptions; +import com.customprefix.core.SeedApiApiException; +import com.customprefix.core.SeedApiException; +import com.customprefix.resources.imdb.errors.MovieDoesNotExistError; +import com.customprefix.resources.imdb.types.CreateMovieRequest; +import com.customprefix.resources.imdb.types.Movie; +import com.fasterxml.jackson.core.JsonProcessingException; +import java.io.IOException; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class ImdbClient { + protected final ClientOptions clientOptions; + + public ImdbClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + /** + * Add a movie to the database + */ + public String createMovie(CreateMovieRequest request) { + return createMovie(request, null); + } + + /** + * Add a movie to the database + */ + public String createMovie(CreateMovieRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("movies") + .addPathSegments("create-movie") + .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") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + if (response.isSuccessful()) { + return ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), String.class); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new SeedApiApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class)); + } catch (IOException e) { + throw new SeedApiException("Network error executing HTTP request", e); + } + } + + public Movie getMovie(String movieId) { + return getMovie(movieId, null); + } + + public Movie getMovie(String movieId, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("movies") + .addPathSegment(movieId) + .build(); + Request okhttpRequest = new Request.Builder() + .url(httpUrl) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + if (response.isSuccessful()) { + return ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), Movie.class); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + try { + if (response.code() == 404) { + throw new MovieDoesNotExistError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, String.class)); + } + } catch (JsonProcessingException ignored) { + // unable to map error response, throwing generic error + } + throw new SeedApiApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class)); + } catch (IOException e) { + throw new SeedApiException("Network error executing HTTP request", e); + } + } +} diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/resources/imdb/errors/MovieDoesNotExistError.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/resources/imdb/errors/MovieDoesNotExistError.java new file mode 100644 index 00000000000..ba240d7a9a2 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/resources/imdb/errors/MovieDoesNotExistError.java @@ -0,0 +1,26 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.resources.imdb.errors; + +import com.customprefix.core.SeedApiApiException; + +public final class MovieDoesNotExistError extends SeedApiApiException { + /** + * The body of the response that triggered the exception. + */ + private final String body; + + public MovieDoesNotExistError(String body) { + super("MovieDoesNotExistError", 404, body); + this.body = body; + } + + /** + * @return the body + */ + @java.lang.Override + public String body() { + return this.body; + } +} diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/resources/imdb/types/CreateMovieRequest.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/resources/imdb/types/CreateMovieRequest.java new file mode 100644 index 00000000000..9a96cd726b4 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/resources/imdb/types/CreateMovieRequest.java @@ -0,0 +1,124 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.resources.imdb.types; + +import com.customprefix.core.ObjectMappers; +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.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = CreateMovieRequest.Builder.class) +public final class CreateMovieRequest { + private final String title; + + private final double rating; + + private final Map additionalProperties; + + private CreateMovieRequest(String title, double rating, Map additionalProperties) { + this.title = title; + this.rating = rating; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("title") + public String getTitle() { + return title; + } + + @JsonProperty("rating") + public double getRating() { + return rating; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof CreateMovieRequest && equalTo((CreateMovieRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(CreateMovieRequest other) { + return title.equals(other.title) && rating == other.rating; + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.title, this.rating); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static TitleStage builder() { + return new Builder(); + } + + public interface TitleStage { + RatingStage title(@NotNull String title); + + Builder from(CreateMovieRequest other); + } + + public interface RatingStage { + _FinalStage rating(double rating); + } + + public interface _FinalStage { + CreateMovieRequest build(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements TitleStage, RatingStage, _FinalStage { + private String title; + + private double rating; + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(CreateMovieRequest other) { + title(other.getTitle()); + rating(other.getRating()); + return this; + } + + @java.lang.Override + @JsonSetter("title") + public RatingStage title(@NotNull String title) { + this.title = Objects.requireNonNull(title, "title must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("rating") + public _FinalStage rating(double rating) { + this.rating = rating; + return this; + } + + @java.lang.Override + public CreateMovieRequest build() { + return new CreateMovieRequest(title, rating, additionalProperties); + } + } +} diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/resources/imdb/types/Movie.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/resources/imdb/types/Movie.java new file mode 100644 index 00000000000..aa947526222 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/resources/imdb/types/Movie.java @@ -0,0 +1,153 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.resources.imdb.types; + +import com.customprefix.core.ObjectMappers; +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.databind.annotation.JsonDeserialize; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = Movie.Builder.class) +public final class Movie { + private final String id; + + private final String title; + + private final double rating; + + private final Map additionalProperties; + + private Movie(String id, String title, double rating, Map additionalProperties) { + this.id = id; + this.title = title; + this.rating = rating; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("title") + public String getTitle() { + return title; + } + + /** + * @return The rating scale is one to five stars + */ + @JsonProperty("rating") + public double getRating() { + return rating; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof Movie && equalTo((Movie) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(Movie other) { + return id.equals(other.id) && title.equals(other.title) && rating == other.rating; + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.title, this.rating); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + TitleStage id(@NotNull String id); + + Builder from(Movie other); + } + + public interface TitleStage { + RatingStage title(@NotNull String title); + } + + public interface RatingStage { + _FinalStage rating(double rating); + } + + public interface _FinalStage { + Movie build(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements IdStage, TitleStage, RatingStage, _FinalStage { + private String id; + + private String title; + + private double rating; + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(Movie other) { + id(other.getId()); + title(other.getTitle()); + rating(other.getRating()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public TitleStage id(@NotNull String id) { + this.id = Objects.requireNonNull(id, "id must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("title") + public RatingStage title(@NotNull String title) { + this.title = Objects.requireNonNull(title, "title must not be null"); + return this; + } + + /** + *

The rating scale is one to five stars

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("rating") + public _FinalStage rating(double rating) { + this.rating = rating; + return this; + } + + @java.lang.Override + public Movie build() { + return new Movie(id, title, rating, additionalProperties); + } + } +} diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/resources/service/ServiceClient.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/resources/service/ServiceClient.java new file mode 100644 index 00000000000..656e6124f20 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/main/java/com/customprefix/resources/service/ServiceClient.java @@ -0,0 +1,66 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix.resources.service; + +import com.customprefix.core.ClientOptions; +import com.customprefix.core.ObjectMappers; +import com.customprefix.core.RequestOptions; +import com.customprefix.core.SeedApiApiException; +import com.customprefix.core.SeedApiException; +import java.io.IOException; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class ServiceClient { + protected final ClientOptions clientOptions; + + public ServiceClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + /** + * A test endpoint + */ + public String test() { + return test(null); + } + + /** + * A test endpoint + */ + public String test(RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("test") + .addPathSegments("endpoint") + .build(); + Request okhttpRequest = new Request.Builder() + .url(httpUrl) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + if (response.isSuccessful()) { + return ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), String.class); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new SeedApiApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class)); + } catch (IOException e) { + throw new SeedApiException("Network error executing HTTP request", e); + } + } +} diff --git a/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/test/java/com/customprefix/TestClient.java b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/test/java/com/customprefix/TestClient.java new file mode 100644 index 00000000000..f78f2a82cd6 --- /dev/null +++ b/seed/java-sdk/java-custom-package-prefix/java-custom-package-prefix/src/test/java/com/customprefix/TestClient.java @@ -0,0 +1,11 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.customprefix; + +public final class TestClient { + public void test() { + // Add tests here and mark this file in .fernignore + assert true; + } +} diff --git a/seed/java-sdk/seed.yml b/seed/java-sdk/seed.yml index bca41117392..39440d985c5 100644 --- a/seed/java-sdk/seed.yml +++ b/seed/java-sdk/seed.yml @@ -82,6 +82,10 @@ fixtures: - customConfig: disable-required-property-builder-checks: true outputFolder: disable-required-property-builder-checks + java-custom-package-prefix: + - customConfig: + package-prefix: "com.customprefix" + outputFolder: java-custom-package-prefix scripts: - docker: fernapi/java-seed commands: diff --git a/test-definitions/fern/apis/java-custom-package-prefix/definition/api.yml b/test-definitions/fern/apis/java-custom-package-prefix/definition/api.yml new file mode 100644 index 00000000000..c437dc0ab29 --- /dev/null +++ b/test-definitions/fern/apis/java-custom-package-prefix/definition/api.yml @@ -0,0 +1,4 @@ +name: api +error-discrimination: + strategy: status-code +auth: bearer diff --git a/test-definitions/fern/apis/java-custom-package-prefix/definition/imdb.yml b/test-definitions/fern/apis/java-custom-package-prefix/definition/imdb.yml new file mode 100644 index 00000000000..4f013d969e8 --- /dev/null +++ b/test-definitions/fern/apis/java-custom-package-prefix/definition/imdb.yml @@ -0,0 +1,42 @@ +types: + MovieId: string + + Movie: + properties: + id: MovieId + title: string + rating: + type: double + docs: The rating scale is one to five stars + + CreateMovieRequest: + properties: + title: string + rating: double + +service: + auth: false + base-path: /movies + endpoints: + createMovie: + docs: Add a movie to the database + method: POST + path: /create-movie + request: CreateMovieRequest + response: + type: MovieId + status-code: 201 + + getMovie: + method: GET + path: /{movieId} + path-parameters: + movieId: MovieId + response: Movie + errors: + - MovieDoesNotExistError + +errors: + MovieDoesNotExistError: + status-code: 404 + type: MovieId diff --git a/test-definitions/fern/apis/java-custom-package-prefix/generators.yml b/test-definitions/fern/apis/java-custom-package-prefix/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/test-definitions/fern/apis/java-custom-package-prefix/generators.yml @@ -0,0 +1 @@ +{}