diff --git a/generators/java-v2/dynamic-snippets/src/context/DynamicTypeLiteralMapper.ts b/generators/java-v2/dynamic-snippets/src/context/DynamicTypeLiteralMapper.ts index c12cdcc98823..0dbcbc690a8d 100644 --- a/generators/java-v2/dynamic-snippets/src/context/DynamicTypeLiteralMapper.ts +++ b/generators/java-v2/dynamic-snippets/src/context/DynamicTypeLiteralMapper.ts @@ -290,6 +290,23 @@ export class DynamicTypeLiteralMapper { }): java.TypeLiteral { switch (named.type) { case "alias": + if (named.typeReference.type === "unknown") { + const convertedValue = this.convert({ + typeReference: named.typeReference, + value, + as, + inUndiscriminatedUnion + }); + return java.TypeLiteral.reference( + java.invokeMethod({ + on: this.context.getJavaClassReferenceFromDeclaration({ + declaration: named.declaration + }), + method: "of", + arguments_: [convertedValue] + }) + ); + } return this.convert({ typeReference: named.typeReference, value, as, inUndiscriminatedUnion }); case "discriminatedUnion": return this.convertDiscriminatedUnion({ diff --git a/generators/java/generator-utils/src/main/java/com/fern/java/PoetTypeNameMapper.java b/generators/java/generator-utils/src/main/java/com/fern/java/PoetTypeNameMapper.java index f7f9f595ee01..583a7e8f3e11 100644 --- a/generators/java/generator-utils/src/main/java/com/fern/java/PoetTypeNameMapper.java +++ b/generators/java/generator-utils/src/main/java/com/fern/java/PoetTypeNameMapper.java @@ -75,7 +75,9 @@ public TypeName visitNamed(NamedType named) { if (isAlias) { AliasTypeDeclaration aliasTypeDeclaration = typeDeclaration.getShape().getAlias().get(); - return aliasTypeDeclaration.getResolvedType().visit(this); + if (!aliasTypeDeclaration.getAliasOf().isUnknown()) { + return aliasTypeDeclaration.getResolvedType().visit(this); + } } } return applyEnclosing( diff --git a/generators/java/generator-utils/src/main/java/com/fern/java/generators/SingleTypeGenerator.java b/generators/java/generator-utils/src/main/java/com/fern/java/generators/SingleTypeGenerator.java index 97a9260ca4d6..98cbb199dd7b 100644 --- a/generators/java/generator-utils/src/main/java/com/fern/java/generators/SingleTypeGenerator.java +++ b/generators/java/generator-utils/src/main/java/com/fern/java/generators/SingleTypeGenerator.java @@ -48,7 +48,9 @@ public SingleTypeGenerator( @Override public Optional visitAlias(AliasTypeDeclaration value) { - if (generatorContext.getCustomConfig().wrappedAliases() || fromErrorDeclaration) { + if (generatorContext.getCustomConfig().wrappedAliases() + || fromErrorDeclaration + || value.getAliasOf().isUnknown()) { AliasGenerator aliasGenerator = new AliasGenerator(className, generatorContext, value, reservedTypeNamesInScope, isTopLevelClass); return Optional.of(aliasGenerator); 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 87d870d7e00f..19f9347a4e41 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 @@ -278,7 +278,15 @@ public GeneratedRootClient generateClient( NullableNonemptyFilterGenerator nullableNonemptyFilterGenerator = new NullableNonemptyFilterGenerator(context); this.addGeneratedFile(nullableNonemptyFilterGenerator.generateFile()); - if (context.getCustomConfig().wrappedAliases()) { + boolean hasUnknownAliasTypes = ir.getTypes().values().stream() + .anyMatch(typeDeclaration -> typeDeclaration.getShape().isAlias() + && typeDeclaration + .getShape() + .getAlias() + .get() + .getAliasOf() + .isUnknown()); + if (context.getCustomConfig().wrappedAliases() || hasUnknownAliasTypes) { WrappedAliasGenerator wrappedAliasGenerator = new WrappedAliasGenerator(context); this.addGeneratedFile(wrappedAliasGenerator.generateFile()); } diff --git a/generators/java/sdk/versions.yml b/generators/java/sdk/versions.yml index b1e12ac2cca9..4dc2af894a2e 100644 --- a/generators/java/sdk/versions.yml +++ b/generators/java/sdk/versions.yml @@ -1,4 +1,17 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 3.44.2 + changelogEntry: + - summary: | + Generate named Java wrapper classes for unknown type aliases. Previously, + when an API schema had no `type` and no `properties` (representing an + "any" type), the generator dropped the named type definition and inlined + `Object` wherever it was referenced. Unknown type aliases now generate a + proper wrapper class (e.g. `DocumentedUnknownType`) based on + `java.lang.Object`, consistent with how other alias types are handled. + type: fix + createdAt: "2026-03-16" + irVersion: 65 + - version: 3.44.1 changelogEntry: - summary: | diff --git a/generators/php/sdk/src/wire-tests/WireTestGenerator.ts b/generators/php/sdk/src/wire-tests/WireTestGenerator.ts index 48829e9d6ea0..6d179d1f5038 100644 --- a/generators/php/sdk/src/wire-tests/WireTestGenerator.ts +++ b/generators/php/sdk/src/wire-tests/WireTestGenerator.ts @@ -128,6 +128,7 @@ export class WireTestGenerator { parameters: [], body: php.codeblock((writer) => { writer.writeTextStatement("parent::setUp()"); + writer.writeTextStatement("$wiremockUrl = getenv('WIREMOCK_URL') ?: 'http://localhost:8080'"); // Build auth parameters const authParams = this.buildAuthParamsForTest(); @@ -174,7 +175,7 @@ export class WireTestGenerator { name: "Environments" }) ); - writer.write(`::custom(${envValues.map((value) => `'${value}'`).join(", ")}),`); + writer.write(`::custom(${envValues.map(() => "$wiremockUrl").join(", ")}),`); if (authParams.length === 0) { writer.write("\n"); writer.dedent(); @@ -190,7 +191,7 @@ export class WireTestGenerator { } writer.writeLine("options: ["); writer.indent(); - writer.writeLine("'baseUrl' => getenv('WIREMOCK_URL') ?: 'http://localhost:8080',"); + writer.writeLine("'baseUrl' => $wiremockUrl,"); writer.dedent(); writer.write("]"); if (authParams.length === 0) { diff --git a/generators/php/sdk/versions.yml b/generators/php/sdk/versions.yml index 1ad1be0c75e9..f474b2fac2d9 100644 --- a/generators/php/sdk/versions.yml +++ b/generators/php/sdk/versions.yml @@ -1,4 +1,16 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 2.2.1 + changelogEntry: + - summary: | + Fix Wire test files to read `WIREMOCK_URL` environment variable instead of + hardcoding `http://localhost:8080`. The `setUp()` method now declares a + `$wiremockUrl` variable that reads from the environment with a fallback, + enabling tests to work with dynamically assigned Docker ports in local + development and CI/CD environments. + type: fix + createdAt: "2026-03-17" + irVersion: 62 + - version: 2.2.0 changelogEntry: - summary: | diff --git a/generators/typescript/sdk/generator/src/SdkGenerator.ts b/generators/typescript/sdk/generator/src/SdkGenerator.ts index a32c63306c65..67b64018840a 100644 --- a/generators/typescript/sdk/generator/src/SdkGenerator.ts +++ b/generators/typescript/sdk/generator/src/SdkGenerator.ts @@ -2136,6 +2136,16 @@ export class SdkGenerator { namedExports: [clientClassName] }); + if (this.generateWebSocketClients && package_.websocket != null) { + const socketClassName = this.websocketSocketDeclarationReferencer.getExportedName( + packageId.subpackageId + ); + sourceFile.addExportDeclaration({ + moduleSpecifier: "./client/Socket", + namedExports: [socketClassName] + }); + } + sourceFile.addExportDeclaration({ moduleSpecifier: "./client/index" }); diff --git a/generators/typescript/sdk/versions.yml b/generators/typescript/sdk/versions.yml index 2730cfc105f5..cb4854438bf3 100644 --- a/generators/typescript/sdk/versions.yml +++ b/generators/typescript/sdk/versions.yml @@ -1,4 +1,15 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 3.58.1 + changelogEntry: + - summary: | + Export the generated Socket class from subpackage `exports.ts` when + WebSocket clients are enabled. Previously only the Client class was + exported, making the Socket class inaccessible through the public + exports path. + type: fix + createdAt: "2026-03-17" + irVersion: 65 + - version: 3.58.0 changelogEntry: - summary: | diff --git a/packages/cli/ai/baml_src/diff_analyzer.baml b/packages/cli/ai/baml_src/diff_analyzer.baml index 1b4f2ead976a..15ebf416ac7a 100644 --- a/packages/cli/ai/baml_src/diff_analyzer.baml +++ b/packages/cli/ai/baml_src/diff_analyzer.baml @@ -614,6 +614,9 @@ function AnalyzeSdkDiff( - PATCH: leave empty string — patch changes don't warrant changelog entries - Do not use conventional commit prefixes (no "feat:", "fix:", etc.) - Write in third person ("The SDK now supports..." not "Add support for...") + - IMPORTANT: Wrap any type references containing angle brackets in backticks + to prevent MDX parsing issues. For example, write `Optional` not + Optional, and `Map` not Map. Remember again that YOU MUST return a structured JSON response with these four fields: - message: A git commit message formatted like the example previously provided diff --git a/packages/cli/ai/src/baml_client/inlinedbaml.ts b/packages/cli/ai/src/baml_client/inlinedbaml.ts index fd83093d6899..cc20ee811302 100644 --- a/packages/cli/ai/src/baml_client/inlinedbaml.ts +++ b/packages/cli/ai/src/baml_client/inlinedbaml.ts @@ -20,7 +20,7 @@ $ pnpm add @boundaryml/baml const fileMap = { - "diff_analyzer.baml": "// SDK Diff Analyzer\n// Analyzes git diffs of SDK code and produces semantic commit messages and version bumps\n\nenum VersionBump {\n MAJOR\n MINOR\n PATCH\n NO_CHANGE\n}\n\nclass AnalyzeCommitDiffRequest {\n diff string @description(\"The git diff to analyze for generating a commit message\")\n}\n\nclass AnalyzeCommitDiffResponse {\n message string\n @description(\"Developer-facing git commit message. Use conventional commit format. Reference specific code symbols (function names, class names). Keep summary under 72 chars.\")\n\n changelog_entry string\n @description(\"User-facing release note for CHANGELOG.md and GitHub Releases. Describe the impact on SDK consumers, not implementation details. Include migration instructions for MAJOR. Use plain prose, not conventional commit format. Empty string for PATCH.\")\n\n version_bump VersionBump\n @description(\"The recommended semantic version bump: MAJOR for breaking changes, MINOR for new features, PATCH for bug fixes and other changes, NO_CHANGE for empty diffs\")\n\n version_bump_reason string\n @description(\"One sentence explaining WHY this version bump was chosen. For MAJOR: name the specific breaking symbol(s). For MINOR: name the new capability. For PATCH: describe the fix. For NO_CHANGE: 'No functional changes detected.' Example: 'MAJOR because `parserCreateJob` InputStream overloads were removed from `RawLabReportClient`.'\")\n}\n\n// Main function that analyzes SDK diffs\nfunction AnalyzeSdkDiff(\n diff: string @description(\"The git diff to analyze\"),\n language: string @description(\"The SDK programming language, e.g. 'typescript', 'python', 'java'\"),\n previous_version: string @description(\"The current published version before this change, e.g. '1.2.3'\"),\n prior_changelog: string @description(\"The last 3 changelog entries for this SDK, empty string if none. Use this to match the existing commit message style and understand the versioning pattern.\"),\n spec_commit_message: string @description(\"The commit message from the API spec repository that triggered this SDK generation. Use as a hint for the intent of the change, but verify against the actual diff. Empty string if unavailable.\")\n) -> AnalyzeCommitDiffResponse {\n client DefaultClient\n\n prompt #\"\n You are an expert software engineer analyzing changes to generate semantic commit messages.\n\n Analyze the provided git diff and return a structured response with these fields:\n - message: A git commit message formatted like the example below\n - version_bump: One of: MAJOR, MINOR, PATCH, or NO_CHANGE\n\n Version Bump Guidelines:\n - MAJOR: Breaking changes (removed/renamed functions, changed signatures, removed parameters)\n - MINOR: New features that are backward compatible (new functions, new optional parameters).\n Also MINOR: behavioral changes invisible to the public API surface that still affect consumers:\n - Changed HTTP status code handling (e.g. 404 now throws instead of returning null)\n - Changed default parameter values (timeout, retry count, page size, base URL)\n - Changed serialization behavior (date formats, null handling, field ordering)\n - Changed error message text that consumers may depend on\n - Changed HTTP header names or values sent to the server\n - Changed retry or backoff behavior (different retry counts, delay strategies)\n - PATCH: Bug fixes, documentation, internal refactoring with no observable behavioral change\n - NO_CHANGE: The diff is empty\n\n --- Examples ---\n\n Examples of correct classifications:\n\n --- MAJOR: removed exported TypeScript function ---\n diff --git a/src/api/client.ts b/src/api/client.ts\n -export async function getUser(id: string): Promise {\n - return this.request(\"GET\", `/users/${id}`);\n -}\n version_bump: MAJOR\n reason: Existing callers of getUser() will get a compile error.\n\n --- MAJOR: removed Python public method ---\n diff --git a/vital/client.py b/vital/client.py\n - def get_user(self, user_id: str) -> User:\n - return self._request(\"GET\", f\"/users/{user_id}\")\n version_bump: MAJOR\n reason: Existing callers crash with AttributeError.\n\n --- MINOR: new optional TypeScript parameter ---\n diff --git a/src/api/client.ts b/src/api/client.ts\n -async createUser(name: string): Promise\n +async createUser(name: string, role?: UserRole): Promise\n version_bump: MINOR\n reason: Existing callers unaffected — new parameter is optional.\n\n --- MINOR: new Java public method ---\n diff --git a/src/.../UsersClient.java b/src/.../UsersClient.java\n + public CompletableFuture getUserAsync(String userId) {\n + return this.httpClient.sendAsync(...);\n + }\n version_bump: MINOR\n reason: New capability added, nothing removed or changed.\n\n --- MINOR: changed default retry count ---\n diff --git a/src/core/http_client.py b/src/core/http_client.py\n -MAX_RETRIES = 3\n +MAX_RETRIES = 5\n version_bump: MINOR\n reason: Changed default retry count — existing consumers will experience different retry behavior.\n\n --- PATCH: Go import reorganization ---\n diff --git a/client.go b/client.go\n -import \"fmt\"\n -import \"net/http\"\n +import (\n + \"fmt\"\n + \"net/http\"\n +)\n version_bump: PATCH\n reason: Formatting change only, no functional difference.\n\n --- End Examples ---\n\n {% if language == \"typescript\" %}\n Language-specific breaking change rules for TypeScript:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public class, method, function, or exported symbol is MAJOR\n - Making a response field optional is MAJOR (type changes from T to T | undefined, breaking existing property access without null checks)\n - Changing a method return type (e.g. Promise to Promise, or T to T | null) is MAJOR\n - New enum values are MAJOR if the SDK generates exhaustive switch/if-else chains (callers get compile errors on unhandled cases)\n - Changing a union type (adding/removing variants) is MAJOR if callers use exhaustive type narrowing or discriminated unions\n - Adding a new required property to a request/input type is MAJOR (existing callers won't compile)\n - Removing or renaming an exported type, interface, or type alias is MAJOR\n - Changing the type of an existing property (e.g. string to number, or string to string[]) is MAJOR\n - Changing a generic type parameter constraint (e.g. to ) is MAJOR\n - Converting a synchronous method to async (or vice versa) is MAJOR (changes return type to Promise)\n - Removing a default export or switching between default and named exports is MAJOR\n - Changing the structure of a discriminated union (e.g. changing the discriminant field name) is MAJOR\n - Removing or renaming environment/server URL constants is MAJOR\n - Changing the constructor signature of a client class (adding required params) is MAJOR\n - Narrowing a parameter type (e.g. string | number to string) is MAJOR (callers passing number break)\n\n MINOR (backward-compatible additions):\n - Adding a new optional parameter to a function is MINOR\n - Adding new exported types, interfaces, or classes is MINOR\n - Adding new methods to an existing client class is MINOR\n - Adding new optional properties to request types is MINOR\n - Adding new enum values when NOT used in exhaustive checks is MINOR\n - Adding new environment/server URL constants is MINOR\n - Widening a parameter type (e.g. string to string | number) is MINOR\n - Adding new re-exports from index files is MINOR\n - Adding new error types or exception classes is MINOR\n - Adding new RequestOptions fields (e.g. timeout, retries) is MINOR\n - Deprecating (but not removing) a public API is MINOR\n\n PATCH (no API surface change):\n - Changes to internal/private modules (core/, _internal/, utils/) are PATCH\n - Reordering imports, formatting, or comment changes are PATCH\n - Updating SDK version headers (X-Fern-SDK-Version, User-Agent) is PATCH\n - Refactoring HTTP client internals without changing observable defaults or behavior is PATCH\n - Updating dependency versions in package.json is PATCH\n - Adding or modifying JSDoc/TSDoc comments is PATCH\n - Refactoring internal implementation without changing public signatures is PATCH\n - Changes to .npmignore, tsconfig.json, or build configuration are PATCH\n - Updating serialization/deserialization logic that preserves the same public types is PATCH\n\n {% elif language == \"python\" %}\n Language-specific breaking change rules for Python:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public class, method, or function is always MAJOR\n - Adding a new required parameter to a public method is MAJOR (callers get TypeError)\n - Renaming a parameter in a public method is MAJOR if callers use keyword arguments\n - Removing a parameter from a public method signature is MAJOR\n - Changing the type of a parameter from one concrete type to an incompatible one is MAJOR\n - Changing exception types raised by a method (callers catching specific exceptions break) is MAJOR\n - Removing or renaming a public module or subpackage is MAJOR (import statements break)\n - Moving a public class/function to a different module without re-exporting from the original is MAJOR\n - Changing a class from inheriting one base to another when callers use isinstance() checks is MAJOR\n - Removing a public class attribute or property is MAJOR\n\n MINOR (backward-compatible additions):\n - Making a response field optional is usually MINOR (Python uses None propagation; callers rarely type-check strictly)\n - New enum values are MINOR (unknown values are handled gracefully with string fallbacks)\n - Changing a return type from a concrete type to Optional is MINOR (duck typing absorbs this)\n - Adding new public methods, classes, or modules is MINOR\n - Adding new optional parameters (with defaults) is MINOR\n - Adding new optional fields to Pydantic models is MINOR\n - Adding new exception/error classes is MINOR\n - Adding new class attributes or properties is MINOR\n - Adding new type overloads (@overload decorator) is MINOR\n - Adding new environment/server URL constants is MINOR\n - Adding new RequestOptions fields (e.g. timeout, max_retries) is MINOR\n - Deprecating (but not removing) a public API is MINOR\n - Widening a type annotation (e.g. str to Union[str, int]) is MINOR\n\n PATCH (no API surface change):\n - Changes to private methods (prefixed with _) are PATCH\n - Changes to type hints only (no runtime effect) are PATCH\n - Reformatting, docstring updates, or comment changes are PATCH\n - Updating SDK version headers (X-Fern-SDK-Version, User-Agent) are PATCH\n - Refactoring httpx/requests client internals without changing observable defaults or behavior is PATCH\n - Updating dependency versions in pyproject.toml/setup.py is PATCH\n - Updating serialization/deserialization logic that preserves the same public types is PATCH\n - Refactoring internal implementation without changing public signatures is PATCH\n - Changes to __init__.py that don't alter public re-exports are PATCH\n - Changes to conftest.py, test files, or CI configuration are PATCH\n\n {% elif language == \"java\" %}\n Language-specific breaking change rules for Java:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public class, method, or interface is always MAJOR\n - Making a response field optional (e.g. T to Optional) is MAJOR (callers must handle Optional unwrapping)\n - New enum values are MAJOR if the SDK generates exhaustive switch statements\n - Adding a new required parameter to a public method is MAJOR\n - Changing a method's return type is MAJOR (even if compatible at runtime, recompilation fails)\n - Removing or changing a public static final constant is MAJOR\n - Changing a class from concrete to abstract (or vice versa) is MAJOR\n - Changing the checked exceptions declared in a throws clause is MAJOR\n - Removing a public constructor or changing its parameter list is MAJOR\n - Removing an interface that a public class implements is MAJOR\n - Changing generic type parameters on a public class (e.g. Foo to Foo) is MAJOR\n - Moving a public class to a different package without re-exporting is MAJOR\n - Narrowing a parameter type (e.g. Object to String) is MAJOR\n - Making a non-final class final is MAJOR (breaks subclassing)\n - Changing the type of a builder method parameter is MAJOR\n\n MINOR (backward-compatible additions):\n - Adding new overloaded methods is MINOR\n - Adding new public classes or interfaces is MINOR\n - Adding new optional builder methods is MINOR\n - Adding new enum values when NOT used in exhaustive switch statements is MINOR\n - Adding new optional fields to request objects is MINOR\n - Adding new exception/error classes is MINOR\n - Adding new static utility methods is MINOR\n - Adding new environment/server URL constants is MINOR\n - Adding new RequestOptions fields (e.g. timeout, retries) is MINOR\n - Widening a parameter type (e.g. String to Object) is MINOR\n - Adding default methods to interfaces (Java 8+) is MINOR\n - Deprecating (but not removing) public APIs with @Deprecated is MINOR\n\n PATCH (no API surface change):\n - Changes to package-private or private methods are PATCH\n - Changes to annotations (other than public API annotations), Javadoc, or formatting are PATCH\n - Updating SDK version headers (X-Fern-SDK-Version, User-Agent) is PATCH\n - Refactoring OkHttp/HttpClient internals without changing observable defaults or behavior is PATCH\n - Updating dependency versions in build.gradle/pom.xml is PATCH\n - Refactoring internal implementation without changing public signatures is PATCH\n - Updating serialization/deserialization logic (Jackson/Gson config) that preserves public types is PATCH\n - Changes to test files, CI configuration, or build scripts are PATCH\n\n {% elif language == \"go\" %}\n Language-specific breaking change rules for Go:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming an exported function, method, or type is always MAJOR\n - Making a response field a pointer type (e.g. string to *string) is MAJOR (callers must dereference)\n - Changing a function signature (adding/removing parameters or return values) is MAJOR (Go has no overloading)\n - Removing or renaming an exported struct field is MAJOR\n - Changing the type of an exported struct field is MAJOR\n - Removing or renaming an exported constant or variable is MAJOR\n - Changing an interface by adding methods is MAJOR (all implementations must add the method)\n - Removing a method from an interface is MAJOR (callers using that method break)\n - Changing a function's return type(s) is MAJOR (Go is strict about return types)\n - Changing a variadic parameter to non-variadic (or vice versa) is MAJOR\n - Moving a type/function to a different package without aliasing in the original is MAJOR\n - Changing the receiver type of a method (value receiver to pointer receiver changes method set) is MAJOR\n - Changing an exported error variable's type or value is MAJOR (callers using errors.Is break)\n\n MINOR (backward-compatible additions):\n - Adding new exported functions, methods, or types is MINOR\n - New enum-like constants are MINOR (Go enums are not exhaustive by default)\n - Adding new fields to a struct is MINOR (existing code still compiles, zero-value initialization)\n - Making a response field optional (pointer) is usually MINOR if the field was already a struct field\n - Adding new optional function parameters via functional options pattern is MINOR\n - Adding new interface types is MINOR\n - Adding new error types/variables is MINOR\n - Adding new environment/server URL constants is MINOR\n - Adding new RequestOption functions is MINOR\n - Widening a return type from a concrete type to an interface is MINOR (if interface is satisfied)\n - Adding new methods to a concrete type (does not break interface implementations) is MINOR\n\n PATCH (no API surface change):\n - Changes to unexported (lowercase) functions or types are PATCH\n - Changes to go.mod dependencies, import reordering, or formatting are PATCH\n - Updating SDK version headers (X-Fern-SDK-Version, User-Agent) is PATCH\n - Refactoring http.Client internals without changing observable defaults or behavior is PATCH\n - Updating serialization/deserialization logic (JSON tags, encoding) that preserves identical output is PATCH\n - Refactoring internal implementation without changing exported signatures is PATCH\n - Changes to *_test.go files, Makefile, or CI configuration are PATCH\n - Updating comments, godoc, or code formatting (gofmt) is PATCH\n\n {% elif language == \"ruby\" %}\n Language-specific breaking change rules for Ruby:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public method is MAJOR (callers get NoMethodError)\n - Adding a new required positional parameter is MAJOR\n - Removing a method parameter is MAJOR (callers passing that argument get ArgumentError)\n - Changing the order of positional parameters is MAJOR\n - Removing or renaming a public class or module is MAJOR (callers get NameError)\n - Changing a method from instance to class method (or vice versa) is MAJOR\n - Changing the return type in a breaking way (e.g. returning nil where an object was expected and callers chain methods) is MAJOR\n - Removing a public constant is MAJOR\n - Changing exception types raised by a method is MAJOR (callers rescuing specific exceptions break)\n\n MINOR (backward-compatible additions):\n - Making a response field optional is usually MINOR (Ruby uses nil propagation; callers rarely type-check)\n - New enum values are MINOR (unknown values are handled gracefully)\n - Adding new optional keyword parameters (with defaults) is MINOR\n - Adding new public methods or classes is MINOR\n - Adding new optional fields to response/request objects is MINOR\n - Adding new exception/error classes is MINOR\n - Adding new environment/server URL constants is MINOR\n - Adding new RequestOptions fields (e.g. timeout, max_retries) is MINOR\n - Deprecating (but not removing) a public API is MINOR\n - Adding new modules or mixins is MINOR\n\n PATCH (no API surface change):\n - Changes to private methods are PATCH\n - Gemspec metadata, comment, or formatting changes are PATCH\n - Updating SDK version headers is PATCH\n - Refactoring Faraday/Net::HTTP internals without changing observable defaults or behavior is PATCH\n - Updating dependency versions in Gemfile/gemspec is PATCH\n - Refactoring internal implementation without changing public signatures is PATCH\n - Changes to test files, Rakefile, or CI configuration are PATCH\n\n {% elif language == \"csharp\" %}\n Language-specific breaking change rules for C#:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public class, method, property, or interface is always MAJOR\n - Making a response field nullable (e.g. T to T?) is MAJOR (callers must handle null checks)\n - New enum values are MAJOR if the SDK generates exhaustive switch expressions\n - Adding a new required parameter to a public method is MAJOR\n - Changing a method's return type is MAJOR\n - Removing or changing a public constant or static readonly field is MAJOR\n - Changing a class from non-sealed to sealed (or abstract to concrete) is MAJOR\n - Changing the base class of a public class is MAJOR\n - Removing an interface implementation from a public class is MAJOR\n - Changing generic type constraints on a public class or method is MAJOR\n - Moving a public type to a different namespace without type forwarding is MAJOR\n - Changing property from read-write to read-only (removing setter) is MAJOR\n - Changing async method to sync (Task to T) or vice versa is MAJOR\n\n MINOR (backward-compatible additions):\n - Adding new public classes, interfaces, or methods is MINOR\n - Adding new optional parameters (with default values) is MINOR\n - Adding new overloaded methods is MINOR\n - Adding new enum values when NOT used in exhaustive switch expressions is MINOR\n - Adding new optional properties to request objects is MINOR\n - Adding new exception types is MINOR\n - Adding new environment/server URL constants is MINOR\n - Adding new RequestOptions fields (e.g. Timeout, MaxRetries) is MINOR\n - Adding new extension methods is MINOR\n - Deprecating (but not removing) public APIs with [Obsolete] is MINOR\n\n PATCH (no API surface change):\n - Changes to internal or private members are PATCH\n - XML doc comments, formatting, or namespace reorganization are PATCH\n - Updating SDK version headers is PATCH\n - Refactoring HttpClient internals without changing observable defaults or behavior is PATCH\n - Updating dependency versions in .csproj is PATCH\n - Refactoring internal implementation without changing public signatures is PATCH\n - Updating serialization/deserialization (System.Text.Json/Newtonsoft) config that preserves public types is PATCH\n - Changes to test files, .sln, or CI configuration are PATCH\n\n {% elif language == \"php\" %}\n Language-specific breaking change rules for PHP:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public method or class is always MAJOR\n - Changing a method signature (adding required parameters) is MAJOR\n - Removing a method parameter is MAJOR\n - Changing the type declaration of a parameter to an incompatible type is MAJOR\n - Removing or renaming a public constant is MAJOR\n - Changing a class from non-final to final is MAJOR (breaks extension)\n - Removing an interface implementation from a public class is MAJOR\n - Changing the return type declaration to an incompatible type is MAJOR\n - Moving a class to a different namespace without aliasing is MAJOR\n - Changing exception types thrown by a method is MAJOR\n\n MINOR (backward-compatible additions):\n - Making a response field nullable is MINOR in most cases (PHP is dynamically typed)\n - Adding new optional parameters (with defaults) is MINOR\n - Adding new public classes or methods is MINOR\n - New enum cases are usually MINOR (PHP enums are not typically used in exhaustive matches)\n - Adding new optional fields to request/response objects is MINOR\n - Adding new exception classes is MINOR\n - Adding new environment/server URL constants is MINOR\n - Adding new traits or interfaces is MINOR\n - Adding new RequestOptions fields is MINOR\n - Deprecating (but not removing) public APIs is MINOR\n\n PATCH (no API surface change):\n - Changes to private/protected methods are PATCH\n - PHPDoc, formatting, or composer.json metadata changes are PATCH\n - Updating SDK version headers is PATCH\n - Refactoring Guzzle/cURL internals without changing observable defaults or behavior is PATCH\n - Updating dependency versions in composer.json is PATCH\n - Refactoring internal implementation without changing public signatures is PATCH\n - Changes to test files, phpunit.xml, or CI configuration are PATCH\n\n {% elif language == \"swift\" %}\n Language-specific breaking change rules for Swift:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public method, property, or type is always MAJOR\n - Making a response field optional (T to T?) is MAJOR (callers must unwrap with if-let/guard)\n - New enum cases are MAJOR (Swift switch statements must be exhaustive unless using default)\n - Adding a new required parameter to a public method is MAJOR\n - Changing the type of a public property is MAJOR\n - Removing or changing a public protocol requirement is MAJOR\n - Removing protocol conformance from a public type is MAJOR\n - Changing a struct to a class (or vice versa) is MAJOR (value vs reference semantics)\n - Making a public initializer failable (init to init?) or vice versa is MAJOR\n - Changing the associated values of an enum case is MAJOR\n - Removing a public typealias is MAJOR\n - Changing access level from public to internal/private is MAJOR\n\n MINOR (backward-compatible additions):\n - Adding new public types, methods, or properties is MINOR\n - Adding new optional parameters (with default values) is MINOR\n - Adding new enum cases when callers use default in switch is MINOR\n - Adding new protocol extensions with default implementations is MINOR\n - Adding new optional fields to request/response structs is MINOR\n - Adding new error types is MINOR\n - Adding new environment/server URL constants is MINOR\n - Adding new RequestOptions fields is MINOR\n - Adding new convenience initializers is MINOR\n - Deprecating (but not removing) public APIs with @available(*, deprecated) is MINOR\n\n PATCH (no API surface change):\n - Changes to internal or private members are PATCH\n - Formatting, comments, or documentation changes are PATCH\n - Updating SDK version headers is PATCH\n - Refactoring URLSession internals without changing observable defaults or behavior is PATCH\n - Updating dependency versions in Package.swift is PATCH\n - Refactoring internal implementation without changing public signatures is PATCH\n - Changes to test files, xcconfig, or CI configuration are PATCH\n\n {% elif language == \"rust\" %}\n Language-specific breaking change rules for Rust:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public function, struct, enum, or trait is always MAJOR\n - Making a response field optional (T to Option) is MAJOR (callers must handle the Option)\n - New enum variants are MAJOR (Rust match statements must be exhaustive unless using _ wildcard)\n - Adding a new required field to a public struct is MAJOR (unless #[non_exhaustive])\n - Removing a public trait implementation is MAJOR\n - Changing a function's return type is MAJOR\n - Adding a required method to a public trait is MAJOR (all implementations must add it)\n - Changing the type of a public struct field is MAJOR\n - Removing or renaming a public module is MAJOR\n - Making a public type private (pub to pub(crate) or removing pub) is MAJOR\n - Changing a struct from non-exhaustive to exhaustive construction (removing .. Default::default()) is MAJOR\n - Changing generic type parameters or their bounds on public types is MAJOR\n - Changing from Result to Result where E2 is a different error type is MAJOR\n - Removing a public constant or static is MAJOR\n\n MINOR (backward-compatible additions):\n - Adding new public functions, structs, or enums is MINOR\n - Adding new optional fields to #[non_exhaustive] structs is MINOR\n - Adding new enum variants to #[non_exhaustive] enums is MINOR\n - Adding new trait implementations for existing types is MINOR\n - Adding new public constants or statics is MINOR\n - Adding new methods to existing impl blocks is MINOR\n - Adding new error types is MINOR\n - Adding new environment/server URL constants is MINOR\n - Adding new RequestOptions fields is MINOR\n - Adding new optional builder methods is MINOR\n - Deprecating (but not removing) public APIs with #[deprecated] is MINOR\n\n PATCH (no API surface change):\n - Changes to pub(crate) or private items are PATCH\n - Cargo.toml metadata, formatting, or comment changes are PATCH\n - Updating SDK version headers is PATCH\n - Refactoring reqwest/hyper internals without changing observable defaults or behavior is PATCH\n - Updating dependency versions in Cargo.toml is PATCH\n - Refactoring internal implementation without changing public signatures is PATCH\n - Updating serialization/deserialization (serde) config that preserves public types is PATCH\n - Changes to test files, build.rs, or CI configuration are PATCH\n\n {% elif language == \"kotlin\" %}\n Language-specific breaking change rules for Kotlin:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public function, class, or property is always MAJOR\n - Making a response field nullable (T to T?) is MAJOR (callers must handle null safety operators)\n - New enum values are MAJOR if used in when() expressions without else branch\n - Adding a new required parameter to a public function is MAJOR\n - Changing a method's return type is MAJOR\n - Removing or changing a public constant (const val / companion object val) is MAJOR\n - Changing a class from open to final (or data class to regular class) is MAJOR\n - Removing an interface implementation from a public class is MAJOR\n - Changing generic type parameters or their variance (in/out) on public types is MAJOR\n - Moving a public class to a different package without type aliasing is MAJOR\n - Changing a property from var to val (or removing a setter) is MAJOR\n - Changing a suspend function to non-suspend (or vice versa) is MAJOR\n - Changing sealed class/interface hierarchy (removing subclasses) is MAJOR\n\n MINOR (backward-compatible additions):\n - Adding new public classes, functions, or extension functions is MINOR\n - Adding new optional parameters (with default values) is MINOR\n - Adding new enum values when callers use else in when() is MINOR\n - Adding new optional properties to data classes is MINOR\n - Adding new exception types is MINOR\n - Adding new environment/server URL constants is MINOR\n - Adding new RequestOptions fields is MINOR\n - Adding new sealed class/interface subtypes is MINOR (if callers have else branch)\n - Deprecating (but not removing) public APIs with @Deprecated is MINOR\n - Adding new companion object functions is MINOR\n\n PATCH (no API surface change):\n - Changes to internal or private members are PATCH\n - KDoc, formatting, or build.gradle changes are PATCH\n - Updating SDK version headers is PATCH\n - Refactoring OkHttp internals without changing observable defaults or behavior is PATCH\n - Updating dependency versions in build.gradle.kts is PATCH\n - Refactoring internal implementation without changing public signatures is PATCH\n - Updating serialization/deserialization (kotlinx.serialization/Moshi) config that preserves public types is PATCH\n - Changes to test files or CI configuration are PATCH\n\n {% else %}\n Language-specific breaking change rules (language: {{ language }}):\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public class, method, or function is always MAJOR\n - Making a response field optional is MAJOR in statically-typed languages (TypeScript, Java, C#, Swift, Rust, Kotlin, Go), usually MINOR in dynamically-typed ones (Python, Ruby, PHP)\n - New enum values are MAJOR if the language enforces exhaustive matching (TypeScript, Java, C#, Swift, Rust), MINOR otherwise\n - Adding a new required parameter to a public method is MAJOR\n - Changing a method's return type is MAJOR\n - Changing the type of an existing field/property is MAJOR\n - Removing or changing public constants is MAJOR\n\n MINOR (backward-compatible additions):\n - Adding new public APIs (classes, methods, functions) is MINOR\n - Adding new optional parameters is MINOR\n - Adding new optional fields to request/response objects is MINOR\n - Adding new error/exception types is MINOR\n - Deprecating (but not removing) public APIs is MINOR\n\n PATCH (no API surface change):\n - Internal/private changes are PATCH\n - Formatting, documentation, or comment changes are PATCH\n - Dependency version updates are PATCH\n - SDK version header updates are PATCH\n - Refactoring retry/timeout internals without changing observable defaults or behavior is PATCH\n - Refactoring internals without changing public signatures is PATCH\n {% endif %}\n\n Apply these patterns to the diff below. When in doubt between MINOR and PATCH,\n prefer MINOR. When in doubt between MAJOR and MINOR, examine whether existing\n callers would break without any code changes on their side.\n\n Message Format (use this exact structure):\n ```\n : \n\n \n\n Key changes:\n - \n - \n - \n ```\n\n Message Guidelines:\n - Use conventional commit types: feat, fix, refactor, docs, chore, test, style, perf\n - Keep the summary line under 72 characters\n - Write in present tense imperative mood (\"add\" not \"added\" or \"adds\")\n - For breaking changes: include migration instructions in the detailed description\n - For new features: highlight new capabilities in the key changes\n - For PATCH: describe the fix or improvement\n - For NO_CHANGE: use type \"chore\" and state that no functional changes were made\n - Be specific and action-oriented\n - Do not use \"Fern regeneration\" in commit messages - use \"SDK regeneration\" instead\n - NEVER include the literal version \"505.503.4455\" in the commit message - if you see this placeholder\n in the diff, describe changes generically (e.g., \"added X-Fern-SDK-Version header\")\n - The previous version is provided for context only. Do not include it\n literally in the commit message summary line.\n\n {% if prior_changelog %}\n Prior changelog entries (for style reference):\n ---\n {{prior_changelog}}\n ---\n Match the tone and format of these entries in your commit message.\n {% endif %}\n\n {% if spec_commit_message %}\n The API spec change that triggered this SDK generation had the following commit message:\n \"{{spec_commit_message}}\"\n Use this as a hint for understanding the intent of the change, but always verify\n against the actual diff below. The commit message may be vague or inaccurate.\n {% endif %}\n\n Previous version: {{previous_version}}\n SDK language: {{language}}\n\n Git Diff:\n ---\n {{diff}}\n ---\n\n Changelog Entry Guidelines:\n - Write for SDK consumers, not engineers reading the source code\n - MAJOR: explain what broke and how to migrate (\"The `getUser` method has been\n removed. Replace calls with `fetchUser(id)` which returns the same type.\")\n - MINOR: describe the new capability (\"New `createPayment()` method available\n on `PaymentsClient`.\")\n - PATCH: leave empty string — patch changes don't warrant changelog entries\n - Do not use conventional commit prefixes (no \"feat:\", \"fix:\", etc.)\n - Write in third person (\"The SDK now supports...\" not \"Add support for...\")\n\n Remember again that YOU MUST return a structured JSON response with these four fields:\n - message: A git commit message formatted like the example previously provided\n - changelog_entry: A user-facing release note (empty string for PATCH)\n - version_bump: One of: MAJOR, MINOR, PATCH, or NO_CHANGE\n - version_bump_reason: One sentence explaining WHY this bump level was chosen.\n For MAJOR, name the specific breaking symbol(s) and explain why existing callers break.\n For MINOR, name the new capability added.\n For PATCH, describe what was fixed or improved.\n For NO_CHANGE, say \"No functional changes detected.\"\n Example: \"MAJOR because `parserCreateJob` InputStream overloads were removed from `RawLabReportClient`, breaking existing callers.\"\n \"#\n}\n\nclass ConsolidateChangelogResponse {\n consolidated_changelog string\n @description(\"CHANGELOG.md entry in Keep a Changelog format. Group under ### Breaking Changes, ### Added, ### Changed, ### Fixed. Bold symbol names, one tight sentence per bullet. Prose only, no code fences. Append migration action inline for breaking changes.\")\n\n pr_description string\n @description(\"PR description with ## Breaking Changes section (if any) containing ### per breaking change with Before/After code fences and Migration line, then ## What's New section summarizing features in prose paragraphs grouped by theme. Do NOT list every class individually — summarize repetitive changes as a single entry.\")\n\n version_bump_reason string\n @description(\"One sentence explaining WHY the overall version bump was chosen. For MAJOR: name the specific breaking symbol(s). For MINOR: name the new capability. For PATCH: describe the fix. Example: 'MAJOR because `parserCreateJob` InputStream overloads were removed from `RawLabReportClient`.'\")\n}\n\nfunction ConsolidateChangelog(\n raw_entries: string @description(\"Newline-separated raw changelog entries from chunked diff analysis\"),\n version_bump: string @description(\"The overall version bump: MAJOR, MINOR, or PATCH\"),\n language: string @description(\"The SDK programming language, e.g. 'typescript', 'python', 'java'\")\n) -> ConsolidateChangelogResponse {\n client DefaultClient\n\n prompt #\"\n You are a technical writer formatting release notes for a {{language}} SDK.\n\n The raw change notes below are noisy and repetitive — many bullets describe the same\n change across different packages. Deduplicate aggressively: if the same feature appears\n multiple times, merge into one entry.\n\n Raw changelog entries:\n ---\n {{raw_entries}}\n ---\n\n Overall version bump: {{version_bump}}\n\n Produce three outputs:\n\n ---\n\n ## 1. CHANGELOG.md entry (Keep a Changelog format)\n\n - Group under: `### Breaking Changes`, `### Added`, `### Changed`, `### Fixed`\n - Only include sections with entries\n - **Bold the symbol name** first, then one tight sentence for SDK consumers\n - No code fences — prose only\n - For breaking changes, append the migration action inline\n\n ## 2. PR Description\n\n - `## Breaking Changes` section at top (if any)\n - One `###` per breaking change with **Before/After** code fences and a **Migration:** line\n - `## What's New` section summarizing added/changed features in prose paragraphs,\n grouped by theme (e.g. logging, streaming, pagination, builder improvements)\n - Do NOT list every class that got the same method — summarize as a single entry\n\n ## 3. Version Bump Reason\n\n - One sentence explaining WHY the overall version bump ({{version_bump}}) was chosen\n - For MAJOR: name the specific breaking symbol(s) and explain why existing callers break\n - For MINOR: name the new capability added\n - For PATCH: describe what was fixed or improved\n - Example: \"MAJOR because `parserCreateJob` InputStream overloads were removed from `RawLabReportClient`, breaking existing callers.\"\n\n ---\n\n {{ ctx.output_format }}\n \"#\n}\n", + "diff_analyzer.baml": "// SDK Diff Analyzer\n// Analyzes git diffs of SDK code and produces semantic commit messages and version bumps\n\nenum VersionBump {\n MAJOR\n MINOR\n PATCH\n NO_CHANGE\n}\n\nclass AnalyzeCommitDiffRequest {\n diff string @description(\"The git diff to analyze for generating a commit message\")\n}\n\nclass AnalyzeCommitDiffResponse {\n message string\n @description(\"Developer-facing git commit message. Use conventional commit format. Reference specific code symbols (function names, class names). Keep summary under 72 chars.\")\n\n changelog_entry string\n @description(\"User-facing release note for CHANGELOG.md and GitHub Releases. Describe the impact on SDK consumers, not implementation details. Include migration instructions for MAJOR. Use plain prose, not conventional commit format. Empty string for PATCH.\")\n\n version_bump VersionBump\n @description(\"The recommended semantic version bump: MAJOR for breaking changes, MINOR for new features, PATCH for bug fixes and other changes, NO_CHANGE for empty diffs\")\n\n version_bump_reason string\n @description(\"One sentence explaining WHY this version bump was chosen. For MAJOR: name the specific breaking symbol(s). For MINOR: name the new capability. For PATCH: describe the fix. For NO_CHANGE: 'No functional changes detected.' Example: 'MAJOR because `parserCreateJob` InputStream overloads were removed from `RawLabReportClient`.'\")\n}\n\n// Main function that analyzes SDK diffs\nfunction AnalyzeSdkDiff(\n diff: string @description(\"The git diff to analyze\"),\n language: string @description(\"The SDK programming language, e.g. 'typescript', 'python', 'java'\"),\n previous_version: string @description(\"The current published version before this change, e.g. '1.2.3'\"),\n prior_changelog: string @description(\"The last 3 changelog entries for this SDK, empty string if none. Use this to match the existing commit message style and understand the versioning pattern.\"),\n spec_commit_message: string @description(\"The commit message from the API spec repository that triggered this SDK generation. Use as a hint for the intent of the change, but verify against the actual diff. Empty string if unavailable.\")\n) -> AnalyzeCommitDiffResponse {\n client DefaultClient\n\n prompt #\"\n You are an expert software engineer analyzing changes to generate semantic commit messages.\n\n Analyze the provided git diff and return a structured response with these fields:\n - message: A git commit message formatted like the example below\n - version_bump: One of: MAJOR, MINOR, PATCH, or NO_CHANGE\n\n Version Bump Guidelines:\n - MAJOR: Breaking changes (removed/renamed functions, changed signatures, removed parameters)\n - MINOR: New features that are backward compatible (new functions, new optional parameters).\n Also MINOR: behavioral changes invisible to the public API surface that still affect consumers:\n - Changed HTTP status code handling (e.g. 404 now throws instead of returning null)\n - Changed default parameter values (timeout, retry count, page size, base URL)\n - Changed serialization behavior (date formats, null handling, field ordering)\n - Changed error message text that consumers may depend on\n - Changed HTTP header names or values sent to the server\n - Changed retry or backoff behavior (different retry counts, delay strategies)\n - PATCH: Bug fixes, documentation, internal refactoring with no observable behavioral change\n - NO_CHANGE: The diff is empty\n\n --- Examples ---\n\n Examples of correct classifications:\n\n --- MAJOR: removed exported TypeScript function ---\n diff --git a/src/api/client.ts b/src/api/client.ts\n -export async function getUser(id: string): Promise {\n - return this.request(\"GET\", `/users/${id}`);\n -}\n version_bump: MAJOR\n reason: Existing callers of getUser() will get a compile error.\n\n --- MAJOR: removed Python public method ---\n diff --git a/vital/client.py b/vital/client.py\n - def get_user(self, user_id: str) -> User:\n - return self._request(\"GET\", f\"/users/{user_id}\")\n version_bump: MAJOR\n reason: Existing callers crash with AttributeError.\n\n --- MINOR: new optional TypeScript parameter ---\n diff --git a/src/api/client.ts b/src/api/client.ts\n -async createUser(name: string): Promise\n +async createUser(name: string, role?: UserRole): Promise\n version_bump: MINOR\n reason: Existing callers unaffected — new parameter is optional.\n\n --- MINOR: new Java public method ---\n diff --git a/src/.../UsersClient.java b/src/.../UsersClient.java\n + public CompletableFuture getUserAsync(String userId) {\n + return this.httpClient.sendAsync(...);\n + }\n version_bump: MINOR\n reason: New capability added, nothing removed or changed.\n\n --- MINOR: changed default retry count ---\n diff --git a/src/core/http_client.py b/src/core/http_client.py\n -MAX_RETRIES = 3\n +MAX_RETRIES = 5\n version_bump: MINOR\n reason: Changed default retry count — existing consumers will experience different retry behavior.\n\n --- PATCH: Go import reorganization ---\n diff --git a/client.go b/client.go\n -import \"fmt\"\n -import \"net/http\"\n +import (\n + \"fmt\"\n + \"net/http\"\n +)\n version_bump: PATCH\n reason: Formatting change only, no functional difference.\n\n --- End Examples ---\n\n {% if language == \"typescript\" %}\n Language-specific breaking change rules for TypeScript:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public class, method, function, or exported symbol is MAJOR\n - Making a response field optional is MAJOR (type changes from T to T | undefined, breaking existing property access without null checks)\n - Changing a method return type (e.g. Promise to Promise, or T to T | null) is MAJOR\n - New enum values are MAJOR if the SDK generates exhaustive switch/if-else chains (callers get compile errors on unhandled cases)\n - Changing a union type (adding/removing variants) is MAJOR if callers use exhaustive type narrowing or discriminated unions\n - Adding a new required property to a request/input type is MAJOR (existing callers won't compile)\n - Removing or renaming an exported type, interface, or type alias is MAJOR\n - Changing the type of an existing property (e.g. string to number, or string to string[]) is MAJOR\n - Changing a generic type parameter constraint (e.g. to ) is MAJOR\n - Converting a synchronous method to async (or vice versa) is MAJOR (changes return type to Promise)\n - Removing a default export or switching between default and named exports is MAJOR\n - Changing the structure of a discriminated union (e.g. changing the discriminant field name) is MAJOR\n - Removing or renaming environment/server URL constants is MAJOR\n - Changing the constructor signature of a client class (adding required params) is MAJOR\n - Narrowing a parameter type (e.g. string | number to string) is MAJOR (callers passing number break)\n\n MINOR (backward-compatible additions):\n - Adding a new optional parameter to a function is MINOR\n - Adding new exported types, interfaces, or classes is MINOR\n - Adding new methods to an existing client class is MINOR\n - Adding new optional properties to request types is MINOR\n - Adding new enum values when NOT used in exhaustive checks is MINOR\n - Adding new environment/server URL constants is MINOR\n - Widening a parameter type (e.g. string to string | number) is MINOR\n - Adding new re-exports from index files is MINOR\n - Adding new error types or exception classes is MINOR\n - Adding new RequestOptions fields (e.g. timeout, retries) is MINOR\n - Deprecating (but not removing) a public API is MINOR\n\n PATCH (no API surface change):\n - Changes to internal/private modules (core/, _internal/, utils/) are PATCH\n - Reordering imports, formatting, or comment changes are PATCH\n - Updating SDK version headers (X-Fern-SDK-Version, User-Agent) is PATCH\n - Refactoring HTTP client internals without changing observable defaults or behavior is PATCH\n - Updating dependency versions in package.json is PATCH\n - Adding or modifying JSDoc/TSDoc comments is PATCH\n - Refactoring internal implementation without changing public signatures is PATCH\n - Changes to .npmignore, tsconfig.json, or build configuration are PATCH\n - Updating serialization/deserialization logic that preserves the same public types is PATCH\n\n {% elif language == \"python\" %}\n Language-specific breaking change rules for Python:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public class, method, or function is always MAJOR\n - Adding a new required parameter to a public method is MAJOR (callers get TypeError)\n - Renaming a parameter in a public method is MAJOR if callers use keyword arguments\n - Removing a parameter from a public method signature is MAJOR\n - Changing the type of a parameter from one concrete type to an incompatible one is MAJOR\n - Changing exception types raised by a method (callers catching specific exceptions break) is MAJOR\n - Removing or renaming a public module or subpackage is MAJOR (import statements break)\n - Moving a public class/function to a different module without re-exporting from the original is MAJOR\n - Changing a class from inheriting one base to another when callers use isinstance() checks is MAJOR\n - Removing a public class attribute or property is MAJOR\n\n MINOR (backward-compatible additions):\n - Making a response field optional is usually MINOR (Python uses None propagation; callers rarely type-check strictly)\n - New enum values are MINOR (unknown values are handled gracefully with string fallbacks)\n - Changing a return type from a concrete type to Optional is MINOR (duck typing absorbs this)\n - Adding new public methods, classes, or modules is MINOR\n - Adding new optional parameters (with defaults) is MINOR\n - Adding new optional fields to Pydantic models is MINOR\n - Adding new exception/error classes is MINOR\n - Adding new class attributes or properties is MINOR\n - Adding new type overloads (@overload decorator) is MINOR\n - Adding new environment/server URL constants is MINOR\n - Adding new RequestOptions fields (e.g. timeout, max_retries) is MINOR\n - Deprecating (but not removing) a public API is MINOR\n - Widening a type annotation (e.g. str to Union[str, int]) is MINOR\n\n PATCH (no API surface change):\n - Changes to private methods (prefixed with _) are PATCH\n - Changes to type hints only (no runtime effect) are PATCH\n - Reformatting, docstring updates, or comment changes are PATCH\n - Updating SDK version headers (X-Fern-SDK-Version, User-Agent) are PATCH\n - Refactoring httpx/requests client internals without changing observable defaults or behavior is PATCH\n - Updating dependency versions in pyproject.toml/setup.py is PATCH\n - Updating serialization/deserialization logic that preserves the same public types is PATCH\n - Refactoring internal implementation without changing public signatures is PATCH\n - Changes to __init__.py that don't alter public re-exports are PATCH\n - Changes to conftest.py, test files, or CI configuration are PATCH\n\n {% elif language == \"java\" %}\n Language-specific breaking change rules for Java:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public class, method, or interface is always MAJOR\n - Making a response field optional (e.g. T to Optional) is MAJOR (callers must handle Optional unwrapping)\n - New enum values are MAJOR if the SDK generates exhaustive switch statements\n - Adding a new required parameter to a public method is MAJOR\n - Changing a method's return type is MAJOR (even if compatible at runtime, recompilation fails)\n - Removing or changing a public static final constant is MAJOR\n - Changing a class from concrete to abstract (or vice versa) is MAJOR\n - Changing the checked exceptions declared in a throws clause is MAJOR\n - Removing a public constructor or changing its parameter list is MAJOR\n - Removing an interface that a public class implements is MAJOR\n - Changing generic type parameters on a public class (e.g. Foo to Foo) is MAJOR\n - Moving a public class to a different package without re-exporting is MAJOR\n - Narrowing a parameter type (e.g. Object to String) is MAJOR\n - Making a non-final class final is MAJOR (breaks subclassing)\n - Changing the type of a builder method parameter is MAJOR\n\n MINOR (backward-compatible additions):\n - Adding new overloaded methods is MINOR\n - Adding new public classes or interfaces is MINOR\n - Adding new optional builder methods is MINOR\n - Adding new enum values when NOT used in exhaustive switch statements is MINOR\n - Adding new optional fields to request objects is MINOR\n - Adding new exception/error classes is MINOR\n - Adding new static utility methods is MINOR\n - Adding new environment/server URL constants is MINOR\n - Adding new RequestOptions fields (e.g. timeout, retries) is MINOR\n - Widening a parameter type (e.g. String to Object) is MINOR\n - Adding default methods to interfaces (Java 8+) is MINOR\n - Deprecating (but not removing) public APIs with @Deprecated is MINOR\n\n PATCH (no API surface change):\n - Changes to package-private or private methods are PATCH\n - Changes to annotations (other than public API annotations), Javadoc, or formatting are PATCH\n - Updating SDK version headers (X-Fern-SDK-Version, User-Agent) is PATCH\n - Refactoring OkHttp/HttpClient internals without changing observable defaults or behavior is PATCH\n - Updating dependency versions in build.gradle/pom.xml is PATCH\n - Refactoring internal implementation without changing public signatures is PATCH\n - Updating serialization/deserialization logic (Jackson/Gson config) that preserves public types is PATCH\n - Changes to test files, CI configuration, or build scripts are PATCH\n\n {% elif language == \"go\" %}\n Language-specific breaking change rules for Go:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming an exported function, method, or type is always MAJOR\n - Making a response field a pointer type (e.g. string to *string) is MAJOR (callers must dereference)\n - Changing a function signature (adding/removing parameters or return values) is MAJOR (Go has no overloading)\n - Removing or renaming an exported struct field is MAJOR\n - Changing the type of an exported struct field is MAJOR\n - Removing or renaming an exported constant or variable is MAJOR\n - Changing an interface by adding methods is MAJOR (all implementations must add the method)\n - Removing a method from an interface is MAJOR (callers using that method break)\n - Changing a function's return type(s) is MAJOR (Go is strict about return types)\n - Changing a variadic parameter to non-variadic (or vice versa) is MAJOR\n - Moving a type/function to a different package without aliasing in the original is MAJOR\n - Changing the receiver type of a method (value receiver to pointer receiver changes method set) is MAJOR\n - Changing an exported error variable's type or value is MAJOR (callers using errors.Is break)\n\n MINOR (backward-compatible additions):\n - Adding new exported functions, methods, or types is MINOR\n - New enum-like constants are MINOR (Go enums are not exhaustive by default)\n - Adding new fields to a struct is MINOR (existing code still compiles, zero-value initialization)\n - Making a response field optional (pointer) is usually MINOR if the field was already a struct field\n - Adding new optional function parameters via functional options pattern is MINOR\n - Adding new interface types is MINOR\n - Adding new error types/variables is MINOR\n - Adding new environment/server URL constants is MINOR\n - Adding new RequestOption functions is MINOR\n - Widening a return type from a concrete type to an interface is MINOR (if interface is satisfied)\n - Adding new methods to a concrete type (does not break interface implementations) is MINOR\n\n PATCH (no API surface change):\n - Changes to unexported (lowercase) functions or types are PATCH\n - Changes to go.mod dependencies, import reordering, or formatting are PATCH\n - Updating SDK version headers (X-Fern-SDK-Version, User-Agent) is PATCH\n - Refactoring http.Client internals without changing observable defaults or behavior is PATCH\n - Updating serialization/deserialization logic (JSON tags, encoding) that preserves identical output is PATCH\n - Refactoring internal implementation without changing exported signatures is PATCH\n - Changes to *_test.go files, Makefile, or CI configuration are PATCH\n - Updating comments, godoc, or code formatting (gofmt) is PATCH\n\n {% elif language == \"ruby\" %}\n Language-specific breaking change rules for Ruby:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public method is MAJOR (callers get NoMethodError)\n - Adding a new required positional parameter is MAJOR\n - Removing a method parameter is MAJOR (callers passing that argument get ArgumentError)\n - Changing the order of positional parameters is MAJOR\n - Removing or renaming a public class or module is MAJOR (callers get NameError)\n - Changing a method from instance to class method (or vice versa) is MAJOR\n - Changing the return type in a breaking way (e.g. returning nil where an object was expected and callers chain methods) is MAJOR\n - Removing a public constant is MAJOR\n - Changing exception types raised by a method is MAJOR (callers rescuing specific exceptions break)\n\n MINOR (backward-compatible additions):\n - Making a response field optional is usually MINOR (Ruby uses nil propagation; callers rarely type-check)\n - New enum values are MINOR (unknown values are handled gracefully)\n - Adding new optional keyword parameters (with defaults) is MINOR\n - Adding new public methods or classes is MINOR\n - Adding new optional fields to response/request objects is MINOR\n - Adding new exception/error classes is MINOR\n - Adding new environment/server URL constants is MINOR\n - Adding new RequestOptions fields (e.g. timeout, max_retries) is MINOR\n - Deprecating (but not removing) a public API is MINOR\n - Adding new modules or mixins is MINOR\n\n PATCH (no API surface change):\n - Changes to private methods are PATCH\n - Gemspec metadata, comment, or formatting changes are PATCH\n - Updating SDK version headers is PATCH\n - Refactoring Faraday/Net::HTTP internals without changing observable defaults or behavior is PATCH\n - Updating dependency versions in Gemfile/gemspec is PATCH\n - Refactoring internal implementation without changing public signatures is PATCH\n - Changes to test files, Rakefile, or CI configuration are PATCH\n\n {% elif language == \"csharp\" %}\n Language-specific breaking change rules for C#:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public class, method, property, or interface is always MAJOR\n - Making a response field nullable (e.g. T to T?) is MAJOR (callers must handle null checks)\n - New enum values are MAJOR if the SDK generates exhaustive switch expressions\n - Adding a new required parameter to a public method is MAJOR\n - Changing a method's return type is MAJOR\n - Removing or changing a public constant or static readonly field is MAJOR\n - Changing a class from non-sealed to sealed (or abstract to concrete) is MAJOR\n - Changing the base class of a public class is MAJOR\n - Removing an interface implementation from a public class is MAJOR\n - Changing generic type constraints on a public class or method is MAJOR\n - Moving a public type to a different namespace without type forwarding is MAJOR\n - Changing property from read-write to read-only (removing setter) is MAJOR\n - Changing async method to sync (Task to T) or vice versa is MAJOR\n\n MINOR (backward-compatible additions):\n - Adding new public classes, interfaces, or methods is MINOR\n - Adding new optional parameters (with default values) is MINOR\n - Adding new overloaded methods is MINOR\n - Adding new enum values when NOT used in exhaustive switch expressions is MINOR\n - Adding new optional properties to request objects is MINOR\n - Adding new exception types is MINOR\n - Adding new environment/server URL constants is MINOR\n - Adding new RequestOptions fields (e.g. Timeout, MaxRetries) is MINOR\n - Adding new extension methods is MINOR\n - Deprecating (but not removing) public APIs with [Obsolete] is MINOR\n\n PATCH (no API surface change):\n - Changes to internal or private members are PATCH\n - XML doc comments, formatting, or namespace reorganization are PATCH\n - Updating SDK version headers is PATCH\n - Refactoring HttpClient internals without changing observable defaults or behavior is PATCH\n - Updating dependency versions in .csproj is PATCH\n - Refactoring internal implementation without changing public signatures is PATCH\n - Updating serialization/deserialization (System.Text.Json/Newtonsoft) config that preserves public types is PATCH\n - Changes to test files, .sln, or CI configuration are PATCH\n\n {% elif language == \"php\" %}\n Language-specific breaking change rules for PHP:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public method or class is always MAJOR\n - Changing a method signature (adding required parameters) is MAJOR\n - Removing a method parameter is MAJOR\n - Changing the type declaration of a parameter to an incompatible type is MAJOR\n - Removing or renaming a public constant is MAJOR\n - Changing a class from non-final to final is MAJOR (breaks extension)\n - Removing an interface implementation from a public class is MAJOR\n - Changing the return type declaration to an incompatible type is MAJOR\n - Moving a class to a different namespace without aliasing is MAJOR\n - Changing exception types thrown by a method is MAJOR\n\n MINOR (backward-compatible additions):\n - Making a response field nullable is MINOR in most cases (PHP is dynamically typed)\n - Adding new optional parameters (with defaults) is MINOR\n - Adding new public classes or methods is MINOR\n - New enum cases are usually MINOR (PHP enums are not typically used in exhaustive matches)\n - Adding new optional fields to request/response objects is MINOR\n - Adding new exception classes is MINOR\n - Adding new environment/server URL constants is MINOR\n - Adding new traits or interfaces is MINOR\n - Adding new RequestOptions fields is MINOR\n - Deprecating (but not removing) public APIs is MINOR\n\n PATCH (no API surface change):\n - Changes to private/protected methods are PATCH\n - PHPDoc, formatting, or composer.json metadata changes are PATCH\n - Updating SDK version headers is PATCH\n - Refactoring Guzzle/cURL internals without changing observable defaults or behavior is PATCH\n - Updating dependency versions in composer.json is PATCH\n - Refactoring internal implementation without changing public signatures is PATCH\n - Changes to test files, phpunit.xml, or CI configuration are PATCH\n\n {% elif language == \"swift\" %}\n Language-specific breaking change rules for Swift:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public method, property, or type is always MAJOR\n - Making a response field optional (T to T?) is MAJOR (callers must unwrap with if-let/guard)\n - New enum cases are MAJOR (Swift switch statements must be exhaustive unless using default)\n - Adding a new required parameter to a public method is MAJOR\n - Changing the type of a public property is MAJOR\n - Removing or changing a public protocol requirement is MAJOR\n - Removing protocol conformance from a public type is MAJOR\n - Changing a struct to a class (or vice versa) is MAJOR (value vs reference semantics)\n - Making a public initializer failable (init to init?) or vice versa is MAJOR\n - Changing the associated values of an enum case is MAJOR\n - Removing a public typealias is MAJOR\n - Changing access level from public to internal/private is MAJOR\n\n MINOR (backward-compatible additions):\n - Adding new public types, methods, or properties is MINOR\n - Adding new optional parameters (with default values) is MINOR\n - Adding new enum cases when callers use default in switch is MINOR\n - Adding new protocol extensions with default implementations is MINOR\n - Adding new optional fields to request/response structs is MINOR\n - Adding new error types is MINOR\n - Adding new environment/server URL constants is MINOR\n - Adding new RequestOptions fields is MINOR\n - Adding new convenience initializers is MINOR\n - Deprecating (but not removing) public APIs with @available(*, deprecated) is MINOR\n\n PATCH (no API surface change):\n - Changes to internal or private members are PATCH\n - Formatting, comments, or documentation changes are PATCH\n - Updating SDK version headers is PATCH\n - Refactoring URLSession internals without changing observable defaults or behavior is PATCH\n - Updating dependency versions in Package.swift is PATCH\n - Refactoring internal implementation without changing public signatures is PATCH\n - Changes to test files, xcconfig, or CI configuration are PATCH\n\n {% elif language == \"rust\" %}\n Language-specific breaking change rules for Rust:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public function, struct, enum, or trait is always MAJOR\n - Making a response field optional (T to Option) is MAJOR (callers must handle the Option)\n - New enum variants are MAJOR (Rust match statements must be exhaustive unless using _ wildcard)\n - Adding a new required field to a public struct is MAJOR (unless #[non_exhaustive])\n - Removing a public trait implementation is MAJOR\n - Changing a function's return type is MAJOR\n - Adding a required method to a public trait is MAJOR (all implementations must add it)\n - Changing the type of a public struct field is MAJOR\n - Removing or renaming a public module is MAJOR\n - Making a public type private (pub to pub(crate) or removing pub) is MAJOR\n - Changing a struct from non-exhaustive to exhaustive construction (removing .. Default::default()) is MAJOR\n - Changing generic type parameters or their bounds on public types is MAJOR\n - Changing from Result to Result where E2 is a different error type is MAJOR\n - Removing a public constant or static is MAJOR\n\n MINOR (backward-compatible additions):\n - Adding new public functions, structs, or enums is MINOR\n - Adding new optional fields to #[non_exhaustive] structs is MINOR\n - Adding new enum variants to #[non_exhaustive] enums is MINOR\n - Adding new trait implementations for existing types is MINOR\n - Adding new public constants or statics is MINOR\n - Adding new methods to existing impl blocks is MINOR\n - Adding new error types is MINOR\n - Adding new environment/server URL constants is MINOR\n - Adding new RequestOptions fields is MINOR\n - Adding new optional builder methods is MINOR\n - Deprecating (but not removing) public APIs with #[deprecated] is MINOR\n\n PATCH (no API surface change):\n - Changes to pub(crate) or private items are PATCH\n - Cargo.toml metadata, formatting, or comment changes are PATCH\n - Updating SDK version headers is PATCH\n - Refactoring reqwest/hyper internals without changing observable defaults or behavior is PATCH\n - Updating dependency versions in Cargo.toml is PATCH\n - Refactoring internal implementation without changing public signatures is PATCH\n - Updating serialization/deserialization (serde) config that preserves public types is PATCH\n - Changes to test files, build.rs, or CI configuration are PATCH\n\n {% elif language == \"kotlin\" %}\n Language-specific breaking change rules for Kotlin:\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public function, class, or property is always MAJOR\n - Making a response field nullable (T to T?) is MAJOR (callers must handle null safety operators)\n - New enum values are MAJOR if used in when() expressions without else branch\n - Adding a new required parameter to a public function is MAJOR\n - Changing a method's return type is MAJOR\n - Removing or changing a public constant (const val / companion object val) is MAJOR\n - Changing a class from open to final (or data class to regular class) is MAJOR\n - Removing an interface implementation from a public class is MAJOR\n - Changing generic type parameters or their variance (in/out) on public types is MAJOR\n - Moving a public class to a different package without type aliasing is MAJOR\n - Changing a property from var to val (or removing a setter) is MAJOR\n - Changing a suspend function to non-suspend (or vice versa) is MAJOR\n - Changing sealed class/interface hierarchy (removing subclasses) is MAJOR\n\n MINOR (backward-compatible additions):\n - Adding new public classes, functions, or extension functions is MINOR\n - Adding new optional parameters (with default values) is MINOR\n - Adding new enum values when callers use else in when() is MINOR\n - Adding new optional properties to data classes is MINOR\n - Adding new exception types is MINOR\n - Adding new environment/server URL constants is MINOR\n - Adding new RequestOptions fields is MINOR\n - Adding new sealed class/interface subtypes is MINOR (if callers have else branch)\n - Deprecating (but not removing) public APIs with @Deprecated is MINOR\n - Adding new companion object functions is MINOR\n\n PATCH (no API surface change):\n - Changes to internal or private members are PATCH\n - KDoc, formatting, or build.gradle changes are PATCH\n - Updating SDK version headers is PATCH\n - Refactoring OkHttp internals without changing observable defaults or behavior is PATCH\n - Updating dependency versions in build.gradle.kts is PATCH\n - Refactoring internal implementation without changing public signatures is PATCH\n - Updating serialization/deserialization (kotlinx.serialization/Moshi) config that preserves public types is PATCH\n - Changes to test files or CI configuration are PATCH\n\n {% else %}\n Language-specific breaking change rules (language: {{ language }}):\n\n MAJOR (breaking):\n - Removing a required response field is always MAJOR\n - Removing or renaming a public class, method, or function is always MAJOR\n - Making a response field optional is MAJOR in statically-typed languages (TypeScript, Java, C#, Swift, Rust, Kotlin, Go), usually MINOR in dynamically-typed ones (Python, Ruby, PHP)\n - New enum values are MAJOR if the language enforces exhaustive matching (TypeScript, Java, C#, Swift, Rust), MINOR otherwise\n - Adding a new required parameter to a public method is MAJOR\n - Changing a method's return type is MAJOR\n - Changing the type of an existing field/property is MAJOR\n - Removing or changing public constants is MAJOR\n\n MINOR (backward-compatible additions):\n - Adding new public APIs (classes, methods, functions) is MINOR\n - Adding new optional parameters is MINOR\n - Adding new optional fields to request/response objects is MINOR\n - Adding new error/exception types is MINOR\n - Deprecating (but not removing) public APIs is MINOR\n\n PATCH (no API surface change):\n - Internal/private changes are PATCH\n - Formatting, documentation, or comment changes are PATCH\n - Dependency version updates are PATCH\n - SDK version header updates are PATCH\n - Refactoring retry/timeout internals without changing observable defaults or behavior is PATCH\n - Refactoring internals without changing public signatures is PATCH\n {% endif %}\n\n Apply these patterns to the diff below. When in doubt between MINOR and PATCH,\n prefer MINOR. When in doubt between MAJOR and MINOR, examine whether existing\n callers would break without any code changes on their side.\n\n Message Format (use this exact structure):\n ```\n : \n\n \n\n Key changes:\n - \n - \n - \n ```\n\n Message Guidelines:\n - Use conventional commit types: feat, fix, refactor, docs, chore, test, style, perf\n - Keep the summary line under 72 characters\n - Write in present tense imperative mood (\"add\" not \"added\" or \"adds\")\n - For breaking changes: include migration instructions in the detailed description\n - For new features: highlight new capabilities in the key changes\n - For PATCH: describe the fix or improvement\n - For NO_CHANGE: use type \"chore\" and state that no functional changes were made\n - Be specific and action-oriented\n - Do not use \"Fern regeneration\" in commit messages - use \"SDK regeneration\" instead\n - NEVER include the literal version \"505.503.4455\" in the commit message - if you see this placeholder\n in the diff, describe changes generically (e.g., \"added X-Fern-SDK-Version header\")\n - The previous version is provided for context only. Do not include it\n literally in the commit message summary line.\n\n {% if prior_changelog %}\n Prior changelog entries (for style reference):\n ---\n {{prior_changelog}}\n ---\n Match the tone and format of these entries in your commit message.\n {% endif %}\n\n {% if spec_commit_message %}\n The API spec change that triggered this SDK generation had the following commit message:\n \"{{spec_commit_message}}\"\n Use this as a hint for understanding the intent of the change, but always verify\n against the actual diff below. The commit message may be vague or inaccurate.\n {% endif %}\n\n Previous version: {{previous_version}}\n SDK language: {{language}}\n\n Git Diff:\n ---\n {{diff}}\n ---\n\n Changelog Entry Guidelines:\n - Write for SDK consumers, not engineers reading the source code\n - MAJOR: explain what broke and how to migrate (\"The `getUser` method has been\n removed. Replace calls with `fetchUser(id)` which returns the same type.\")\n - MINOR: describe the new capability (\"New `createPayment()` method available\n on `PaymentsClient`.\")\n - PATCH: leave empty string — patch changes don't warrant changelog entries\n - Do not use conventional commit prefixes (no \"feat:\", \"fix:\", etc.)\n - Write in third person (\"The SDK now supports...\" not \"Add support for...\")\n - IMPORTANT: Wrap any type references containing angle brackets in backticks\n to prevent MDX parsing issues. For example, write `Optional` not\n Optional, and `Map` not Map.\n\n Remember again that YOU MUST return a structured JSON response with these four fields:\n - message: A git commit message formatted like the example previously provided\n - changelog_entry: A user-facing release note (empty string for PATCH)\n - version_bump: One of: MAJOR, MINOR, PATCH, or NO_CHANGE\n - version_bump_reason: One sentence explaining WHY this bump level was chosen.\n For MAJOR, name the specific breaking symbol(s) and explain why existing callers break.\n For MINOR, name the new capability added.\n For PATCH, describe what was fixed or improved.\n For NO_CHANGE, say \"No functional changes detected.\"\n Example: \"MAJOR because `parserCreateJob` InputStream overloads were removed from `RawLabReportClient`, breaking existing callers.\"\n \"#\n}\n\nclass ConsolidateChangelogResponse {\n consolidated_changelog string\n @description(\"CHANGELOG.md entry in Keep a Changelog format. Group under ### Breaking Changes, ### Added, ### Changed, ### Fixed. Bold symbol names, one tight sentence per bullet. Prose only, no code fences. Append migration action inline for breaking changes.\")\n\n pr_description string\n @description(\"PR description with ## Breaking Changes section (if any) containing ### per breaking change with Before/After code fences and Migration line, then ## What's New section summarizing features in prose paragraphs grouped by theme. Do NOT list every class individually — summarize repetitive changes as a single entry.\")\n\n version_bump_reason string\n @description(\"One sentence explaining WHY the overall version bump was chosen. For MAJOR: name the specific breaking symbol(s). For MINOR: name the new capability. For PATCH: describe the fix. Example: 'MAJOR because `parserCreateJob` InputStream overloads were removed from `RawLabReportClient`.'\")\n}\n\nfunction ConsolidateChangelog(\n raw_entries: string @description(\"Newline-separated raw changelog entries from chunked diff analysis\"),\n version_bump: string @description(\"The overall version bump: MAJOR, MINOR, or PATCH\"),\n language: string @description(\"The SDK programming language, e.g. 'typescript', 'python', 'java'\")\n) -> ConsolidateChangelogResponse {\n client DefaultClient\n\n prompt #\"\n You are a technical writer formatting release notes for a {{language}} SDK.\n\n The raw change notes below are noisy and repetitive — many bullets describe the same\n change across different packages. Deduplicate aggressively: if the same feature appears\n multiple times, merge into one entry.\n\n Raw changelog entries:\n ---\n {{raw_entries}}\n ---\n\n Overall version bump: {{version_bump}}\n\n Produce three outputs:\n\n ---\n\n ## 1. CHANGELOG.md entry (Keep a Changelog format)\n\n - Group under: `### Breaking Changes`, `### Added`, `### Changed`, `### Fixed`\n - Only include sections with entries\n - **Bold the symbol name** first, then one tight sentence for SDK consumers\n - No code fences — prose only\n - For breaking changes, append the migration action inline\n\n ## 2. PR Description\n\n - `## Breaking Changes` section at top (if any)\n - One `###` per breaking change with **Before/After** code fences and a **Migration:** line\n - `## What's New` section summarizing added/changed features in prose paragraphs,\n grouped by theme (e.g. logging, streaming, pagination, builder improvements)\n - Do NOT list every class that got the same method — summarize as a single entry\n\n ## 3. Version Bump Reason\n\n - One sentence explaining WHY the overall version bump ({{version_bump}}) was chosen\n - For MAJOR: name the specific breaking symbol(s) and explain why existing callers break\n - For MINOR: name the new capability added\n - For PATCH: describe what was fixed or improved\n - Example: \"MAJOR because `parserCreateJob` InputStream overloads were removed from `RawLabReportClient`, breaking existing callers.\"\n\n ---\n\n {{ ctx.output_format }}\n \"#\n}\n", "generators.baml": "generator target {\n output_type \"typescript\"\n\n output_dir \"../src/\"\n\n version \"0.219.0\"\n\n default_client_mode async\n}\n", "main.baml": "// BAML client configuration for LLM providers\n\n// OpenAI client configuration\nclient OpenAI {\n provider openai\n options {\n model gpt-4o\n api_key env.OPENAI_API_KEY\n }\n}\n\n// Anthropic client configuration\nclient Anthropic {\n provider anthropic\n options {\n model claude-sonnet-4-5-20250929\n api_key env.ANTHROPIC_API_KEY\n }\n}\n\n// Bedrock client configuration\nclient Bedrock {\n provider aws-bedrock\n options {\n model anthropic.claude-3-5-sonnet-20240620-v1:0\n }\n}\n\n// Fallback client that tries multiple providers\n// TODO(tjb9dc): I wish we didn't have to specify this, we're only going to use one at runtime\nclient DefaultClient {\n provider fallback\n options {\n strategy [\n Anthropic\n OpenAI\n Bedrock\n ]\n }\n}\n", } diff --git a/packages/cli/cli-v2/.gitignore b/packages/cli/cli-v2/.gitignore new file mode 100644 index 000000000000..db154f295904 --- /dev/null +++ b/packages/cli/cli-v2/.gitignore @@ -0,0 +1 @@ +*.bun-build diff --git a/packages/cli/cli-v2/CLAUDE.md b/packages/cli/cli-v2/CLAUDE.md new file mode 100644 index 000000000000..1e524a7e0d3c --- /dev/null +++ b/packages/cli/cli-v2/CLAUDE.md @@ -0,0 +1,87 @@ +# CLI-V2 Package + +The next-generation Fern CLI, built on yargs with a class-based command architecture. + +## Command Structure + +Commands use one of three registration patterns from `src/commands/_internal/`: + +### Leaf Command (`command()` helper) +Single command with no subcommands. This is the most common pattern. + +```typescript +export declare namespace SplitCommand { + export interface Args extends GlobalArgs { + api?: string; + format?: SplitFormatInput; + } +} + +export class SplitCommand { + public async handle(context: Context, args: SplitCommand.Args): Promise { + // ... + } + + private async splitAsOverlay(/* ... */): Promise { + // Private helpers for internal logic + } +} + +export function addSplitCommand(cli: Argv): void { + const cmd = new SplitCommand(); + command( + cli, + "split", + "Description shown in help", + (context, args) => cmd.handle(context, args as SplitCommand.Args), + (yargs) => + yargs + .option("api", { type: "string", description: "Filter by API name" }) + .option("format", { type: "string", default: "overlay" }) + ); +} +``` + +### Command Group (`commandGroup()` helper) +Routes to required subcommands, no default handler. + +```typescript +export function addAuthCommand(cli: Argv): void { + commandGroup(cli, "auth", "Authenticate fern", [ + addLoginCommand, + addLogoutCommand, + addStatusCommand + ]); +} +``` + +### Command with Subcommands (`commandWithSubcommands()` helper) +Has both a default handler AND subcommands (git-stash pattern). + +```typescript +commandWithSubcommands(cli, "preview", "Preview docs", handler, builder, [addDeleteCommand]); +``` + +## Key Conventions + +- **Class-based handlers**: Commands are classes with a public `handle(context, args)` method. Private methods for internal logic. The class is instantiated once in the `add*Command` registration function. +- **Args via declare namespace**: Each command declares its args interface inside a `declare namespace` block extending `GlobalArgs`. +- **File naming**: `command.ts` for implementation, `index.ts` for single-line re-export, `__test__/` for tests alongside source. +- **Import paths**: Always use `.js` extensions (ESM). Use `@fern-api/*` for workspace packages, relative paths within this package. +- **Context-first**: All handlers receive `Context` as first argument — provides logging, workspace loading, auth, telemetry, and shutdown hooks. +- **Errors**: Throw `CliError` (with static factories like `CliError.authRequired()`, `CliError.notFound()`). Never swallow errors silently. +- **UI output**: Use `Icons.success`/`Icons.error` from `ui/format.ts` with `chalk` for colored output. Info/debug to `context.stderr`, structured output to `context.stdout`. + +## Testing + +```typescript +// Silent context for logic tests +const context = createTestContext({ cwd: AbsoluteFilePath.of(testDir) }); + +// Context with output capture for asserting messages +const { context, getStdout, getStderr } = createTestContextWithCapture({ cwd }); +await cmd.handle(context, args); +expect(getStderr()).toContain("expected message"); +``` + +Test utilities live in `src/__test__/utils/`. Command tests live in `src/commands/*/__test__/`. diff --git a/packages/cli/cli-v2/src/__test__/FernYmlEditor.test.ts b/packages/cli/cli-v2/src/__test__/FernYmlEditor.test.ts index 734fcd5b47b8..9253b8c62e96 100644 --- a/packages/cli/cli-v2/src/__test__/FernYmlEditor.test.ts +++ b/packages/cli/cli-v2/src/__test__/FernYmlEditor.test.ts @@ -18,6 +18,8 @@ describe("FernYmlEditor", () => { await rm(testDir, { recursive: true, force: true }); }); + // ─── SDK target tests ──────────────────────────────────────────────── + describe("setTargetVersion", () => { it("updates a target version in fern.yml", async () => { const fernYmlPath = AbsoluteFilePath.of(join(testDir, "fern.yml")); @@ -35,7 +37,7 @@ sdks: ); const editor = await FernYmlEditor.load({ fernYmlPath }); - editor.setTargetVersion("typescript", "2.0.0"); + await editor.setTargetVersion("typescript", "2.0.0"); await editor.save(); const content = await readFile(fernYmlPath, "utf-8"); @@ -60,7 +62,7 @@ sdks: ); const editor = await FernYmlEditor.load({ fernYmlPath }); - editor.setTargetVersion("typescript", "2.0.0"); + await editor.setTargetVersion("typescript", "2.0.0"); await editor.save(); const content = await readFile(fernYmlPath, "utf-8"); @@ -68,7 +70,7 @@ sdks: }); }); - describe("$ref resolution", () => { + describe("sdks $ref resolution", () => { it("writes to the referenced file when sdks uses $ref", async () => { const fernYmlPath = AbsoluteFilePath.of(join(testDir, "fern.yml")); const sdksPath = join(testDir, "sdks.yaml"); @@ -92,7 +94,7 @@ sdks: ); const editor = await FernYmlEditor.load({ fernYmlPath }); - editor.setTargetVersion("typescript", "2.0.0"); + await editor.setTargetVersion("typescript", "2.0.0"); await editor.save(); // The referenced file should be updated, not fern.yml @@ -122,7 +124,7 @@ sdks: ); const editor = await FernYmlEditor.load({ fernYmlPath }); - editor.addTarget("python", { version: "2.0.0", output: "./sdks/python" }); + await editor.addTarget("python", { version: "2.0.0", output: "./sdks/python" }); await editor.save(); const content = await readFile(fernYmlPath, "utf-8"); @@ -140,7 +142,7 @@ org: acme ); const editor = await FernYmlEditor.load({ fernYmlPath }); - editor.addTarget("typescript", { output: "./sdks/typescript" }); + await editor.addTarget("typescript", { output: "./sdks/typescript" }); await editor.save(); const content = await readFile(fernYmlPath, "utf-8"); @@ -172,7 +174,7 @@ sdks: ); const editor = await FernYmlEditor.load({ fernYmlPath }); - editor.addTarget("python", { version: "2.0.0", output: "./sdks/python" }); + await editor.addTarget("python", { version: "2.0.0", output: "./sdks/python" }); await editor.save(); const sdksContent = await readFile(sdksPath, "utf-8"); @@ -197,7 +199,7 @@ sdks: ); const editor = await FernYmlEditor.load({ fernYmlPath }); - editor.setTargetConfig("typescript", { streamType: "wrapper" }); + await editor.setTargetConfig("typescript", { streamType: "wrapper" }); await editor.save(); const content = await readFile(fernYmlPath, "utf-8"); @@ -223,7 +225,7 @@ sdks: ); const editor = await FernYmlEditor.load({ fernYmlPath }); - editor.setTargetConfig("typescript", { streamType: "wrapper", fetchSupport: "node-fetch" }); + await editor.setTargetConfig("typescript", { streamType: "wrapper", fetchSupport: "node-fetch" }); await editor.save(); const content = await readFile(fernYmlPath, "utf-8"); @@ -251,7 +253,7 @@ sdks: ); const editor = await FernYmlEditor.load({ fernYmlPath }); - editor.deleteTargetConfig("typescript"); + await editor.deleteTargetConfig("typescript"); await editor.save(); const content = await readFile(fernYmlPath, "utf-8"); @@ -260,4 +262,322 @@ sdks: expect(content).toContain('version: "1.0.0"'); }); }); + + // ─── API spec override/overlay tests ───────────────────────────────── + + describe("addOverride — inline api section", () => { + it("adds override to spec with no existing overrides", async () => { + const fernYmlPath = AbsoluteFilePath.of(join(testDir, "fern.yml")); + const specFilePath = AbsoluteFilePath.of(join(testDir, "openapi.yml")); + await writeFile( + fernYmlPath, + `api: + specs: + - openapi: ./openapi.yml +` + ); + + const editor = await FernYmlEditor.load({ fernYmlPath }); + const overridePath = AbsoluteFilePath.of(join(testDir, "openapi-overrides.yml")); + const edit = await editor.addOverride(specFilePath, overridePath); + await editor.save(); + + const result = await readFile(fernYmlPath, "utf8"); + expect(result).toContain("overrides: ./openapi-overrides.yml"); + expect(edit?.line).toBeGreaterThan(0); + }); + + it("converts single override to array when adding second", async () => { + const fernYmlPath = AbsoluteFilePath.of(join(testDir, "fern.yml")); + const specFilePath = AbsoluteFilePath.of(join(testDir, "openapi.yml")); + await writeFile( + fernYmlPath, + `api: + specs: + - openapi: ./openapi.yml + overrides: ./overrides-1.yml +` + ); + + const editor = await FernYmlEditor.load({ fernYmlPath }); + const overridePath = AbsoluteFilePath.of(join(testDir, "overrides-2.yml")); + await editor.addOverride(specFilePath, overridePath); + await editor.save(); + + const result = await readFile(fernYmlPath, "utf8"); + expect(result).toContain("overrides-1.yml"); + expect(result).toContain("overrides-2.yml"); + }); + + it("appends to existing array of overrides", async () => { + const fernYmlPath = AbsoluteFilePath.of(join(testDir, "fern.yml")); + const specFilePath = AbsoluteFilePath.of(join(testDir, "openapi.yml")); + await writeFile( + fernYmlPath, + `api: + specs: + - openapi: ./openapi.yml + overrides: + - ./overrides-1.yml + - ./overrides-2.yml +` + ); + + const editor = await FernYmlEditor.load({ fernYmlPath }); + const overridePath = AbsoluteFilePath.of(join(testDir, "overrides-3.yml")); + await editor.addOverride(specFilePath, overridePath); + await editor.save(); + + const result = await readFile(fernYmlPath, "utf8"); + expect(result).toContain("overrides-1.yml"); + expect(result).toContain("overrides-2.yml"); + expect(result).toContain("overrides-3.yml"); + }); + + it("does not duplicate existing override", async () => { + const fernYmlPath = AbsoluteFilePath.of(join(testDir, "fern.yml")); + const specFilePath = AbsoluteFilePath.of(join(testDir, "openapi.yml")); + await writeFile( + fernYmlPath, + `api: + specs: + - openapi: ./openapi.yml + overrides: ./openapi-overrides.yml +` + ); + + const editor = await FernYmlEditor.load({ fernYmlPath }); + const overridePath = AbsoluteFilePath.of(join(testDir, "openapi-overrides.yml")); + const edit = await editor.addOverride(specFilePath, overridePath); + + expect(edit).toBeUndefined(); + }); + + it("returns undefined when no specs array", async () => { + const fernYmlPath = AbsoluteFilePath.of(join(testDir, "fern.yml")); + const specFilePath = AbsoluteFilePath.of(join(testDir, "openapi.yml")); + await writeFile( + fernYmlPath, + `api: + auth: bearer +` + ); + + const editor = await FernYmlEditor.load({ fernYmlPath }); + const overridePath = AbsoluteFilePath.of(join(testDir, "overrides.yml")); + const edit = await editor.addOverride(specFilePath, overridePath); + + expect(edit).toBeUndefined(); + }); + + it("removes single override reference", async () => { + const fernYmlPath = AbsoluteFilePath.of(join(testDir, "fern.yml")); + const specFilePath = AbsoluteFilePath.of(join(testDir, "openapi.yml")); + await writeFile( + fernYmlPath, + `api: + specs: + - openapi: ./openapi.yml + overrides: ./overrides.yml +` + ); + + const editor = await FernYmlEditor.load({ fernYmlPath }); + const edit = await editor.removeOverrides(specFilePath); + await editor.save(); + + const result = await readFile(fernYmlPath, "utf8"); + expect(result).not.toContain("overrides"); + expect(edit?.removed).toEqual(["./overrides.yml"]); + expect(edit?.line).toBeGreaterThan(0); + }); + + it("removes array of overrides", async () => { + const fernYmlPath = AbsoluteFilePath.of(join(testDir, "fern.yml")); + const specFilePath = AbsoluteFilePath.of(join(testDir, "openapi.yml")); + await writeFile( + fernYmlPath, + `api: + specs: + - openapi: ./openapi.yml + overrides: + - ./a.yml + - ./b.yml +` + ); + + const editor = await FernYmlEditor.load({ fernYmlPath }); + const edit = await editor.removeOverrides(specFilePath); + await editor.save(); + + const result = await readFile(fernYmlPath, "utf8"); + expect(result).not.toContain("overrides"); + expect(edit?.removed).toEqual(["./a.yml", "./b.yml"]); + }); + + it("returns undefined when no overrides exist", async () => { + const fernYmlPath = AbsoluteFilePath.of(join(testDir, "fern.yml")); + const specFilePath = AbsoluteFilePath.of(join(testDir, "openapi.yml")); + await writeFile( + fernYmlPath, + `api: + specs: + - openapi: ./openapi.yml +` + ); + + const editor = await FernYmlEditor.load({ fernYmlPath }); + const edit = await editor.removeOverrides(specFilePath); + expect(edit).toBeUndefined(); + }); + + it("only removes overrides for matching spec", async () => { + const fernYmlPath = AbsoluteFilePath.of(join(testDir, "fern.yml")); + const specFilePath = AbsoluteFilePath.of(join(testDir, "openapi.yml")); + await writeFile( + fernYmlPath, + `api: + specs: + - openapi: ./openapi.yml + overrides: ./overrides-a.yml + - openapi: ./other.yml + overrides: ./overrides-b.yml +` + ); + + const editor = await FernYmlEditor.load({ fernYmlPath }); + await editor.removeOverrides(specFilePath); + await editor.save(); + + const result = await readFile(fernYmlPath, "utf8"); + expect(result).toContain("overrides-b.yml"); + expect(result).not.toContain("overrides-a.yml"); + }); + + it("does not write file when no mutations were made", async () => { + const fernYmlPath = AbsoluteFilePath.of(join(testDir, "fern.yml")); + await writeFile( + fernYmlPath, + `api: + specs: + - openapi: ./openapi.yml +` + ); + const original = await readFile(fernYmlPath, "utf8"); + + const editor = await FernYmlEditor.load({ fernYmlPath }); + await editor.save(); + + const after = await readFile(fernYmlPath, "utf8"); + expect(after).toBe(original); + }); + + it("preserves comments and formatting when adding overrides", async () => { + const fernYmlPath = AbsoluteFilePath.of(join(testDir, "fern.yml")); + const specFilePath = AbsoluteFilePath.of(join(testDir, "openapi.yml")); + await writeFile( + fernYmlPath, + `# API configuration +api: + specs: + # Main spec + - openapi: ./openapi.yml +` + ); + + const editor = await FernYmlEditor.load({ fernYmlPath }); + const overridePath = AbsoluteFilePath.of(join(testDir, "overrides.yml")); + await editor.addOverride(specFilePath, overridePath); + await editor.save(); + + const result = await readFile(fernYmlPath, "utf8"); + expect(result).toContain("# API configuration"); + expect(result).toContain("# Main spec"); + expect(result).toContain("overrides: ./overrides.yml"); + }); + + it("preserves comments and formatting when removing overrides", async () => { + const fernYmlPath = AbsoluteFilePath.of(join(testDir, "fern.yml")); + const specFilePath = AbsoluteFilePath.of(join(testDir, "openapi.yml")); + await writeFile( + fernYmlPath, + `# API configuration +api: + specs: + # Main spec + - openapi: ./openapi.yml + overrides: ./overrides.yml +` + ); + + const editor = await FernYmlEditor.load({ fernYmlPath }); + await editor.removeOverrides(specFilePath); + await editor.save(); + + const result = await readFile(fernYmlPath, "utf8"); + expect(result).toContain("# API configuration"); + expect(result).toContain("# Main spec"); + expect(result).not.toContain("overrides"); + }); + }); + + describe("addOverride — api $ref section", () => { + it("edits the referenced file when api uses $ref", async () => { + const fernYmlPath = AbsoluteFilePath.of(join(testDir, "fern.yml")); + const specFilePath = AbsoluteFilePath.of(join(testDir, "openapi.yml")); + const apiYmlPath = join(testDir, "api.yml"); + await writeFile( + fernYmlPath, + `api: + $ref: ./api.yml +` + ); + await writeFile( + apiYmlPath, + `specs: + - openapi: ./openapi.yml +` + ); + + const editor = await FernYmlEditor.load({ fernYmlPath }); + const overridePath = AbsoluteFilePath.of(join(testDir, "openapi-overrides.yml")); + await editor.addOverride(specFilePath, overridePath); + await editor.save(); + + // fern.yml should be unchanged + const fernContent = await readFile(fernYmlPath, "utf8"); + expect(fernContent).toContain("$ref: ./api.yml"); + + // api.yml should have the override added + const apiContent = await readFile(apiYmlPath, "utf8"); + expect(apiContent).toContain("overrides: ./openapi-overrides.yml"); + }); + + it("removes overrides from the referenced file", async () => { + const fernYmlPath = AbsoluteFilePath.of(join(testDir, "fern.yml")); + const specFilePath = AbsoluteFilePath.of(join(testDir, "openapi.yml")); + const apiYmlPath = join(testDir, "api.yml"); + await writeFile( + fernYmlPath, + `api: + $ref: ./api.yml +` + ); + await writeFile( + apiYmlPath, + `specs: + - openapi: ./openapi.yml + overrides: ./overrides.yml +` + ); + + const editor = await FernYmlEditor.load({ fernYmlPath }); + const edit = await editor.removeOverrides(specFilePath); + await editor.save(); + + expect(edit?.removed).toEqual(["./overrides.yml"]); + const apiContent = await readFile(apiYmlPath, "utf8"); + expect(apiContent).not.toContain("overrides"); + }); + }); }); diff --git a/packages/cli/cli-v2/src/commands/_internal/command.ts b/packages/cli/cli-v2/src/commands/_internal/command.ts index 50a410964d6c..41842450ece8 100644 --- a/packages/cli/cli-v2/src/commands/_internal/command.ts +++ b/packages/cli/cli-v2/src/commands/_internal/command.ts @@ -22,8 +22,7 @@ export function command( name: string, description: string, handler: CommandHandler, - builder?: BuilderCallback, - parentPath?: string + builder?: BuilderCallback ): void { cli.command( name, diff --git a/packages/cli/cli-v2/src/commands/_internal/commandGroup.ts b/packages/cli/cli-v2/src/commands/_internal/commandGroup.ts index 82f9d731bafa..9b032fc8aa79 100644 --- a/packages/cli/cli-v2/src/commands/_internal/commandGroup.ts +++ b/packages/cli/cli-v2/src/commands/_internal/commandGroup.ts @@ -1,7 +1,7 @@ import type { Argv } from "yargs"; import type { GlobalArgs } from "../../context/GlobalArgs.js"; -type CommandAdder = (cli: Argv, parentPath?: string) => void; +type CommandAdder = (cli: Argv) => void; /** * Registers a command group with subcommands. @@ -22,7 +22,7 @@ export function commandGroup( ): void { cli.command(name, description, (yargs) => { for (const add of subcommands) { - add(yargs, name); + add(yargs); } return yargs .usage(description) diff --git a/packages/cli/cli-v2/src/commands/_internal/commandWithSubcommands.ts b/packages/cli/cli-v2/src/commands/_internal/commandWithSubcommands.ts index 425ac07bbe86..dffb0f3455b5 100644 --- a/packages/cli/cli-v2/src/commands/_internal/commandWithSubcommands.ts +++ b/packages/cli/cli-v2/src/commands/_internal/commandWithSubcommands.ts @@ -4,7 +4,7 @@ import type { GlobalArgs } from "../../context/GlobalArgs.js"; import { withContext } from "../../context/withContext.js"; type CommandHandler = (context: Context, args: T) => Promise; -type CommandAdder = (cli: Argv, parentPath?: string) => void; +type CommandAdder = (cli: Argv) => void; /** * Registers a command that has both a default handler and subcommands. @@ -37,7 +37,7 @@ export function commandWithSubcommands( ): void { cli.command(name, description, (yargs) => { for (const add of subcommands) { - add(yargs, name); + add(yargs); } // Register the default command ($0) so that `fern docs preview [options]` diff --git a/packages/cli/cli-v2/src/commands/api/__test__/mergeAndSplitInverse.test.ts b/packages/cli/cli-v2/src/commands/api/__test__/mergeAndSplitInverse.test.ts new file mode 100644 index 000000000000..381c9dc7486e --- /dev/null +++ b/packages/cli/cli-v2/src/commands/api/__test__/mergeAndSplitInverse.test.ts @@ -0,0 +1,534 @@ +import { applyOpenAPIOverlay, mergeWithOverrides } from "@fern-api/core-utils"; +import { randomUUID } from "crypto"; +import { mkdir, readFile, rm, writeFile } from "fs/promises"; +import yaml from "js-yaml"; +import { tmpdir } from "os"; +import path from "path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { generateOverlay, generateOverrides, toOverlay } from "../split/diffSpecs.js"; +import { serializeSpec } from "../utils/loadSpec.js"; + +/** + * Tests that `merge` and `split` are clean inverses of one another. + * + * The core invariants: + * 1. merge(base, overrides) → merged spec. split(base, merged) → same overrides. + * 2. split(base, modified) → overrides. merge(base, overrides) → same modified spec. + * 3. merge then immediately split (without edits) recovers the original overrides. + * 4. split then immediately merge recovers the modified spec. + */ + +// biome-ignore lint/suspicious/noExplicitAny: test data +type Spec = Record; + +let testDir: string; + +beforeEach(async () => { + testDir = path.join(tmpdir(), `fern-inverse-test-${randomUUID()}`); + await mkdir(testDir, { recursive: true }); +}); + +afterEach(async () => { + await rm(testDir, { recursive: true, force: true }); +}); + +/** Simulates `fern api overrides merge` */ +function merge(base: Spec, overrides: Spec): Spec { + return mergeWithOverrides({ data: structuredClone(base), overrides }); +} + +/** Simulates `fern api overrides split` */ +function split(original: Spec, modified: Spec): { restored: Spec; overrides: Spec } { + const overrides = generateOverrides(original, modified); + return { restored: structuredClone(original), overrides }; +} + +/** Round-trip through YAML to ensure serialization doesn't alter data */ +function yamlRoundTrip(data: Spec): Spec { + return yaml.load(yaml.dump(data, { lineWidth: -1, noRefs: true })) as Spec; +} + +const baseSpec: Spec = { + openapi: "3.0.0", + info: { title: "Pet Store", version: "1.0.0", description: "A sample API" }, + paths: { + "/pets": { + get: { + operationId: "listPets", + summary: "List all pets", + parameters: [{ name: "limit", in: "query", schema: { type: "integer" } }], + responses: { + "200": { + description: "A list of pets", + content: { + "application/json": { + schema: { type: "array", items: { $ref: "#/components/schemas/Pet" } } + } + } + } + } + }, + post: { + operationId: "createPet", + summary: "Create a pet", + requestBody: { + content: { + "application/json": { + schema: { $ref: "#/components/schemas/Pet" } + } + } + }, + responses: { + "201": { description: "Created" } + } + } + }, + "/pets/{petId}": { + get: { + operationId: "getPet", + summary: "Get a pet by ID", + parameters: [{ name: "petId", in: "path", required: true, schema: { type: "string" } }], + responses: { + "200": { description: "A pet" } + } + } + } + }, + components: { + schemas: { + Pet: { + type: "object", + required: ["id", "name"], + properties: { + id: { type: "integer", format: "int64" }, + name: { type: "string" }, + tag: { type: "string" } + } + }, + Error: { + type: "object", + properties: { + code: { type: "integer" }, + message: { type: "string" } + } + } + } + } +}; + +const sampleOverrides: Spec = { + paths: { + "/pets": { + get: { + "x-fern-sdk-group-name": "pets", + "x-fern-sdk-method-name": "list" + }, + post: { + "x-fern-sdk-group-name": "pets", + "x-fern-sdk-method-name": "create" + } + }, + "/pets/{petId}": { + get: { + "x-fern-sdk-group-name": "pets", + "x-fern-sdk-method-name": "get" + } + } + }, + components: { + schemas: { + Pet: { + properties: { + breed: { type: "string", description: "The breed of the pet" } + } + } + } + } +}; + +describe("merge and split are inverses", () => { + it("merge then split recovers the original overrides", () => { + // Start: base + overrides + // Step 1: merge → merged spec + const merged = merge(baseSpec, sampleOverrides); + + // Step 2: split(base, merged) → should recover sampleOverrides + const { overrides } = split(baseSpec, merged); + + expect(overrides).toEqual(sampleOverrides); + }); + + it("split then merge recovers the modified spec", () => { + // Start: modified spec (base + edits) + const modified = structuredClone(baseSpec); + modified.paths["/pets"].get["x-fern-sdk-group-name"] = "pets"; + modified.paths["/pets"].get["x-fern-sdk-method-name"] = "list"; + modified.info.description = "An updated API"; + + // Step 1: split(base, modified) → overrides + const { restored, overrides } = split(baseSpec, modified); + + // Restored should equal base + expect(restored).toEqual(baseSpec); + + // Step 2: merge(base, overrides) → should recover modified + const remerged = merge(baseSpec, overrides); + expect(remerged).toEqual(modified); + }); + + it("merge then split then merge is idempotent", () => { + const merged1 = merge(baseSpec, sampleOverrides); + const { overrides } = split(baseSpec, merged1); + const merged2 = merge(baseSpec, overrides); + + expect(merged2).toEqual(merged1); + }); + + it("handles empty overrides as no-op", () => { + const merged = merge(baseSpec, {}); + expect(merged).toEqual(baseSpec); + + const { overrides } = split(baseSpec, merged); + expect(Object.keys(overrides)).toHaveLength(0); + }); + + it("round-trips through YAML serialization", () => { + // Simulate the full file-based workflow + const base = yamlRoundTrip(baseSpec); + const over = yamlRoundTrip(sampleOverrides); + + // merge + const merged = yamlRoundTrip(merge(base, over)); + + // split + const { overrides } = split(base, merged); + const roundTrippedOverrides = yamlRoundTrip(overrides); + + expect(roundTrippedOverrides).toEqual(over); + }); + + it("handles multiple sequential overrides merge then split", () => { + const overrides1: Spec = { + paths: { + "/pets": { + get: { "x-fern-sdk-group-name": "pets" } + } + } + }; + const overrides2: Spec = { + paths: { + "/pets": { + get: { "x-fern-sdk-method-name": "list" } + } + } + }; + + // Merge sequentially + let merged = merge(baseSpec, overrides1); + merged = merge(merged, overrides2); + + // Split should capture all changes from both overrides + const { overrides } = split(baseSpec, merged); + + expect(overrides.paths["/pets"].get["x-fern-sdk-group-name"]).toBe("pets"); + expect(overrides.paths["/pets"].get["x-fern-sdk-method-name"]).toBe("list"); + }); + + it("handles adding new top-level sections", () => { + const overridesWithServers: Spec = { + servers: [{ url: "https://api.example.com" }] + }; + + const merged = merge(baseSpec, overridesWithServers); + expect(merged.servers).toBeDefined(); + + const { overrides } = split(baseSpec, merged); + expect(overrides.servers).toEqual([{ url: "https://api.example.com" }]); + + // Re-merge recovers + const remerged = merge(baseSpec, overrides); + expect(remerged).toEqual(merged); + }); + + it("handles adding entirely new paths", () => { + const overridesWithNewPath: Spec = { + paths: { + "/owners": { + get: { + operationId: "listOwners", + summary: "List all owners" + } + } + } + }; + + const merged = merge(baseSpec, overridesWithNewPath); + const { overrides } = split(baseSpec, merged); + + expect(overrides.paths["/owners"]).toBeDefined(); + expect(overrides.paths["/pets"]).toBeUndefined(); + + const remerged = merge(baseSpec, overrides); + expect(remerged).toEqual(merged); + }); + + it("handles modifying scalar values", () => { + const overridesWithScalar: Spec = { + info: { title: "Updated Pet Store" } + }; + + const merged = merge(baseSpec, overridesWithScalar); + expect(merged.info.title).toBe("Updated Pet Store"); + expect(merged.info.version).toBe("1.0.0"); + + const { overrides } = split(baseSpec, merged); + expect(overrides.info.title).toBe("Updated Pet Store"); + + const remerged = merge(baseSpec, overrides); + expect(remerged).toEqual(merged); + }); + + it("performs full file-based workflow", async () => { + // Write base spec and overrides to files + const specPath = path.join(testDir, "openapi.yml"); + const overridesPath = path.join(testDir, "openapi-overrides.yml"); + + await writeFile(specPath, yaml.dump(baseSpec, { lineWidth: -1, noRefs: true })); + await writeFile(overridesPath, yaml.dump(sampleOverrides, { lineWidth: -1, noRefs: true })); + + // Step 1: Simulate merge — read files, merge, write back + const base = yaml.load(await readFile(specPath, "utf8")) as Spec; + const overridesFromFile = yaml.load(await readFile(overridesPath, "utf8")) as Spec; + const merged = mergeWithOverrides({ data: base, overrides: overridesFromFile }); + await writeFile(specPath, yaml.dump(merged, { lineWidth: -1, noRefs: true })); + + // Verify merged content + const mergedFromFile = yaml.load(await readFile(specPath, "utf8")) as Spec; + expect(mergedFromFile.paths["/pets"].get["x-fern-sdk-group-name"]).toBe("pets"); + + // Step 2: Simulate split — compare merged with original base, extract overrides + const originalBase = yaml.load(yaml.dump(baseSpec, { lineWidth: -1, noRefs: true })) as Spec; + const extractedOverrides = generateOverrides(originalBase, mergedFromFile); + + // Write overrides and restore spec + await writeFile(overridesPath, yaml.dump(extractedOverrides, { lineWidth: -1, noRefs: true })); + await writeFile(specPath, yaml.dump(originalBase, { lineWidth: -1, noRefs: true })); + + // Verify: spec restored to original + const restoredSpec = yaml.load(await readFile(specPath, "utf8")) as Spec; + expect(restoredSpec).toEqual(originalBase); + + // Verify: extracted overrides match original + const restoredOverrides = yaml.load(await readFile(overridesPath, "utf8")) as Spec; + expect(restoredOverrides).toEqual(yaml.load(yaml.dump(sampleOverrides, { lineWidth: -1, noRefs: true }))); + + // Step 3: Re-merge to verify full round-trip + const base2 = yaml.load(await readFile(specPath, "utf8")) as Spec; + const over2 = yaml.load(await readFile(overridesPath, "utf8")) as Spec; + const remerged = mergeWithOverrides({ data: base2, overrides: over2 }); + expect(remerged).toEqual(mergedFromFile); + }); + + it("preserves JSON format for .json spec files through merge and split", async () => { + const specPath = path.join(testDir, "openapi.json"); + const overridesPath = path.join(testDir, "openapi-overrides.yml"); + + // Write base spec as JSON, overrides as YAML + await writeFile(specPath, JSON.stringify(baseSpec, null, 2) + "\n"); + await writeFile(overridesPath, yaml.dump(sampleOverrides, { lineWidth: -1, noRefs: true })); + + // Simulate merge: read, merge, write back using serializeSpec + const base = JSON.parse(await readFile(specPath, "utf8")) as Spec; + const over = yaml.load(await readFile(overridesPath, "utf8")) as Spec; + const merged = mergeWithOverrides({ data: base, overrides: over }); + await writeFile(specPath, serializeSpec(merged, specPath)); + + // Verify the file is still valid JSON (not YAML) + const mergedRaw = await readFile(specPath, "utf8"); + const mergedParsed = JSON.parse(mergedRaw); + expect(mergedParsed.paths["/pets"].get["x-fern-sdk-group-name"]).toBe("pets"); + + // Simulate split: extract overrides, restore spec as JSON + const originalBase = JSON.parse(JSON.stringify(baseSpec)) as Spec; + const extractedOverrides = generateOverrides(originalBase, mergedParsed); + await writeFile(specPath, serializeSpec(originalBase, specPath)); + await writeFile(overridesPath, yaml.dump(extractedOverrides, { lineWidth: -1, noRefs: true })); + + // Verify spec is valid JSON after restore + const restoredRaw = await readFile(specPath, "utf8"); + const restoredSpec = JSON.parse(restoredRaw); + expect(restoredSpec).toEqual(originalBase); + + // Verify overrides is YAML (not JSON) + const overridesRaw = await readFile(overridesPath, "utf8"); + expect(() => yaml.load(overridesRaw)).not.toThrow(); + }); + + it("writes YAML spec files as YAML through merge", async () => { + const specPath = path.join(testDir, "openapi.yml"); + await writeFile(specPath, yaml.dump(baseSpec, { lineWidth: -1, noRefs: true })); + + const base = yaml.load(await readFile(specPath, "utf8")) as Spec; + const merged = mergeWithOverrides({ data: base, overrides: sampleOverrides }); + await writeFile(specPath, serializeSpec(merged, specPath)); + + // Verify the file is YAML (not JSON — should fail to parse as strict JSON) + const raw = await readFile(specPath, "utf8"); + expect(() => JSON.parse(raw)).toThrow(); + const parsed = yaml.load(raw) as Spec; + expect(parsed.paths["/pets"].get["x-fern-sdk-group-name"]).toBe("pets"); + }); +}); + +/** Simulates `fern api merge` with overlays */ +function mergeOverlay(base: Spec, overlayDoc: ReturnType): Spec { + return applyOpenAPIOverlay({ + data: structuredClone(base), + overlay: toOverlay(overlayDoc) + }); +} + +/** Simulates `fern api split --format overlays` */ +function splitOverlay(original: Spec, modified: Spec): { restored: Spec; overlay: ReturnType } { + const overlay = generateOverlay(original, modified); + return { restored: structuredClone(original), overlay }; +} + +describe("merge and split are inverses (overlay format)", () => { + it("split as overlay then merge recovers the modified spec", () => { + const modified = structuredClone(baseSpec); + modified.paths["/pets"].get["x-fern-sdk-group-name"] = "pets"; + modified.paths["/pets"].get["x-fern-sdk-method-name"] = "list"; + modified.info.description = "An updated API"; + + const { restored, overlay } = splitOverlay(baseSpec, modified); + + // Restored should equal base + expect(restored).toEqual(baseSpec); + + // Merge overlay into base should recover modified + const remerged = mergeOverlay(baseSpec, overlay); + expect(remerged).toEqual(modified); + }); + + it("merge overlay then split recovers the overlay actions' effect", () => { + // Start with a known overlay + const overlay = generateOverlay(baseSpec, merge(baseSpec, sampleOverrides)); + + // Merge overlay into base + const merged = mergeOverlay(baseSpec, overlay); + + // Split should produce an overlay that has the same effect + const { overlay: recoveredOverlay } = splitOverlay(baseSpec, merged); + const remerged = mergeOverlay(baseSpec, recoveredOverlay); + expect(remerged).toEqual(merged); + }); + + it("overlay merge then split then merge is idempotent", () => { + const modified = structuredClone(baseSpec); + modified.paths["/pets"].get["x-fern-sdk-group-name"] = "pets"; + modified.components.schemas.Pet.properties.breed = { type: "string" }; + + const merged1 = mergeOverlay(baseSpec, generateOverlay(baseSpec, modified)); + const { overlay } = splitOverlay(baseSpec, merged1); + const merged2 = mergeOverlay(baseSpec, overlay); + + expect(merged2).toEqual(merged1); + }); + + it("handles empty overlay as no-op", () => { + const overlay = generateOverlay(baseSpec, structuredClone(baseSpec)); + expect(overlay.actions).toHaveLength(0); + + const merged = mergeOverlay(baseSpec, overlay); + expect(merged).toEqual(baseSpec); + }); + + it("handles adding entirely new paths via overlay", () => { + const modified = structuredClone(baseSpec); + modified.paths["/owners"] = { + get: { operationId: "listOwners", summary: "List all owners" } + }; + + const overlay = generateOverlay(baseSpec, modified); + const merged = mergeOverlay(baseSpec, overlay); + expect(merged).toEqual(modified); + + // Round trip + const { overlay: recovered } = splitOverlay(baseSpec, merged); + const remerged = mergeOverlay(baseSpec, recovered); + expect(remerged).toEqual(modified); + }); + + it("handles adding new component schemas via overlay", () => { + const modified = structuredClone(baseSpec); + modified.components.schemas.Owner = { + type: "object", + properties: { + name: { type: "string" }, + email: { type: "string" } + } + }; + + const overlay = generateOverlay(baseSpec, modified); + const merged = mergeOverlay(baseSpec, overlay); + expect(merged).toEqual(modified); + }); + + it("round-trips through YAML serialization with overlay", () => { + const base = yamlRoundTrip(baseSpec); + const modified = structuredClone(base); + modified.paths["/pets"].get["x-fern-sdk-group-name"] = "pets"; + modified.paths["/pets"].get["x-fern-sdk-method-name"] = "list"; + + const overlay = generateOverlay(base, modified); + const overlayYaml = yamlRoundTrip(overlay); + + // Apply serialized overlay + const merged = mergeOverlay(base, overlayYaml as ReturnType); + expect(merged).toEqual(modified); + }); + + it("handles deletions via overlay remove actions", () => { + const modified = structuredClone(baseSpec); + // Delete a property + delete modified.info.description; + // Delete a path + delete modified.paths["/pets/{petId}"]; + + const overlay = generateOverlay(baseSpec, modified); + const merged = mergeOverlay(baseSpec, overlay); + expect(merged).toEqual(modified); + + // Round trip + const { overlay: recovered } = splitOverlay(baseSpec, merged); + const remerged = mergeOverlay(baseSpec, recovered); + expect(remerged).toEqual(modified); + }); + + it("handles array element modifications with index targeting via overlay", () => { + const modified = structuredClone(baseSpec); + // Modify 2nd parameter element by index + modified.paths["/pets"].get.parameters[0].description = "Max items to return"; + + const overlay = generateOverlay(baseSpec, modified); + const merged = mergeOverlay(baseSpec, overlay); + expect(merged).toEqual(modified); + }); + + it("handles combined additions and deletions via overlay", () => { + const modified = structuredClone(baseSpec); + modified.paths["/pets"].get["x-fern-group"] = "pets"; + delete modified.paths["/pets"].post; + delete modified.components.schemas.Error; + + const overlay = generateOverlay(baseSpec, modified); + const merged = mergeOverlay(baseSpec, overlay); + expect(merged).toEqual(modified); + + // Round trip + const { overlay: recovered } = splitOverlay(baseSpec, merged); + const remerged = mergeOverlay(baseSpec, recovered); + expect(remerged).toEqual(modified); + }); +}); diff --git a/packages/cli/cli-v2/src/commands/api/check/command.ts b/packages/cli/cli-v2/src/commands/api/check/command.ts index 76090921ac6d..a68ac12ae556 100644 --- a/packages/cli/cli-v2/src/commands/api/check/command.ts +++ b/packages/cli/cli-v2/src/commands/api/check/command.ts @@ -91,7 +91,7 @@ export class CheckCommand { } } -export function addCheckCommand(cli: Argv, parentPath?: string): void { +export function addCheckCommand(cli: Argv): void { const cmd = new CheckCommand(); command( cli, @@ -113,7 +113,6 @@ export function addCheckCommand(cli: Argv, parentPath?: string): voi type: "boolean", description: "Output results as JSON to stdout", default: false - }), - parentPath + }) ); } diff --git a/packages/cli/cli-v2/src/commands/api/command.ts b/packages/cli/cli-v2/src/commands/api/command.ts index b893906f7b18..e0b7c5471d66 100644 --- a/packages/cli/cli-v2/src/commands/api/command.ts +++ b/packages/cli/cli-v2/src/commands/api/command.ts @@ -4,10 +4,14 @@ import type { GlobalArgs } from "../../context/GlobalArgs.js"; import { commandGroup } from "../_internal/commandGroup.js"; import { addCheckCommand } from "./check/index.js"; import { addCompileCommand } from "./compile/index.js"; +import { addMergeCommand } from "./merge/index.js"; +import { addSplitCommand } from "./split/index.js"; export function addApiCommand(cli: Argv): void { commandGroup(cli, "api", "Configure, compile, edit, and inspect your API specs", [ addCheckCommand, - addCompileCommand + addCompileCommand, + addMergeCommand, + addSplitCommand ]); } diff --git a/packages/cli/cli-v2/src/commands/api/compile/command.ts b/packages/cli/cli-v2/src/commands/api/compile/command.ts index ce584e2701e7..4e28645d3e16 100644 --- a/packages/cli/cli-v2/src/commands/api/compile/command.ts +++ b/packages/cli/cli-v2/src/commands/api/compile/command.ts @@ -137,7 +137,7 @@ export class CompileCommand { } } -export function addCompileCommand(cli: Argv, parentPath?: string): void { +export function addCompileCommand(cli: Argv): void { const cmd = new CompileCommand(); command( cli, @@ -178,7 +178,6 @@ export function addCompileCommand(cli: Argv, parentPath?: string): v type: "string", nargs: 1, description: 'Path to write IR output. Use "-" for stdout.' - }), - parentPath + }) ); } diff --git a/packages/cli/cli-v2/src/commands/api/merge/__test__/merge.test.ts b/packages/cli/cli-v2/src/commands/api/merge/__test__/merge.test.ts new file mode 100644 index 000000000000..e9e09e77d0ea --- /dev/null +++ b/packages/cli/cli-v2/src/commands/api/merge/__test__/merge.test.ts @@ -0,0 +1,169 @@ +import { mergeWithOverrides } from "@fern-api/core-utils"; +import { randomUUID } from "crypto"; +import { mkdir, readFile, rm, writeFile } from "fs/promises"; +import yaml from "js-yaml"; +import { tmpdir } from "os"; +import path from "path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; + +// biome-ignore lint/suspicious/noExplicitAny: test data +type Spec = Record; + +let testDir: string; + +beforeEach(async () => { + testDir = path.join(tmpdir(), `fern-merge-test-${randomUUID()}`); + await mkdir(testDir, { recursive: true }); +}); + +afterEach(async () => { + await rm(testDir, { recursive: true, force: true }); +}); + +async function writeYaml(filename: string, data: Spec): Promise { + const filepath = path.join(testDir, filename); + await writeFile(filepath, yaml.dump(data, { lineWidth: -1, noRefs: true })); + return filepath; +} + +async function readYaml(filename: string): Promise { + const filepath = path.join(testDir, filename); + const contents = await readFile(filepath, "utf8"); + return yaml.load(contents) as Spec; +} + +function mergeSpec(base: Spec, overrides: Spec): Spec { + return mergeWithOverrides({ data: structuredClone(base), overrides }); +} + +describe("merge logic", () => { + const baseSpec: Spec = { + openapi: "3.0.0", + info: { title: "Pet Store", version: "1.0.0" }, + paths: { + "/pets": { + get: { + operationId: "listPets", + summary: "List all pets", + responses: { + "200": { description: "A list of pets" } + } + } + } + }, + components: { + schemas: { + Pet: { + type: "object", + properties: { + id: { type: "integer" }, + name: { type: "string" } + } + } + } + } + }; + + const overrides: Spec = { + paths: { + "/pets": { + get: { + "x-fern-sdk-group-name": "pets", + "x-fern-sdk-method-name": "list" + } + } + }, + components: { + schemas: { + Pet: { + properties: { + breed: { type: "string" } + } + } + } + } + }; + + it("deep merges override into base spec", () => { + const merged = mergeSpec(baseSpec, overrides); + + // Original fields preserved + expect(merged.openapi).toBe("3.0.0"); + expect(merged.info.title).toBe("Pet Store"); + expect(merged.paths["/pets"].get.operationId).toBe("listPets"); + expect(merged.paths["/pets"].get.summary).toBe("List all pets"); + + // Override fields added + expect(merged.paths["/pets"].get["x-fern-sdk-group-name"]).toBe("pets"); + expect(merged.paths["/pets"].get["x-fern-sdk-method-name"]).toBe("list"); + expect(merged.components.schemas.Pet.properties.breed).toEqual({ type: "string" }); + + // Existing nested fields preserved + expect(merged.components.schemas.Pet.properties.id).toEqual({ type: "integer" }); + expect(merged.components.schemas.Pet.properties.name).toEqual({ type: "string" }); + }); + + it("merges multiple overrides sequentially", () => { + const overrides1: Spec = { + paths: { + "/pets": { + get: { "x-fern-sdk-group-name": "pets" } + } + } + }; + const overrides2: Spec = { + paths: { + "/pets": { + get: { "x-fern-sdk-method-name": "list" } + } + } + }; + + let merged = mergeSpec(baseSpec, overrides1); + merged = mergeSpec(merged, overrides2); + + expect(merged.paths["/pets"].get["x-fern-sdk-group-name"]).toBe("pets"); + expect(merged.paths["/pets"].get["x-fern-sdk-method-name"]).toBe("list"); + expect(merged.paths["/pets"].get.operationId).toBe("listPets"); + }); + + it("writes merged result as YAML file", async () => { + await writeYaml("openapi.yml", baseSpec); + await writeYaml("overrides.yml", overrides); + + const base = await readYaml("openapi.yml"); + const over = await readYaml("overrides.yml"); + const merged = mergeSpec(base, over); + + await writeFile(path.join(testDir, "merged.yml"), yaml.dump(merged, { lineWidth: -1, noRefs: true })); + const result = await readYaml("merged.yml"); + + expect(result.paths["/pets"].get["x-fern-sdk-group-name"]).toBe("pets"); + expect(result.paths["/pets"].get.operationId).toBe("listPets"); + }); + + it("is a no-op when overrides are empty", () => { + const merged = mergeSpec(baseSpec, {}); + expect(merged).toEqual(baseSpec); + }); + + it("replaces primitive arrays entirely", () => { + const base: Spec = { tags: ["a", "b"] }; + const over: Spec = { tags: ["c", "d"] }; + const merged = mergeSpec(base, over); + expect(merged.tags).toEqual(["c", "d"]); + }); + + it("handles adding entirely new paths", () => { + const over: Spec = { + paths: { + "/owners": { + get: { operationId: "listOwners" } + } + } + }; + const merged = mergeSpec(baseSpec, over); + expect(merged.paths["/owners"]).toBeDefined(); + expect(merged.paths["/pets"]).toBeDefined(); + }); +}); diff --git a/packages/cli/cli-v2/src/commands/api/merge/command.ts b/packages/cli/cli-v2/src/commands/api/merge/command.ts new file mode 100644 index 000000000000..5b4abfb1ff0d --- /dev/null +++ b/packages/cli/cli-v2/src/commands/api/merge/command.ts @@ -0,0 +1,167 @@ +import { applyOpenAPIOverlay, mergeWithOverrides } from "@fern-api/core-utils"; +import chalk from "chalk"; +import { unlink, writeFile } from "fs/promises"; +import path from "path"; +import type { Argv } from "yargs"; +import { FERN_YML_FILENAME } from "../../../config/fern-yml/constants.js"; +import { FernYmlEditor } from "../../../config/fern-yml/FernYmlEditor.js"; +import type { Context } from "../../../context/Context.js"; +import type { GlobalArgs } from "../../../context/GlobalArgs.js"; +import { CliError } from "../../../errors/CliError.js"; +import { Icons } from "../../../ui/format.js"; +import { command } from "../../_internal/command.js"; +import { type OverlayDocument, toOverlay } from "../split/diffSpecs.js"; +import { filterSpecs } from "../utils/filterSpecs.js"; +import { isEnoentError } from "../utils/isEnoentError.js"; +import { loadSpec, serializeSpec } from "../utils/loadSpec.js"; + +export declare namespace MergeCommand { + export interface Args extends GlobalArgs { + api?: string; + remove?: boolean; + } +} + +export class MergeCommand { + public async handle(context: Context, args: MergeCommand.Args): Promise { + const workspace = await context.loadWorkspaceOrThrow(); + + if (Object.keys(workspace.apis).length === 0) { + throw new CliError({ message: "No APIs found in workspace." }); + } + + const entries = filterSpecs(workspace, { api: args.api }); + + if (entries.length === 0) { + context.stderr.info(chalk.dim("No matching OpenAPI/AsyncAPI specs found.")); + return; + } + + let editor: FernYmlEditor | undefined; + if (args.remove === true) { + const fernYmlPath = workspace.absoluteFilePath; + if (fernYmlPath == null) { + throw new CliError({ + message: `No ${FERN_YML_FILENAME} found. Run 'fern init' to initialize a project.` + }); + } + editor = await FernYmlEditor.load({ fernYmlPath }); + } + let mergedCount = 0; + + for (const entry of entries) { + const overlayPath = entry.overlays; + const overridePaths = entry.overrides != null && entry.overrides.length > 0 ? entry.overrides : undefined; + + if (overlayPath == null && overridePaths == null) { + context.stderr.info(chalk.dim(`${entry.specFilePath}: no overrides or overlays configured, skipping.`)); + continue; + } + + // biome-ignore lint/suspicious/noExplicitAny: OpenAPI specs can have any shape + let merged: Record = await loadSpec(entry.specFilePath); + + // Apply overlays first (per user requirement: overlays before overrides) + if (overlayPath != null) { + const overlayContent = (await loadSpec(overlayPath)) as OverlayDocument; + const overlay = toOverlay(overlayContent); + merged = applyOpenAPIOverlay({ data: merged, overlay }); + context.stderr.info( + `${Icons.success} Applied overlay (${chalk.bold(String(overlay.actions.length))} action(s)) ${chalk.dim("→")} ${chalk.cyan(entry.specFilePath)}` + ); + } + + // Then apply overrides + if (overridePaths != null) { + for (const overridePath of overridePaths) { + const overrideContent = await loadSpec(overridePath); + merged = mergeWithOverrides({ data: merged, overrides: overrideContent }); + } + context.stderr.info( + `${Icons.success} Merged ${chalk.bold(String(overridePaths.length))} override(s) ${chalk.dim("→")} ${chalk.cyan(entry.specFilePath)}` + ); + } + + await writeFile(entry.specFilePath, serializeSpec(merged, entry.specFilePath)); + mergedCount++; + + if (editor != null) { + // Remove references from the in-memory YAML (files deleted after save) + if (overridePaths != null) { + const edit = await editor.removeOverrides(entry.specFilePath); + if (edit != null) { + const relPath = path.relative(context.cwd, await editor.getApiFilePath()); + const names = overridePaths.map((o) => path.basename(o)).join(", "); + context.stderr.info(chalk.dim(`${relPath}:${edit.line}: removed reference to ${names}`)); + } + } + + if (overlayPath != null) { + const edit = await editor.removeOverlay(entry.specFilePath); + if (edit != null) { + const relPath = path.relative(context.cwd, await editor.getApiFilePath()); + context.stderr.info( + chalk.dim(`${relPath}:${edit.line}: removed reference to ${path.basename(overlayPath)}`) + ); + } + } + } + } + + if (editor != null) { + await editor.save(); + } + + // Delete overlay/override files only after the YAML has been saved successfully + if (args.remove === true) { + for (const entry of entries) { + if (entry.overrides != null && entry.overrides.length > 0) { + await this.cleanupFiles(context, entry.overrides); + } + if (entry.overlays != null) { + await this.cleanupFiles(context, [entry.overlays]); + } + } + } + + if (mergedCount === 0) { + context.stderr.info(chalk.dim("No specs had overrides or overlays to merge.")); + } + } + + private async cleanupFiles(context: Context, filePaths: string[]): Promise { + await Promise.all( + filePaths.map(async (filePath) => { + try { + await unlink(filePath); + context.stderr.info(chalk.dim(`Deleted ${filePath}`)); + } catch (error) { + if (!isEnoentError(error)) { + throw error; + } + } + }) + ); + } +} + +export function addMergeCommand(cli: Argv): void { + const cmd = new MergeCommand(); + command( + cli, + "merge", + "Flatten overlay and override files into the base API spec", + (context, args) => cmd.handle(context, args as MergeCommand.Args), + (yargs) => + yargs + .option("api", { + type: "string", + description: "Filter by API name" + }) + .option("remove", { + type: "boolean", + default: false, + description: "Remove overlay/override files and their references from fern.yml after merge" + }) + ); +} diff --git a/packages/cli/cli-v2/src/commands/api/merge/index.ts b/packages/cli/cli-v2/src/commands/api/merge/index.ts new file mode 100644 index 000000000000..63d53df2c98e --- /dev/null +++ b/packages/cli/cli-v2/src/commands/api/merge/index.ts @@ -0,0 +1 @@ +export { addMergeCommand } from "./command.js"; diff --git a/packages/cli/cli-v2/src/commands/api/split/__test__/diffSpecs.test.ts b/packages/cli/cli-v2/src/commands/api/split/__test__/diffSpecs.test.ts new file mode 100644 index 000000000000..6ba629982686 --- /dev/null +++ b/packages/cli/cli-v2/src/commands/api/split/__test__/diffSpecs.test.ts @@ -0,0 +1,618 @@ +import { applyOpenAPIOverlay } from "@fern-api/core-utils"; +import { describe, expect, it } from "vitest"; +import { diffObjects, generateOverlay, generateOverrides, toOverlay } from "../diffSpecs.js"; + +describe("diffObjects", () => { + it("returns undefined when values are identical", () => { + expect(diffObjects("a", "a")).toBeUndefined(); + expect(diffObjects(1, 1)).toBeUndefined(); + expect(diffObjects({ a: 1 }, { a: 1 })).toBeUndefined(); + }); + + it("returns modified value for changed primitives", () => { + expect(diffObjects("a", "b")).toBe("b"); + expect(diffObjects(1, 2)).toBe(2); + expect(diffObjects(true, false)).toBe(false); + }); + + it("returns modified when original is undefined", () => { + expect(diffObjects(undefined, "new")).toBe("new"); + expect(diffObjects(undefined, { a: 1 })).toEqual({ a: 1 }); + }); + + it("returns modified when original is null", () => { + expect(diffObjects(null, "new")).toBe("new"); + expect(diffObjects(null, { a: 1 })).toEqual({ a: 1 }); + }); + + it("returns undefined when modified is undefined", () => { + expect(diffObjects("a", undefined)).toBeUndefined(); + }); + + it("diffs nested objects and returns only changes", () => { + const original = { a: 1, b: 2, c: { d: 3, e: 4 } }; + const modified = { a: 1, b: 5, c: { d: 3, e: 6 } }; + expect(diffObjects(original, modified)).toEqual({ b: 5, c: { e: 6 } }); + }); + + it("includes added keys", () => { + const original = { a: 1 }; + const modified = { a: 1, b: 2 }; + expect(diffObjects(original, modified)).toEqual({ b: 2 }); + }); + + it("does not include deleted keys", () => { + const original = { a: 1, b: 2 }; + const modified = { a: 1 }; + expect(diffObjects(original, modified)).toBeUndefined(); + }); + + it("treats primitive arrays as whole replacements", () => { + const original = { tags: ["a", "b"] }; + const modified = { tags: ["a", "c"] }; + expect(diffObjects(original, modified)).toEqual({ tags: ["a", "c"] }); + }); + + it("returns undefined for identical arrays", () => { + expect(diffObjects([1, 2], [1, 2])).toBeUndefined(); + }); + + it("produces sparse diff for arrays of objects", () => { + const original = [ + { name: "id", in: "path" }, + { name: "limit", in: "query" } + ]; + const modified = [ + { name: "id", in: "path" }, + { name: "limit", in: "query", required: true } + ]; + expect(diffObjects(original, modified)).toEqual([{}, { required: true }]); + }); + + it("includes appended elements in sparse array diff", () => { + const original = [{ name: "id" }]; + const modified = [{ name: "id" }, { name: "limit", in: "query" }]; + expect(diffObjects(original, modified)).toEqual([{}, { name: "limit", in: "query" }]); + }); + + it("falls back to whole replacement when object array shrinks", () => { + const original = [{ a: 1 }, { b: 2 }, { c: 3 }]; + const modified = [{ a: 1 }, { b: 2 }]; + expect(diffObjects(original, modified)).toEqual([{ a: 1 }, { b: 2 }]); + }); + + it("returns undefined for identical arrays of objects", () => { + const arr = [{ a: 1 }, { b: 2 }]; + expect(diffObjects(arr, arr)).toBeUndefined(); + }); + + it("falls back to whole replacement for mixed arrays", () => { + const original = [{ a: 1 }, "primitive"]; + const modified = [{ a: 1 }, "changed"]; + expect(diffObjects(original, modified)).toEqual([{ a: 1 }, "changed"]); + }); + + it("handles type change from primitive to object", () => { + expect(diffObjects("old", { nested: true })).toEqual({ nested: true }); + }); + + it("handles type change from object to primitive", () => { + expect(diffObjects({ nested: true }, "new")).toBe("new"); + }); + + it("handles type change from array to object", () => { + expect(diffObjects([1, 2], { a: 1 })).toEqual({ a: 1 }); + }); + + it("handles deeply nested additions", () => { + const original = { paths: { "/users": { get: { summary: "List" } } } }; + const modified = { + paths: { "/users": { get: { summary: "List", description: "List all users" } } } + }; + expect(diffObjects(original, modified)).toEqual({ + paths: { "/users": { get: { description: "List all users" } } } + }); + }); +}); + +describe("generateOverrides", () => { + it("returns empty object for identical specs", () => { + const spec = { + openapi: "3.0.0", + info: { title: "Test", version: "1.0" }, + paths: {} + }; + expect(generateOverrides(spec, spec)).toEqual({}); + }); + + it("captures added paths", () => { + const original = { + openapi: "3.0.0", + paths: { "/users": { get: { summary: "List users" } } } + }; + const modified = { + openapi: "3.0.0", + paths: { + "/users": { get: { summary: "List users" } }, + "/pets": { get: { summary: "List pets" } } + } + }; + const result = generateOverrides(original, modified); + expect(result).toEqual({ + paths: { "/pets": { get: { summary: "List pets" } } } + }); + }); + + it("captures modified properties", () => { + const original = { + openapi: "3.0.0", + info: { title: "Old Title", version: "1.0" } + }; + const modified = { + openapi: "3.0.0", + info: { title: "New Title", version: "1.0" } + }; + const result = generateOverrides(original, modified); + expect(result).toEqual({ + info: { title: "New Title" } + }); + }); + + it("captures added x-fern extensions", () => { + const original = { + paths: { + "/users": { + get: { operationId: "listUsers" } + } + } + }; + const modified = { + paths: { + "/users": { + get: { + operationId: "listUsers", + "x-fern-sdk-group-name": "users", + "x-fern-sdk-method-name": "list" + } + } + } + }; + const result = generateOverrides(original, modified); + expect(result).toEqual({ + paths: { + "/users": { + get: { + "x-fern-sdk-group-name": "users", + "x-fern-sdk-method-name": "list" + } + } + } + }); + }); + + it("does not include deleted top-level sections", () => { + const original = { + openapi: "3.0.0", + info: { title: "Test" }, + tags: [{ name: "users" }] + }; + const modified = { + openapi: "3.0.0", + info: { title: "Test" } + }; + expect(generateOverrides(original, modified)).toEqual({}); + }); + + it("captures new top-level sections", () => { + const original = { openapi: "3.0.0" }; + const modified = { + openapi: "3.0.0", + servers: [{ url: "https://api.example.com" }] + }; + const result = generateOverrides(original, modified); + expect(result).toEqual({ + servers: [{ url: "https://api.example.com" }] + }); + }); + + it("handles component schema changes", () => { + const original = { + components: { + schemas: { + User: { + type: "object", + properties: { + name: { type: "string" } + } + } + } + } + }; + const modified = { + components: { + schemas: { + User: { + type: "object", + properties: { + name: { type: "string" }, + email: { type: "string" } + } + } + } + } + }; + const result = generateOverrides(original, modified); + expect(result).toEqual({ + components: { + schemas: { + User: { + properties: { + email: { type: "string" } + } + } + } + } + }); + }); + + it("handles changed top-level primitive", () => { + const original = { openapi: "3.0.0" }; + const modified = { openapi: "3.1.0" }; + const result = generateOverrides(original, modified); + expect(result).toEqual({ openapi: "3.1.0" }); + }); +}); + +describe("generateOverlay", () => { + it("returns empty actions for identical specs", () => { + const spec = { + openapi: "3.0.0", + info: { title: "Test", version: "1.0" }, + paths: {} + }; + const result = generateOverlay(spec, spec); + expect(result.overlay).toBe("1.0.0"); + expect(result.actions).toHaveLength(0); + }); + + it("generates action for added x-fern extensions", () => { + const original = { + paths: { + "/users": { + get: { operationId: "listUsers" } + } + } + }; + const modified = { + paths: { + "/users": { + get: { + operationId: "listUsers", + "x-fern-sdk-group-name": "users", + "x-fern-sdk-method-name": "list" + } + } + } + }; + const result = generateOverlay(original, modified); + expect(result.actions.length).toBeGreaterThan(0); + + // Applying the overlay to the original should produce the modified spec + const applied = applyOpenAPIOverlay({ data: original, overlay: toOverlay(result) }); + expect(applied).toEqual(modified); + }); + + it("generates action for added properties in components", () => { + const original = { + components: { + schemas: { + User: { + type: "object", + properties: { + name: { type: "string" } + } + } + } + } + }; + const modified = { + components: { + schemas: { + User: { + type: "object", + properties: { + name: { type: "string" }, + email: { type: "string" } + } + } + } + } + }; + const result = generateOverlay(original, modified); + expect(result.actions.length).toBeGreaterThan(0); + + const applied = applyOpenAPIOverlay({ data: original, overlay: toOverlay(result) }); + expect(applied).toEqual(modified); + }); + + it("generates action for new top-level sections", () => { + const original = { openapi: "3.0.0" }; + const modified = { + openapi: "3.0.0", + servers: [{ url: "https://api.example.com" }] + }; + const result = generateOverlay(original, modified); + expect(result.actions.length).toBeGreaterThan(0); + + const applied = applyOpenAPIOverlay({ data: original, overlay: toOverlay(result) }); + expect(applied).toEqual(modified); + }); + + it("generates action for changed scalar values", () => { + const original = { openapi: "3.0.0", info: { title: "Old", version: "1.0" } }; + const modified = { openapi: "3.0.0", info: { title: "New", version: "1.0" } }; + const result = generateOverlay(original, modified); + expect(result.actions.length).toBeGreaterThan(0); + + const applied = applyOpenAPIOverlay({ data: original, overlay: toOverlay(result) }); + expect(applied).toEqual(modified); + }); + + it("uses bracket notation for special keys in JSONPath", () => { + const original = { + paths: { + "/users/{id}": { + get: { operationId: "getUser" } + } + } + }; + const modified = { + paths: { + "/users/{id}": { + get: { operationId: "getUser", "x-fern-group": "users" } + } + } + }; + const result = generateOverlay(original, modified); + // Should use bracket notation for path keys with special chars + const targets = result.actions.map((a) => a.target); + expect(targets.some((t) => t.includes("['/users/{id}']"))).toBe(true); + + const applied = applyOpenAPIOverlay({ data: original, overlay: toOverlay(result) }); + expect(applied).toEqual(modified); + }); + + it("has correct overlay document structure", () => { + const result = generateOverlay({ openapi: "3.0.0" }, { openapi: "3.1.0" }); + expect(result.overlay).toBe("1.0.0"); + expect(result.info).toEqual({ title: "Fern Overlay", version: "0.0.0" }); + expect(Array.isArray(result.actions)).toBe(true); + }); + + it("round-trips: applying overlay to original produces modified spec", () => { + // biome-ignore lint/suspicious/noExplicitAny: test data + const original: Record = { + openapi: "3.0.0", + info: { title: "Pet Store", version: "1.0.0" }, + paths: { + "/pets": { + get: { + operationId: "listPets", + summary: "List all pets", + responses: { "200": { description: "A list of pets" } } + }, + post: { + operationId: "createPet", + summary: "Create a pet" + } + }, + "/pets/{petId}": { + get: { + operationId: "getPet", + summary: "Get a pet by ID" + } + } + }, + components: { + schemas: { + Pet: { + type: "object", + properties: { + id: { type: "integer" }, + name: { type: "string" } + } + } + } + } + }; + const modified = structuredClone(original); + modified.paths["/pets"].get["x-fern-sdk-group-name"] = "pets"; + modified.paths["/pets"].get["x-fern-sdk-method-name"] = "list"; + modified.paths["/pets"].post["x-fern-sdk-group-name"] = "pets"; + modified.paths["/pets/{petId}"].get["x-fern-sdk-group-name"] = "pets"; + modified.components.schemas.Pet.properties.breed = { type: "string" }; + + const overlay = generateOverlay(original, modified); + const applied = applyOpenAPIOverlay({ data: original, overlay: toOverlay(overlay) }); + expect(applied).toEqual(modified); + }); + + it("handles adding entirely new paths", () => { + const original = { + paths: { + "/pets": { get: { operationId: "listPets" } } + } + }; + const modified = { + paths: { + "/pets": { get: { operationId: "listPets" } }, + "/owners": { get: { operationId: "listOwners" } } + } + }; + const overlay = generateOverlay(original, modified); + const applied = applyOpenAPIOverlay({ data: original, overlay: toOverlay(overlay) }); + expect(applied).toEqual(modified); + }); + + it("targets array elements by index instead of sparse padding", () => { + // biome-ignore lint/suspicious/noExplicitAny: test data + const original: Record = { + paths: { + "/creds": { + get: { + parameters: [ + { name: "id", in: "path" }, + { name: "limit", in: "query", schema: { type: "integer" } }, + { name: "offset", in: "query" }, + { name: "sort", in: "query" }, + { name: "filter", in: "query" }, + { name: "fields", in: "query" }, + { name: "page", in: "query" }, + { name: "size", in: "query" } + ] + } + } + } + }; + const modified = structuredClone(original); + modified.paths["/creds"].get.parameters[1].schema.default = 500; + + const overlay = generateOverlay(original, modified); + + // Should NOT produce sparse arrays with empty objects + const hasEmptyObjectPadding = overlay.actions.some( + (a) => + Array.isArray(a.update) && + (a.update as unknown[]).some( + (el) => + typeof el === "object" && el !== null && Object.keys(el as Record).length === 0 + ) + ); + expect(hasEmptyObjectPadding).toBe(false); + + // Should use indexed JSONPath like $.paths['/creds'].get.parameters[1] + const targets = overlay.actions.map((a) => a.target); + expect(targets.some((t) => t.includes("parameters[1]"))).toBe(true); + + // Round-trip should produce the modified spec + const applied = applyOpenAPIOverlay({ data: original, overlay: toOverlay(overlay) }); + expect(applied).toEqual(modified); + }); + + it("emits remove actions for deleted object keys", () => { + const original = { + info: { title: "API", version: "1.0", description: "My API" } + }; + const modified = { + info: { title: "API", version: "1.0" } + }; + + const overlay = generateOverlay(original, modified); + const removeActions = overlay.actions.filter((a) => a.remove === true); + expect(removeActions.length).toBe(1); + expect(removeActions[0]?.target).toBe("$.info.description"); + + const applied = applyOpenAPIOverlay({ data: original, overlay: toOverlay(overlay) }); + expect(applied).toEqual(modified); + }); + + it("emits remove actions for deleted array elements", () => { + const original = { + tags: [ + { name: "pets", description: "Pet operations" }, + { name: "owners", description: "Owner operations" }, + { name: "admin", description: "Admin operations" } + ] + }; + const modified = { + tags: [ + { name: "pets", description: "Pet operations" }, + { name: "owners", description: "Owner operations" } + ] + }; + + const overlay = generateOverlay(original, modified); + const removeActions = overlay.actions.filter((a) => a.remove === true); + expect(removeActions.length).toBe(1); + expect(removeActions[0]?.target).toBe("$.tags[2]"); + + const applied = applyOpenAPIOverlay({ data: original, overlay: toOverlay(overlay) }); + expect(applied).toEqual(modified); + }); + + it("handles appended array elements via array-level targeting", () => { + const original = { + tags: [{ name: "pets" }] + }; + const modified = { + tags: [{ name: "pets" }, { name: "owners" }, { name: "admin" }] + }; + + const overlay = generateOverlay(original, modified); + // Appended elements should target the array itself (overlay append semantics) + const appendActions = overlay.actions.filter((a) => a.target === "$.tags" && a.remove !== true); + expect(appendActions.length).toBe(2); + + const applied = applyOpenAPIOverlay({ data: original, overlay: toOverlay(overlay) }); + expect(applied).toEqual(modified); + }); + + it("produces clean output without remove:false or empty descriptions", () => { + const original = { openapi: "3.0.0", info: { title: "Old" } }; + const modified = { openapi: "3.0.0", info: { title: "New" } }; + + const overlay = generateOverlay(original, modified); + for (const action of overlay.actions) { + // Update actions should not have remove field + if (action.update !== undefined) { + expect(action.remove).toBeUndefined(); + } + // No actions should have empty string description + expect(action.description).toBeUndefined(); + } + }); + + it("handles multiple removed array elements in correct order", () => { + const original = { + servers: [ + { url: "https://a.example.com" }, + { url: "https://b.example.com" }, + { url: "https://c.example.com" }, + { url: "https://d.example.com" } + ] + }; + const modified = { + servers: [{ url: "https://a.example.com" }, { url: "https://b.example.com" }] + }; + + const overlay = generateOverlay(original, modified); + const removeActions = overlay.actions.filter((a) => a.remove === true); + // Should remove in reverse order (index 3 before index 2) + expect(removeActions.length).toBe(2); + expect(removeActions[0]?.target).toBe("$.servers[3]"); + expect(removeActions[1]?.target).toBe("$.servers[2]"); + + const applied = applyOpenAPIOverlay({ data: original, overlay: toOverlay(overlay) }); + expect(applied).toEqual(modified); + }); + + it("handles simultaneous additions, modifications, and deletions in arrays", () => { + const original = { + tags: [ + { name: "pets", description: "Pet ops" }, + { name: "owners", description: "Owner ops" }, + { name: "deprecated", description: "Old stuff" } + ] + }; + const modified = { + tags: [ + { name: "pets", description: "Pet operations" }, + { name: "owners", description: "Owner ops" } + ] + }; + + const overlay = generateOverlay(original, modified); + const applied = applyOpenAPIOverlay({ data: original, overlay: toOverlay(overlay) }); + expect(applied).toEqual(modified); + }); +}); diff --git a/packages/cli/cli-v2/src/commands/api/split/__test__/split.test.ts b/packages/cli/cli-v2/src/commands/api/split/__test__/split.test.ts new file mode 100644 index 000000000000..955bef71cdc0 --- /dev/null +++ b/packages/cli/cli-v2/src/commands/api/split/__test__/split.test.ts @@ -0,0 +1,137 @@ +import { mergeWithOverrides } from "@fern-api/core-utils"; +import { randomUUID } from "crypto"; +import { mkdir, readFile, rm, writeFile } from "fs/promises"; +import yaml from "js-yaml"; +import { tmpdir } from "os"; +import path from "path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { generateOverrides } from "../diffSpecs.js"; + +// biome-ignore lint/suspicious/noExplicitAny: test data +type Spec = Record; + +let testDir: string; + +beforeEach(async () => { + testDir = path.join(tmpdir(), `fern-split-test-${randomUUID()}`); + await mkdir(testDir, { recursive: true }); +}); + +afterEach(async () => { + await rm(testDir, { recursive: true, force: true }); +}); + +function mergeSpec(base: Spec, overrides: Spec): Spec { + return mergeWithOverrides({ data: structuredClone(base), overrides }); +} + +describe("split logic (generateOverrides)", () => { + const baseSpec: Spec = { + openapi: "3.0.0", + info: { title: "Pet Store", version: "1.0.0" }, + paths: { + "/pets": { + get: { + operationId: "listPets", + summary: "List all pets", + responses: { + "200": { description: "A list of pets" } + } + } + } + }, + components: { + schemas: { + Pet: { + type: "object", + properties: { + id: { type: "integer" }, + name: { type: "string" } + } + } + } + } + }; + + it("extracts added x-fern extensions as overrides", () => { + const modified = structuredClone(baseSpec); + modified.paths["/pets"].get["x-fern-sdk-group-name"] = "pets"; + modified.paths["/pets"].get["x-fern-sdk-method-name"] = "list"; + + const overrides = generateOverrides(baseSpec, modified); + + expect(overrides).toEqual({ + paths: { + "/pets": { + get: { + "x-fern-sdk-group-name": "pets", + "x-fern-sdk-method-name": "list" + } + } + } + }); + }); + + it("extracts added properties as overrides", () => { + const modified = structuredClone(baseSpec); + modified.components.schemas.Pet.properties.breed = { type: "string" }; + + const overrides = generateOverrides(baseSpec, modified); + + expect(overrides).toEqual({ + components: { + schemas: { + Pet: { + properties: { + breed: { type: "string" } + } + } + } + } + }); + }); + + it("extracts modified values as overrides", () => { + const modified = structuredClone(baseSpec); + modified.info.title = "Updated Pet Store"; + + const overrides = generateOverrides(baseSpec, modified); + + expect(overrides).toEqual({ + info: { title: "Updated Pet Store" } + }); + }); + + it("returns empty overrides for identical specs", () => { + const overrides = generateOverrides(baseSpec, structuredClone(baseSpec)); + expect(Object.keys(overrides)).toHaveLength(0); + }); + + it("extracts added paths as overrides", () => { + const modified = structuredClone(baseSpec); + modified.paths["/owners"] = { + get: { operationId: "listOwners", summary: "List owners" } + }; + + const overrides = generateOverrides(baseSpec, modified); + + expect(overrides.paths["/owners"]).toBeDefined(); + expect(overrides.paths["/pets"]).toBeUndefined(); + }); + + it("works with file round-trip through YAML", async () => { + const specPath = path.join(testDir, "openapi.yml"); + await writeFile(specPath, yaml.dump(baseSpec, { lineWidth: -1, noRefs: true })); + + // Read and modify + const raw = await readFile(specPath, "utf8"); + const loaded = yaml.load(raw) as Spec; + loaded.paths["/pets"].get["x-fern-sdk-group-name"] = "pets"; + + // Generate overrides + const original = yaml.load(yaml.dump(baseSpec, { lineWidth: -1, noRefs: true })) as Spec; + const overrides = generateOverrides(original, loaded); + + expect(overrides.paths["/pets"].get["x-fern-sdk-group-name"]).toBe("pets"); + }); +}); diff --git a/packages/cli/cli-v2/src/commands/api/split/command.ts b/packages/cli/cli-v2/src/commands/api/split/command.ts new file mode 100644 index 000000000000..a4a86b412d45 --- /dev/null +++ b/packages/cli/cli-v2/src/commands/api/split/command.ts @@ -0,0 +1,296 @@ +import { mergeWithOverrides } from "@fern-api/core-utils"; +import type { AbsoluteFilePath } from "@fern-api/fs-utils"; +import chalk from "chalk"; +import { execFile } from "child_process"; +import { readFile, writeFile } from "fs/promises"; +import path from "path"; +import { promisify } from "util"; +import type { Argv } from "yargs"; +import { FERN_YML_FILENAME } from "../../../config/fern-yml/constants.js"; +import { FernYmlEditor } from "../../../config/fern-yml/FernYmlEditor.js"; +import type { Context } from "../../../context/Context.js"; +import type { GlobalArgs } from "../../../context/GlobalArgs.js"; +import { CliError } from "../../../errors/CliError.js"; +import { Icons } from "../../../ui/format.js"; +import { command } from "../../_internal/command.js"; +import type { SpecEntry } from "../utils/filterSpecs.js"; +import { filterSpecs } from "../utils/filterSpecs.js"; +import { isEnoentError } from "../utils/isEnoentError.js"; +import { loadSpec, parseSpec, serializeSpec } from "../utils/loadSpec.js"; +import { + ALL_FORMAT_NAMES, + deriveOutputPath, + normalizeSplitFormat, + OVERLAY_NAME, + OVERRIDES_NAME, + type SplitFormat, + type SplitFormatInput +} from "./deriveOutputPath.js"; +import { generateOverlay, generateOverrides, hasChanges, type OverlayOutputAction } from "./diffSpecs.js"; + +const execFileAsync = promisify(execFile); + +/** 50 MB – large enough for oversized OpenAPI specs retrieved via `git show`. */ +const GIT_MAX_BUFFER_BYTES = 50 * 1024 * 1024; + +export declare namespace SplitCommand { + export interface Args extends GlobalArgs { + api?: string; + output?: string; + format?: SplitFormatInput; + } +} + +export class SplitCommand { + public async handle(context: Context, args: SplitCommand.Args): Promise { + const workspace = await context.loadWorkspaceOrThrow(); + + if (Object.keys(workspace.apis).length === 0) { + throw new CliError({ message: "No APIs found in workspace." }); + } + + const entries = filterSpecs(workspace, { api: args.api }); + + if (entries.length === 0) { + context.stderr.info(chalk.dim("No matching OpenAPI/AsyncAPI specs found.")); + return; + } + + const format: SplitFormat = normalizeSplitFormat(args.format ?? OVERLAY_NAME); + const fernYmlPath = workspace.absoluteFilePath; + if (fernYmlPath == null) { + throw new CliError({ + message: `No ${FERN_YML_FILENAME} found. Run 'fern init' to initialize a project.` + }); + } + const editor = await FernYmlEditor.load({ fernYmlPath }); + const repoRoot = await this.getRepoRoot(fernYmlPath); + let splitCount = 0; + + for (const entry of entries) { + const [currentContent, originalRaw] = await Promise.all([ + loadSpec(entry.specFilePath), + this.getFileFromGitHead(repoRoot, entry.specFilePath) + ]); + const originalContent = parseSpec(originalRaw, entry.specFilePath); + + if (!hasChanges(originalContent, currentContent)) { + context.stderr.info(chalk.dim(`${entry.specFilePath}: no changes from git HEAD, skipping.`)); + continue; + } + + if (format === OVERLAY_NAME) { + await this.splitAsOverlay(context, editor, entry, originalContent, currentContent, args.output); + } else { + await this.splitAsOverrides(context, editor, entry, originalContent, currentContent, args.output); + } + + // Restore spec to git HEAD after overlay/overrides are safely written + await writeFile(entry.specFilePath, originalRaw); + context.stderr.debug(chalk.dim(`Restored ${entry.specFilePath} to git HEAD`)); + + splitCount++; + } + + await editor.save(); + + if (splitCount === 0) { + context.stderr.info(chalk.dim("No specs had changes to split.")); + } + } + + private async splitAsOverlay( + context: Context, + editor: FernYmlEditor, + entry: SpecEntry, + // biome-ignore lint/suspicious/noExplicitAny: OpenAPI specs can have any shape + originalContent: Record, + // biome-ignore lint/suspicious/noExplicitAny: OpenAPI specs can have any shape + currentContent: Record, + outputArg: string | undefined + ): Promise { + const overlay = generateOverlay(originalContent, currentContent); + + // Check if there's an existing overlay configured in fern.yml + const existingOverlayPath = (await editor.getOverlayPath(entry.specFilePath)) ?? entry.overlays; + + let outputPath: AbsoluteFilePath; + if (existingOverlayPath != null) { + if (outputArg != null) { + context.stderr.info( + chalk.yellow( + `Warning: --output ignored; merging into existing overlay file ${chalk.cyan(existingOverlayPath)}` + ) + ); + } + outputPath = existingOverlayPath; + } else { + outputPath = + outputArg != null + ? resolvePathOrThrow(context, outputArg) + : deriveOutputPath(entry.specFilePath, OVERLAY_NAME); + } + + if (await this.tryMergeOverlayIntoExistingFile(outputPath, overlay.actions)) { + context.stderr.info( + `${Icons.success} Merged ${chalk.bold(String(overlay.actions.length))} action(s) into existing ${chalk.cyan(outputPath)}` + ); + } else { + await writeFile(outputPath, serializeSpec(overlay, outputPath)); + context.stderr.info( + `${Icons.success} Overlay written (${chalk.bold(String(overlay.actions.length))} action(s)) ${chalk.dim("→")} ${chalk.cyan(outputPath)}` + ); + } + + const edit = await editor.addOverlay(entry.specFilePath, outputPath); + if (edit != null) { + const relPath = path.relative(context.cwd, await editor.getApiFilePath()); + context.stderr.info(chalk.dim(`${relPath}:${edit.line}: added reference to ${path.basename(outputPath)}`)); + } + } + + private async splitAsOverrides( + context: Context, + editor: FernYmlEditor, + entry: SpecEntry, + // biome-ignore lint/suspicious/noExplicitAny: OpenAPI specs can have any shape + originalContent: Record, + // biome-ignore lint/suspicious/noExplicitAny: OpenAPI specs can have any shape + currentContent: Record, + outputArg: string | undefined + ): Promise { + const overrides = generateOverrides(originalContent, currentContent); + const outputPath = + outputArg != null + ? resolvePathOrThrow(context, outputArg) + : deriveOutputPath(entry.specFilePath, OVERRIDES_NAME); + + if (await this.tryMergeIntoExistingFile(outputPath, overrides)) { + context.stderr.info(`${Icons.success} Merged diff into existing ${chalk.cyan(outputPath)}`); + } else { + await writeFile(outputPath, serializeSpec(overrides, outputPath)); + context.stderr.info(`${Icons.success} Overrides written ${chalk.dim("→")} ${chalk.cyan(outputPath)}`); + } + + const edit = await editor.addOverride(entry.specFilePath, outputPath); + if (edit != null) { + const relPath = path.relative(context.cwd, await editor.getApiFilePath()); + context.stderr.info(chalk.dim(`${relPath}:${edit.line}: added reference to ${path.basename(outputPath)}`)); + } + } + + /** + * Tries to load an existing overlay file and append new actions to it. + * Returns true if the file existed and was merged, false if the file does not exist. + */ + private async tryMergeOverlayIntoExistingFile( + outputPath: AbsoluteFilePath, + newActions: OverlayOutputAction[] + ): Promise { + let raw: string; + try { + raw = await readFile(outputPath, "utf8"); + } catch (error) { + if (isEnoentError(error)) { + return false; + } + throw error; + } + const existing = parseSpec(raw, outputPath); + const existingActions = Array.isArray(existing.actions) ? existing.actions : []; + existing.actions = [...existingActions, ...newActions]; + await writeFile(outputPath, serializeSpec(existing, outputPath)); + return true; + } + + /** + * Tries to load an existing file at `outputPath` and merge overrides into it. + * Returns true if the file existed and was merged, false if the file does not exist. + */ + private async tryMergeIntoExistingFile( + outputPath: AbsoluteFilePath, + // biome-ignore lint/suspicious/noExplicitAny: OpenAPI specs can have any shape + overrides: Record + ): Promise { + let raw: string; + try { + raw = await readFile(outputPath, "utf8"); + } catch (error) { + if (isEnoentError(error)) { + return false; + } + throw error; + } + const existing = parseSpec(raw, outputPath); + const merged = mergeWithOverrides({ data: existing, overrides }); + await writeFile(outputPath, serializeSpec(merged, outputPath)); + return true; + } + + private async getFileFromGitHead(repoRoot: string, absolutePath: AbsoluteFilePath): Promise { + try { + const relativePath = path.relative(repoRoot, absolutePath); + + const { stdout } = await execFileAsync("git", ["show", `HEAD:${relativePath}`], { + cwd: repoRoot, + encoding: "utf8", + maxBuffer: GIT_MAX_BUFFER_BYTES + }); + return stdout; + } catch (error: unknown) { + const detail = error instanceof Error ? error.message : String(error); + throw new CliError({ + message: `Failed to get file from git HEAD: ${absolutePath}. Is the file tracked by git and has at least one commit?\n Cause: ${detail}` + }); + } + } + + private async getRepoRoot(nearPath: AbsoluteFilePath): Promise { + const { stdout } = await execFileAsync("git", ["rev-parse", "--show-toplevel"], { + cwd: path.dirname(nearPath), + encoding: "utf8" + }); + return stdout.trim(); + } +} + +function resolvePathOrThrow(context: Context, outputPath: string): AbsoluteFilePath { + const resolved = context.resolveOutputFilePath(outputPath); + if (resolved == null) { + throw new CliError({ message: `Could not resolve output path: ${outputPath}` }); + } + return resolved; +} + +export function addSplitCommand(cli: Argv): void { + const cmd = new SplitCommand(); + command( + cli, + "split", + "Extract changes from a modified spec into an overlay or override file, restoring the spec to its git HEAD state", + (context, args) => cmd.handle(context, args as SplitCommand.Args), + (yargs) => + yargs + .option("api", { + type: "string", + description: "Filter by API name" + }) + .option("output", { + type: "string", + description: "Custom output path for the new overlay/override file" + }) + .option("format", { + type: "string", + default: OVERLAY_NAME, + description: `Output format: '${OVERLAY_NAME}' (OpenAPI Overlay 1.0.0) or '${OVERRIDES_NAME}' (Fern deep-merge)` + }) + .coerce("format", (value: string): SplitFormatInput => { + if (!(ALL_FORMAT_NAMES as readonly string[]).includes(value)) { + throw new Error( + `Invalid format '${value}'. Expected one of: ${OVERLAY_NAME}, ${OVERRIDES_NAME}` + ); + } + return value as SplitFormatInput; + }) + ); +} diff --git a/packages/cli/cli-v2/src/commands/api/split/deriveOutputPath.ts b/packages/cli/cli-v2/src/commands/api/split/deriveOutputPath.ts new file mode 100644 index 000000000000..1e215c0cbbbb --- /dev/null +++ b/packages/cli/cli-v2/src/commands/api/split/deriveOutputPath.ts @@ -0,0 +1,56 @@ +import { type AbsoluteFilePath, dirname, getFilename, join, RelativeFilePath } from "@fern-api/fs-utils"; + +/** Canonical name for the overlay format (singular). */ +export const OVERLAY_NAME = "overlay" as const; + +/** Alternative accepted spellings for the overlay format. */ +export const OVERLAY_NAME_ALTERNATIVES = ["overlays"] as const; + +/** Canonical name for the overrides format (plural). */ +export const OVERRIDES_NAME = "overrides" as const; + +/** Alternative accepted spellings for the overrides format. */ +export const OVERRIDES_NAME_ALTERNATIVES = ["override"] as const; + +/** The canonical split format values (public-facing). */ +export type SplitFormat = typeof OVERLAY_NAME | typeof OVERRIDES_NAME; + +/** All accepted format strings – both singular and plural forms. */ +export type SplitFormatInput = + | SplitFormat + | (typeof OVERLAY_NAME_ALTERNATIVES)[number] + | (typeof OVERRIDES_NAME_ALTERNATIVES)[number]; + +/** Every accepted format string, for input validation. */ +export const ALL_FORMAT_NAMES: readonly SplitFormatInput[] = [ + OVERLAY_NAME, + ...OVERLAY_NAME_ALTERNATIVES, + OVERRIDES_NAME, + ...OVERRIDES_NAME_ALTERNATIVES +] as const; + +/** Normalizes any accepted format input to its canonical form. */ +export function normalizeSplitFormat(input: SplitFormatInput): SplitFormat { + if (input === OVERRIDES_NAME || (OVERRIDES_NAME_ALTERNATIVES as readonly string[]).includes(input)) { + return OVERRIDES_NAME; + } + return OVERLAY_NAME; +} + +/** + * Derives an output path from a spec file path. + * E.g., `/path/to/openapi.yml` → `/path/to/openapi-overlay.yml` + */ +export function deriveOutputPath(specPath: AbsoluteFilePath, format: SplitFormat = OVERLAY_NAME): AbsoluteFilePath { + const specFilename = getFilename(specPath); + const suffix = format === OVERLAY_NAME ? OVERLAY_NAME : OVERRIDES_NAME; + let derivedFilename = `openapi-${suffix}.yml`; + if (specFilename != null) { + const lastDotIndex = specFilename.lastIndexOf("."); + if (lastDotIndex > 0) { + const nameWithoutExt = specFilename.substring(0, lastDotIndex); + derivedFilename = `${nameWithoutExt}-${suffix}.yml`; + } + } + return join(dirname(specPath), RelativeFilePath.of(derivedFilename)); +} diff --git a/packages/cli/cli-v2/src/commands/api/split/diffSpecs.ts b/packages/cli/cli-v2/src/commands/api/split/diffSpecs.ts new file mode 100644 index 000000000000..7d7cfa560f58 --- /dev/null +++ b/packages/cli/cli-v2/src/commands/api/split/diffSpecs.ts @@ -0,0 +1,360 @@ +import { isDeepStrictEqual } from "node:util"; +import type { Overlay, OverlayAction } from "@fern-api/core-utils"; + +// biome-ignore lint/suspicious/noExplicitAny: OpenAPI specs can have any shape +type OpenAPISpec = Record; + +/** + * A single action in the serialized overlay document. + * Only includes fields that are relevant (no `remove: false` or `description: ""`). + */ +export interface OverlayOutputAction { + target: string; + description?: string; + update?: unknown; + remove?: true; +} + +/** + * An OpenAPI Overlay document produced by split. + */ +export interface OverlayDocument { + overlay: "1.0.0"; + info: { title: string; version: string }; + actions: OverlayOutputAction[]; +} + +/** + * Normalizes an OverlayDocument (with optional fields) to the strict Overlay + * interface expected by `applyOpenAPIOverlay`. + */ +export function toOverlay(doc: OverlayDocument): Overlay { + return { + actions: doc.actions.map((a) => ({ + target: a.target, + description: a.description ?? "", + update: a.update, + remove: a.remove ?? false + })) + }; +} + +/** + * Returns true if the two specs have any differences. + * Cheaper than `generateOverrides` when you only need a boolean answer. + */ +export function hasChanges(original: OpenAPISpec, modified: OpenAPISpec): boolean { + return !isDeepStrictEqual(original, modified); +} + +/** + * Generates an overrides object by comparing original and modified specs. + * Only includes paths/properties that have been added or changed in the modified spec. + */ +export function generateOverrides(original: OpenAPISpec, modified: OpenAPISpec): OpenAPISpec { + const overrides: OpenAPISpec = {}; + + for (const key of Object.keys(modified)) { + const diff = diffObjects(original[key], modified[key]); + if (diff !== undefined) { + overrides[key] = diff; + } + } + + return overrides; +} + +/** + * Recursively diff two objects and return only the differences. + * Returns undefined if there are no differences. + * @internal Exported for testing only. + */ +// biome-ignore lint/suspicious/noExplicitAny: recursive diff needs any +export function diffObjects(original: any, modified: any): any { + if (modified === undefined) { + return undefined; + } + if (original === modified) { + return undefined; + } + if (original === undefined || original === null) { + return modified; + } + + // Primitive types + if (typeof modified !== "object" || modified === null) { + return original !== modified ? modified : undefined; + } + + // Arrays + if (Array.isArray(modified)) { + if (!Array.isArray(original)) { + return modified; + } + + // Arrays of objects with no removals: produce sparse element-wise diff. + // This mirrors how mergeWithOverrides (lodash mergeWith) handles arrays + // of objects — it merges index-by-index, so [{}, {}, {changed: "val"}] + // leaves the first two elements untouched and patches the third. + const allObjects = + modified.length >= original.length && + // biome-ignore lint/suspicious/noExplicitAny: recursive diff needs any + original.every((el: any) => typeof el === "object" && el !== null && !Array.isArray(el)) && + // biome-ignore lint/suspicious/noExplicitAny: recursive diff needs any + modified.every((el: any) => typeof el === "object" && el !== null && !Array.isArray(el)); + + if (allObjects) { + const sparse: unknown[] = []; + let hasDiff = false; + + for (let i = 0; i < modified.length; i++) { + if (i < original.length) { + const elementDiff = diffObjects(original[i], modified[i]); + if (elementDiff !== undefined) { + sparse.push(elementDiff); + hasDiff = true; + } else { + sparse.push({}); + } + } else { + // Appended element + sparse.push(modified[i]); + hasDiff = true; + } + } + + return hasDiff ? sparse : undefined; + } + + // Primitive arrays or arrays that shrank: whole replacement + return !isDeepStrictEqual(original, modified) ? modified : undefined; + } + + // Type mismatch — return full modified value + if (typeof original !== "object" || original === null || Array.isArray(original)) { + return modified; + } + + // Object recursion + const diff: Record = {}; + for (const key of Object.keys(modified)) { + const childDiff = diffObjects(original[key], modified[key]); + if (childDiff !== undefined) { + diff[key] = childDiff; + } + } + + // Deleted keys are not included — overrides only add/modify. + return Object.keys(diff).length === 0 ? undefined : diff; +} + +/** + * Generates an OpenAPI Overlay document by comparing original and modified specs. + * + * Unlike `generateOverrides`, this produces proper overlay actions: + * - Array elements are targeted by index (`$.foo[1]`) instead of sparse padding + * - Deleted keys/elements use `remove: true` actions + * - Appended array elements use the parent array as target (overlay append semantics) + */ +export function generateOverlay(original: OpenAPISpec, modified: OpenAPISpec): OverlayDocument { + const actions: OverlayAction[] = []; + if (!isDeepStrictEqual(original, modified)) { + collectOverlayActions(original, modified, "$", actions); + } + return { + overlay: "1.0.0", + info: { title: "Fern Overlay", version: "0.0.0" }, + actions: actions.map(toOutputAction) + }; +} + +/** + * Converts an internal OverlayAction to a clean serialized form, + * omitting empty descriptions and `remove: false`. + */ +function toOutputAction(action: OverlayAction): OverlayOutputAction { + if (action.remove) { + return { target: action.target, remove: true }; + } + return { target: action.target, update: action.update }; +} + +const SIMPLE_KEY_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/; + +/** + * Recursively walks the diff tree and emits overlay actions. + * + * IMPORTANT: Callers must ensure `original` and `modified` are NOT deep-equal + * before calling this function. This avoids redundant `isDeepStrictEqual` checks + * at every level of recursion. + */ +function collectOverlayActions( + // biome-ignore lint/suspicious/noExplicitAny: recursive diff needs any + original: any, + // biome-ignore lint/suspicious/noExplicitAny: recursive diff needs any + modified: any, + jsonPath: string, + actions: OverlayAction[] +): void { + // Both are arrays — handle element-wise + if (Array.isArray(original) && Array.isArray(modified)) { + collectArrayOverlayActions(original, modified, jsonPath, actions); + return; + } + + // Both are plain objects — recurse into properties + if (isPlainObject(original) && isPlainObject(modified)) { + collectObjectOverlayActions(original, modified, jsonPath, actions); + return; + } + + // Everything else: type mismatch, original missing/null, or new value. + if (modified !== undefined) { + actions.push({ + target: jsonPath, + description: "", + update: modified, + remove: false + }); + } +} + +function collectObjectOverlayActions( + original: Record, + modified: Record, + jsonPath: string, + actions: OverlayAction[] +): void { + const leafUpdate: Record = {}; + let hasLeafChanges = false; + + for (const key of Object.keys(modified)) { + const originalChild = original[key]; + const modifiedChild = modified[key]; + + if (isDeepStrictEqual(originalChild, modifiedChild)) { + continue; + } + + const childPath = `${jsonPath}${escapeJsonPathKey(key)}`; + + // If both are objects (or both arrays), recurse for more granular actions. + // No need to re-check equality — we already know they differ. + if (canRecurse(originalChild, modifiedChild)) { + collectOverlayActions(originalChild, modifiedChild, childPath, actions); + } else { + leafUpdate[key] = modifiedChild; + hasLeafChanges = true; + } + } + + // Deleted keys — emit remove actions + for (const key of Object.keys(original)) { + if (!(key in modified)) { + const childPath = `${jsonPath}${escapeJsonPathKey(key)}`; + actions.push({ + target: childPath, + description: "", + update: null, + remove: true + }); + } + } + + if (hasLeafChanges) { + actions.push({ + target: jsonPath, + description: "", + update: leafUpdate, + remove: false + }); + } +} + +function collectArrayOverlayActions( + original: unknown[], + modified: unknown[], + jsonPath: string, + actions: OverlayAction[] +): void { + // Check if all elements in both arrays are plain objects (for element-wise diffing) + const allObjects = original.every((el) => isPlainObject(el)) && modified.every((el) => isPlainObject(el)); + + if (!allObjects) { + // Primitive or mixed arrays — full replacement. + // Caller already verified arrays are not deep-equal. + actions.push({ + target: jsonPath, + description: "", + update: modified, + remove: false + }); + return; + } + + const commonLength = Math.min(original.length, modified.length); + + // Changed elements — use index targeting and recurse. + // Check equality per-element; only recurse into those that differ. + for (let i = 0; i < commonLength; i++) { + if (!isDeepStrictEqual(original[i], modified[i])) { + collectOverlayActions(original[i], modified[i], `${jsonPath}[${i}]`, actions); + } + } + + // Removed elements — emit remove actions in reverse order so that + // earlier splices don't shift the indices of later removes. + for (let i = original.length - 1; i >= commonLength; i--) { + actions.push({ + target: `${jsonPath}[${i}]`, + description: "", + update: null, + remove: true + }); + } + + // Appended elements — per the overlay spec, targeting an array with a + // non-array update value appends the value to the array. + for (let i = commonLength; i < modified.length; i++) { + actions.push({ + target: jsonPath, + description: "", + update: modified[i], + remove: false + }); + } +} + +/** + * Returns true if both values are recursible (both plain objects or both arrays). + */ +function canRecurse(a: unknown, b: unknown): boolean { + if (a == null || b == null) { + return false; + } + if (typeof a !== "object" || typeof b !== "object") { + return false; + } + if (Array.isArray(a) && Array.isArray(b)) { + return true; + } + if (!Array.isArray(a) && !Array.isArray(b)) { + return true; + } + return false; +} + +function isPlainObject(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +/** + * Escapes a key for use in a JSONPath expression. + * Keys containing special characters (/, ~, ., spaces, etc.) are bracket-quoted. + */ +function escapeJsonPathKey(key: string): string { + if (SIMPLE_KEY_RE.test(key)) { + return `.${key}`; + } + return `['${key.replace(/'/g, "\\'")}']`; +} diff --git a/packages/cli/cli-v2/src/commands/api/split/index.ts b/packages/cli/cli-v2/src/commands/api/split/index.ts new file mode 100644 index 000000000000..498faaa971aa --- /dev/null +++ b/packages/cli/cli-v2/src/commands/api/split/index.ts @@ -0,0 +1 @@ +export { addSplitCommand } from "./command.js"; diff --git a/packages/cli/cli-v2/src/commands/api/utils/__test__/filterSpecs.test.ts b/packages/cli/cli-v2/src/commands/api/utils/__test__/filterSpecs.test.ts new file mode 100644 index 000000000000..7093b9836a9a --- /dev/null +++ b/packages/cli/cli-v2/src/commands/api/utils/__test__/filterSpecs.test.ts @@ -0,0 +1,125 @@ +import { AbsoluteFilePath } from "@fern-api/fs-utils"; +import { describe, expect, it } from "vitest"; +import type { AsyncApiSpec } from "../../../../api/config/AsyncApiSpec.js"; +import type { OpenApiSpec } from "../../../../api/config/OpenApiSpec.js"; +import type { ProtobufSpec } from "../../../../api/config/ProtobufSpec.js"; +import type { Workspace } from "../../../../workspace/Workspace.js"; +import { filterSpecs } from "../filterSpecs.js"; + +function makeWorkspace(apis: Workspace["apis"]): Workspace { + return { + apis, + cliVersion: "0.0.0", + org: "test" + }; +} + +const openapiSpec: OpenApiSpec = { + openapi: AbsoluteFilePath.of("/project/fern/openapi.yml") +}; + +const openapiSpecWithOverrides: OpenApiSpec = { + openapi: AbsoluteFilePath.of("/project/fern/openapi.yml"), + overrides: AbsoluteFilePath.of("/project/fern/overrides.yml") +}; + +const openapiSpecWithMultipleOverrides: OpenApiSpec = { + openapi: AbsoluteFilePath.of("/project/fern/openapi.yml"), + overrides: [ + AbsoluteFilePath.of("/project/fern/overrides-1.yml"), + AbsoluteFilePath.of("/project/fern/overrides-2.yml") + ] +}; + +const asyncapiSpec: AsyncApiSpec = { + asyncapi: AbsoluteFilePath.of("/project/fern/asyncapi.yml"), + overrides: AbsoluteFilePath.of("/project/fern/async-overrides.yml") +}; + +describe("filterSpecs", () => { + it("returns OpenAPI specs from workspace", () => { + const workspace = makeWorkspace({ + api: { specs: [openapiSpecWithOverrides] } + }); + const result = filterSpecs(workspace, {}); + expect(result).toHaveLength(1); + expect(result[0]?.apiName).toBe("api"); + expect(result[0]?.specFilePath).toBe("/project/fern/openapi.yml"); + expect(result[0]?.overrides).toEqual([AbsoluteFilePath.of("/project/fern/overrides.yml")]); + }); + + it("returns AsyncAPI specs from workspace", () => { + const workspace = makeWorkspace({ + api: { specs: [asyncapiSpec] } + }); + const result = filterSpecs(workspace, {}); + expect(result).toHaveLength(1); + expect(result[0]?.specFilePath).toBe("/project/fern/asyncapi.yml"); + }); + + it("skips non-OpenAPI/AsyncAPI specs", () => { + const protoSpec: ProtobufSpec = { + proto: { + root: AbsoluteFilePath.of("/project/fern/proto"), + target: AbsoluteFilePath.of("/project/fern/proto/service.proto") + } + }; + const workspace = makeWorkspace({ + api: { specs: [protoSpec] } + }); + const result = filterSpecs(workspace, {}); + expect(result).toHaveLength(0); + }); + + it("filters by api name", () => { + const workspace = makeWorkspace({ + users: { specs: [openapiSpec] }, + orders: { specs: [asyncapiSpec] } + }); + const result = filterSpecs(workspace, { api: "users" }); + expect(result).toHaveLength(1); + expect(result[0]?.apiName).toBe("users"); + }); + + it("returns undefined overrides when spec has none", () => { + const workspace = makeWorkspace({ + api: { specs: [openapiSpec] } + }); + const result = filterSpecs(workspace, {}); + expect(result[0]?.overrides).toBeUndefined(); + }); + + it("normalizes single override to array", () => { + const workspace = makeWorkspace({ + api: { specs: [openapiSpecWithOverrides] } + }); + const result = filterSpecs(workspace, {}); + expect(result[0]?.overrides).toEqual([AbsoluteFilePath.of("/project/fern/overrides.yml")]); + }); + + it("preserves array of overrides", () => { + const workspace = makeWorkspace({ + api: { specs: [openapiSpecWithMultipleOverrides] } + }); + const result = filterSpecs(workspace, {}); + expect(result[0]?.overrides).toEqual([ + AbsoluteFilePath.of("/project/fern/overrides-1.yml"), + AbsoluteFilePath.of("/project/fern/overrides-2.yml") + ]); + }); + + it("returns entries from multiple APIs", () => { + const workspace = makeWorkspace({ + users: { specs: [openapiSpec] }, + orders: { specs: [asyncapiSpec] } + }); + const result = filterSpecs(workspace, {}); + expect(result).toHaveLength(2); + }); + + it("returns empty array for empty workspace", () => { + const workspace = makeWorkspace({}); + const result = filterSpecs(workspace, {}); + expect(result).toHaveLength(0); + }); +}); diff --git a/packages/cli/cli-v2/src/commands/api/utils/__test__/loadSpec.test.ts b/packages/cli/cli-v2/src/commands/api/utils/__test__/loadSpec.test.ts new file mode 100644 index 000000000000..0472bd3e9079 --- /dev/null +++ b/packages/cli/cli-v2/src/commands/api/utils/__test__/loadSpec.test.ts @@ -0,0 +1,66 @@ +import yaml from "js-yaml"; +import { describe, expect, it } from "vitest"; +import { isJsonFile, parseSpec, serializeSpec } from "../loadSpec.js"; + +describe("isJsonFile", () => { + it("returns true for .json files", () => { + expect(isJsonFile("openapi.json")).toBe(true); + expect(isJsonFile("/path/to/spec.json")).toBe(true); + }); + + it("returns false for .yml and .yaml files", () => { + expect(isJsonFile("openapi.yml")).toBe(false); + expect(isJsonFile("openapi.yaml")).toBe(false); + expect(isJsonFile("/path/to/spec.yml")).toBe(false); + }); +}); + +describe("serializeSpec", () => { + const spec = { openapi: "3.0.0", info: { title: "Test", version: "1.0" } }; + + it("serializes as JSON for .json files", () => { + const result = serializeSpec(spec, "openapi.json"); + expect(result).toBe(JSON.stringify(spec, null, 2) + "\n"); + expect(() => JSON.parse(result)).not.toThrow(); + }); + + it("serializes as YAML for .yml files", () => { + const result = serializeSpec(spec, "openapi.yml"); + expect(() => JSON.parse(result)).toThrow(); + expect(yaml.load(result)).toEqual(spec); + }); + + it("serializes as YAML for .yaml files", () => { + const result = serializeSpec(spec, "openapi.yaml"); + expect(() => JSON.parse(result)).toThrow(); + expect(yaml.load(result)).toEqual(spec); + }); + + it("round-trips through JSON serialization", () => { + const result = serializeSpec(spec, "spec.json"); + const parsed = JSON.parse(result); + expect(parsed).toEqual(spec); + }); + + it("round-trips through YAML serialization", () => { + const result = serializeSpec(spec, "spec.yml"); + const parsed = yaml.load(result); + expect(parsed).toEqual(spec); + }); +}); + +describe("parseSpec", () => { + it("parses valid JSON", () => { + const result = parseSpec('{"openapi":"3.0.0"}', "test.json"); + expect(result).toEqual({ openapi: "3.0.0" }); + }); + + it("parses valid YAML", () => { + const result = parseSpec("openapi: '3.0.0'\n", "test.yml"); + expect(result).toEqual({ openapi: "3.0.0" }); + }); + + it("throws on invalid content", () => { + expect(() => parseSpec("{{invalid", "test.yml")).toThrow("Failed to parse"); + }); +}); diff --git a/packages/cli/cli-v2/src/commands/api/utils/filterSpecs.ts b/packages/cli/cli-v2/src/commands/api/utils/filterSpecs.ts new file mode 100644 index 000000000000..ab7d4abb2b48 --- /dev/null +++ b/packages/cli/cli-v2/src/commands/api/utils/filterSpecs.ts @@ -0,0 +1,53 @@ +import type { AbsoluteFilePath } from "@fern-api/fs-utils"; +import type { ApiSpec } from "../../../api/config/ApiSpec.js"; +import { isAsyncApiSpec } from "../../../api/config/AsyncApiSpec.js"; +import { isOpenApiSpec } from "../../../api/config/OpenApiSpec.js"; +import type { Workspace } from "../../../workspace/Workspace.js"; + +export interface SpecEntry { + apiName: string; + spec: ApiSpec; + /** Absolute path to the spec file */ + specFilePath: AbsoluteFilePath; + /** Absolute path(s) to override files, if any */ + overrides: AbsoluteFilePath[] | undefined; + /** Absolute path to the overlay file, if any */ + overlays: AbsoluteFilePath | undefined; +} + +/** + * Returns specs from the workspace that support overrides (OpenAPI, AsyncAPI), + * optionally filtered by API name. + */ +export function filterSpecs(workspace: Workspace, options: { api?: string }): SpecEntry[] { + const results: SpecEntry[] = []; + + for (const [apiName, apiDef] of Object.entries(workspace.apis)) { + if (options.api != null && apiName !== options.api) { + continue; + } + + for (const apiSpec of apiDef.specs) { + let specFilePath: AbsoluteFilePath | undefined; + let overrides: AbsoluteFilePath | AbsoluteFilePath[] | undefined; + let overlays: AbsoluteFilePath | undefined; + + if (isOpenApiSpec(apiSpec)) { + specFilePath = apiSpec.openapi; + overrides = apiSpec.overrides; + overlays = apiSpec.overlays; + } else if (isAsyncApiSpec(apiSpec)) { + specFilePath = apiSpec.asyncapi; + overrides = apiSpec.overrides; + } else { + continue; + } + + const overridesList = overrides == null ? undefined : Array.isArray(overrides) ? overrides : [overrides]; + + results.push({ apiName, spec: apiSpec, specFilePath, overrides: overridesList, overlays }); + } + } + + return results; +} diff --git a/packages/cli/cli-v2/src/commands/api/utils/isEnoentError.ts b/packages/cli/cli-v2/src/commands/api/utils/isEnoentError.ts new file mode 100644 index 000000000000..2004b9e3af01 --- /dev/null +++ b/packages/cli/cli-v2/src/commands/api/utils/isEnoentError.ts @@ -0,0 +1,11 @@ +/** Narrows an unknown caught value to a Node.js system error with an error code. */ +function isNodeError(error: unknown): error is NodeJS.ErrnoException { + return error instanceof Error && "code" in error; +} + +/** + * Returns true if the given error is a Node.js filesystem error with code ENOENT (file not found). + */ +export function isEnoentError(error: unknown): boolean { + return isNodeError(error) && error.code === "ENOENT"; +} diff --git a/packages/cli/cli-v2/src/commands/api/utils/loadSpec.ts b/packages/cli/cli-v2/src/commands/api/utils/loadSpec.ts new file mode 100644 index 000000000000..52b6d17c48bb --- /dev/null +++ b/packages/cli/cli-v2/src/commands/api/utils/loadSpec.ts @@ -0,0 +1,59 @@ +import type { AbsoluteFilePath } from "@fern-api/fs-utils"; +import { readFile } from "fs/promises"; +import yaml from "js-yaml"; +import { CliError } from "../../../errors/CliError.js"; +import { isEnoentError } from "./isEnoentError.js"; + +// biome-ignore lint/suspicious/noExplicitAny: OpenAPI specs can have any shape +type Spec = Record; + +/** + * Reads and parses a JSON or YAML spec file. + * Tries JSON first, then YAML. Throws a CliError with a clear message on failure. + */ +export async function loadSpec(filepath: AbsoluteFilePath): Promise { + let contents: string; + try { + contents = await readFile(filepath, "utf8"); + } catch (error) { + if (isEnoentError(error)) { + throw new CliError({ message: `File does not exist: ${filepath}` }); + } + throw new CliError({ message: `Failed to read file: ${filepath}` }); + } + return parseSpec(contents, filepath); +} + +/** + * Parses a string as JSON or YAML. + * Tries JSON first, then YAML. Throws a CliError on failure. + */ +export function parseSpec(contents: string, filepath: string): Spec { + try { + return JSON.parse(contents); + } catch { + try { + return yaml.load(contents) as Spec; + } catch { + throw new CliError({ message: `Failed to parse file as JSON or YAML: ${filepath}` }); + } + } +} + +/** + * Returns true if the filepath has a JSON extension (.json). + */ +export function isJsonFile(filepath: string): boolean { + return filepath.endsWith(".json"); +} + +/** + * Serializes a spec to a string in the format matching the given filepath's extension. + * JSON files are written as pretty-printed JSON; all others as YAML. + */ +export function serializeSpec(data: Spec, filepath: string): string { + if (isJsonFile(filepath)) { + return JSON.stringify(data, null, 2) + "\n"; + } + return yaml.dump(data, { lineWidth: -1, noRefs: true }); +} diff --git a/packages/cli/cli-v2/src/commands/auth/login/command.ts b/packages/cli/cli-v2/src/commands/auth/login/command.ts index b0eae67a9085..18696780acc9 100644 --- a/packages/cli/cli-v2/src/commands/auth/login/command.ts +++ b/packages/cli/cli-v2/src/commands/auth/login/command.ts @@ -67,7 +67,7 @@ export class LoginCommand { } } -export function addLoginCommand(cli: Argv, parentPath?: string): void { +export function addLoginCommand(cli: Argv): void { const cmd = new LoginCommand(); command( cli, @@ -82,7 +82,6 @@ export function addLoginCommand(cli: Argv, parentPath?: string): voi description: "Use device code flow (for environments where browser cannot open automatically)" }) .example("$0 auth login", "Log in via browser") - .example("$0 auth login --device-code", "Log in via device code"), - parentPath + .example("$0 auth login --device-code", "Log in via device code") ); } diff --git a/packages/cli/cli-v2/src/commands/auth/logout/command.ts b/packages/cli/cli-v2/src/commands/auth/logout/command.ts index b4de2be0ba39..dec455fb6dc3 100644 --- a/packages/cli/cli-v2/src/commands/auth/logout/command.ts +++ b/packages/cli/cli-v2/src/commands/auth/logout/command.ts @@ -127,7 +127,7 @@ export class LogoutCommand { } } -export function addLogoutCommand(cli: Argv, parentPath?: string): void { +export function addLogoutCommand(cli: Argv): void { const cmd = new LogoutCommand(); command( cli, @@ -149,7 +149,6 @@ export function addLogoutCommand(cli: Argv, parentPath?: string): vo type: "boolean", default: false, description: "Skip confirmation prompt" - }), - parentPath + }) ); } diff --git a/packages/cli/cli-v2/src/commands/auth/status/command.ts b/packages/cli/cli-v2/src/commands/auth/status/command.ts index 1d9e5434f9c7..772c0aee6afc 100644 --- a/packages/cli/cli-v2/src/commands/auth/status/command.ts +++ b/packages/cli/cli-v2/src/commands/auth/status/command.ts @@ -133,7 +133,7 @@ export class StatusCommand { } } -export function addStatusCommand(cli: Argv, parentPath?: string): void { +export function addStatusCommand(cli: Argv): void { const cmd = new StatusCommand(); command( cli, @@ -151,7 +151,6 @@ export function addStatusCommand(cli: Argv, parentPath?: string): vo type: "boolean", default: false, description: "Show active account only" - }), - parentPath + }) ); } diff --git a/packages/cli/cli-v2/src/commands/auth/switch/command.ts b/packages/cli/cli-v2/src/commands/auth/switch/command.ts index 534e97c59205..8368add4ed85 100644 --- a/packages/cli/cli-v2/src/commands/auth/switch/command.ts +++ b/packages/cli/cli-v2/src/commands/auth/switch/command.ts @@ -91,7 +91,7 @@ export class SwitchCommand { } } -export function addSwitchCommand(cli: Argv, parentPath?: string): void { +export function addSwitchCommand(cli: Argv): void { const cmd = new SwitchCommand(); command( cli, @@ -102,7 +102,6 @@ export function addSwitchCommand(cli: Argv, parentPath?: string): vo yargs.option("user", { type: "string", description: "Switch to specific account" - }), - parentPath + }) ); } diff --git a/packages/cli/cli-v2/src/commands/auth/token/command.ts b/packages/cli/cli-v2/src/commands/auth/token/command.ts index 9f0b7f5ec267..2c5ad6d46b98 100644 --- a/packages/cli/cli-v2/src/commands/auth/token/command.ts +++ b/packages/cli/cli-v2/src/commands/auth/token/command.ts @@ -76,7 +76,7 @@ export class TokenCommand { } } -export function addTokenCommand(cli: Argv, parentPath?: string): void { +export function addTokenCommand(cli: Argv): void { const cmd = new TokenCommand(); command( cli, @@ -91,7 +91,6 @@ export function addTokenCommand(cli: Argv, parentPath?: string): voi }) .example("$0 auth token", "Generate token for workspace organization") .example("$0 auth token --org acme", "Generate token for specific organization") - .example("export FERN_TOKEN=$($0 auth token)", "Export token to variable"), - parentPath + .example("export FERN_TOKEN=$($0 auth token)", "Export token to variable") ); } diff --git a/packages/cli/cli-v2/src/commands/auth/whoami/command.ts b/packages/cli/cli-v2/src/commands/auth/whoami/command.ts index e3b09f7c2753..c19642f1db46 100644 --- a/packages/cli/cli-v2/src/commands/auth/whoami/command.ts +++ b/packages/cli/cli-v2/src/commands/auth/whoami/command.ts @@ -30,7 +30,7 @@ export class WhoamiCommand { } } -export function addWhoamiCommand(cli: Argv, parentPath?: string): void { +export function addWhoamiCommand(cli: Argv): void { const cmd = new WhoamiCommand(); command( cli, @@ -42,7 +42,6 @@ export function addWhoamiCommand(cli: Argv, parentPath?: string): vo type: "boolean", default: false, description: "Output as JSON" - }), - parentPath + }) ); } diff --git a/packages/cli/cli-v2/src/commands/cache/clear/command.ts b/packages/cli/cli-v2/src/commands/cache/clear/command.ts index 5740bba9e619..bdcffe1f105a 100644 --- a/packages/cli/cli-v2/src/commands/cache/clear/command.ts +++ b/packages/cli/cli-v2/src/commands/cache/clear/command.ts @@ -48,7 +48,7 @@ export class ClearCommand { } } -export function addClearCommand(cli: Argv, parentPath?: string): void { +export function addClearCommand(cli: Argv): void { const cmd = new ClearCommand(); command( cli, @@ -76,7 +76,6 @@ export function addClearCommand(cli: Argv, parentPath?: string): voi type: "boolean", description: "Preview what would be cleared without deleting", default: false - }), - parentPath + }) ); } diff --git a/packages/cli/cli-v2/src/commands/cache/show/command.ts b/packages/cli/cli-v2/src/commands/cache/show/command.ts index 9dd671172249..3e300ccc9be8 100644 --- a/packages/cli/cli-v2/src/commands/cache/show/command.ts +++ b/packages/cli/cli-v2/src/commands/cache/show/command.ts @@ -95,7 +95,7 @@ export class ShowCommand { } } -export function addShowCommand(cli: Argv, parentPath?: string): void { +export function addShowCommand(cli: Argv): void { const cmd = new ShowCommand(); command( cli, @@ -107,7 +107,6 @@ export function addShowCommand(cli: Argv, parentPath?: string): void type: "boolean", description: "Output in JSON format", default: false - }), - parentPath + }) ); } diff --git a/packages/cli/cli-v2/src/commands/config/migrate/command.ts b/packages/cli/cli-v2/src/commands/config/migrate/command.ts index 92e4ffb76a1b..5eef759bec0d 100644 --- a/packages/cli/cli-v2/src/commands/config/migrate/command.ts +++ b/packages/cli/cli-v2/src/commands/config/migrate/command.ts @@ -54,7 +54,7 @@ export class MigrateCommand { } } -export function addMigrateCommand(cli: Argv, parentPath?: string): void { +export function addMigrateCommand(cli: Argv): void { const cmd = new MigrateCommand(); command( cli, @@ -66,7 +66,6 @@ export function addMigrateCommand(cli: Argv, parentPath?: string): v type: "boolean", description: "Keep original files after migration", default: true - }), - parentPath + }) ); } diff --git a/packages/cli/cli-v2/src/commands/docs/check/command.ts b/packages/cli/cli-v2/src/commands/docs/check/command.ts index 0b93a47bbb37..b6fdaa39440d 100644 --- a/packages/cli/cli-v2/src/commands/docs/check/command.ts +++ b/packages/cli/cli-v2/src/commands/docs/check/command.ts @@ -81,7 +81,7 @@ export class CheckCommand { } } -export function addCheckCommand(cli: Argv, parentPath?: string): void { +export function addCheckCommand(cli: Argv): void { const cmd = new CheckCommand(); command( cli, @@ -99,7 +99,6 @@ export function addCheckCommand(cli: Argv, parentPath?: string): voi type: "boolean", description: "Output results as JSON to stdout", default: false - }), - parentPath + }) ); } diff --git a/packages/cli/cli-v2/src/commands/docs/dev/command.ts b/packages/cli/cli-v2/src/commands/docs/dev/command.ts index 8e199c641a20..8720d9a47b8a 100644 --- a/packages/cli/cli-v2/src/commands/docs/dev/command.ts +++ b/packages/cli/cli-v2/src/commands/docs/dev/command.ts @@ -41,7 +41,7 @@ export class DevCommand { } } -export function addDevCommand(cli: Argv, parentPath?: string): void { +export function addDevCommand(cli: Argv): void { const cmd = new DevCommand(); command( cli, @@ -67,7 +67,6 @@ export function addDevCommand(cli: Argv, parentPath?: string): void .option("bundle-path", { type: "string", description: "Path to a custom preview bundle" - }), - parentPath + }) ); } diff --git a/packages/cli/cli-v2/src/commands/docs/preview/command.ts b/packages/cli/cli-v2/src/commands/docs/preview/command.ts index ee9164816ac7..6815bf250c17 100644 --- a/packages/cli/cli-v2/src/commands/docs/preview/command.ts +++ b/packages/cli/cli-v2/src/commands/docs/preview/command.ts @@ -27,7 +27,7 @@ export class PreviewCommand { } } -export function addPreviewCommand(cli: Argv, parentPath?: string): void { +export function addPreviewCommand(cli: Argv): void { const cmd = new PreviewCommand(); commandWithSubcommands( cli, diff --git a/packages/cli/cli-v2/src/commands/docs/preview/delete/command.ts b/packages/cli/cli-v2/src/commands/docs/preview/delete/command.ts index db1c64c28aa5..58079f7ab3d3 100644 --- a/packages/cli/cli-v2/src/commands/docs/preview/delete/command.ts +++ b/packages/cli/cli-v2/src/commands/docs/preview/delete/command.ts @@ -70,7 +70,7 @@ export class DeleteCommand { } } -export function addDeleteCommand(cli: Argv, parentPath?: string): void { +export function addDeleteCommand(cli: Argv): void { const cmd = new DeleteCommand(); command( cli, @@ -85,7 +85,6 @@ export function addDeleteCommand(cli: Argv, parentPath?: string): vo description: "The FQDN of the preview deployment to delete (e.g. acme-preview-abc123.docs.buildwithfern.com)", demandOption: true - }), - parentPath + }) ); } diff --git a/packages/cli/cli-v2/src/commands/docs/publish/command.ts b/packages/cli/cli-v2/src/commands/docs/publish/command.ts index 76fd022d158f..e0182f2dd748 100644 --- a/packages/cli/cli-v2/src/commands/docs/publish/command.ts +++ b/packages/cli/cli-v2/src/commands/docs/publish/command.ts @@ -255,7 +255,7 @@ export class PublishCommand { } } -export function addPublishCommand(cli: Argv, parentPath?: string): void { +export function addPublishCommand(cli: Argv): void { const cmd = new PublishCommand(); command( cli, @@ -291,7 +291,6 @@ export function addPublishCommand(cli: Argv, parentPath?: string): v default: false, description: "Generate a preview link instead of publishing to production", hidden: true - }), - parentPath + }) ); } diff --git a/packages/cli/cli-v2/src/commands/org/create/command.ts b/packages/cli/cli-v2/src/commands/org/create/command.ts index 45657db2b249..8f851dde0d48 100644 --- a/packages/cli/cli-v2/src/commands/org/create/command.ts +++ b/packages/cli/cli-v2/src/commands/org/create/command.ts @@ -50,7 +50,7 @@ export class CreateCommand { } } -export function addCreateCommand(cli: Argv, parentPath?: string): void { +export function addCreateCommand(cli: Argv): void { const cmd = new CreateCommand(); command( cli, @@ -65,7 +65,6 @@ export function addCreateCommand(cli: Argv, parentPath?: string): vo demandOption: true, description: "Organization name" }) - .example("$0 org create acme", "# Create the 'acme' organization"), - parentPath + .example("$0 org create acme", "# Create the 'acme' organization") ); } diff --git a/packages/cli/cli-v2/src/commands/org/list/command.ts b/packages/cli/cli-v2/src/commands/org/list/command.ts index 596a1e29b458..a14e96c4af20 100644 --- a/packages/cli/cli-v2/src/commands/org/list/command.ts +++ b/packages/cli/cli-v2/src/commands/org/list/command.ts @@ -174,7 +174,7 @@ export class ListCommand { } } -export function addListCommand(cli: Argv, parentPath?: string): void { +export function addListCommand(cli: Argv): void { const cmd = new ListCommand(); - command(cli, "list", "List your organizations", (context) => cmd.handle(context), undefined, parentPath); + command(cli, "list", "List your organizations", (context) => cmd.handle(context)); } diff --git a/packages/cli/cli-v2/src/commands/sdk/add/command.ts b/packages/cli/cli-v2/src/commands/sdk/add/command.ts index 6c1a9e75a12e..1644f73413d9 100644 --- a/packages/cli/cli-v2/src/commands/sdk/add/command.ts +++ b/packages/cli/cli-v2/src/commands/sdk/add/command.ts @@ -208,7 +208,7 @@ export class AddCommand { newTarget.group = [group]; } - editor.addTarget(language, newTarget); + await editor.addTarget(language, newTarget); await editor.save(); } @@ -252,7 +252,7 @@ export class AddCommand { } } -export function addAddCommand(cli: Argv, parentPath?: string): void { +export function addAddCommand(cli: Argv): void { const cmd = new AddCommand(); command( cli, @@ -282,7 +282,6 @@ export function addAddCommand(cli: Argv, parentPath?: string): void type: "boolean", description: "Accept all defaults (non-interactive mode)", default: false - }), - parentPath + }) ); } diff --git a/packages/cli/cli-v2/src/commands/sdk/check/command.ts b/packages/cli/cli-v2/src/commands/sdk/check/command.ts index 583a43b65aac..4922e21d15b3 100644 --- a/packages/cli/cli-v2/src/commands/sdk/check/command.ts +++ b/packages/cli/cli-v2/src/commands/sdk/check/command.ts @@ -75,7 +75,7 @@ export class CheckCommand { } } -export function addCheckCommand(cli: Argv, parentPath?: string): void { +export function addCheckCommand(cli: Argv): void { const cmd = new CheckCommand(); command( cli, @@ -93,7 +93,6 @@ export function addCheckCommand(cli: Argv, parentPath?: string): voi type: "boolean", description: "Output results as JSON to stdout", default: false - }), - parentPath + }) ); } diff --git a/packages/cli/cli-v2/src/commands/sdk/generate/command.ts b/packages/cli/cli-v2/src/commands/sdk/generate/command.ts index 6d9f6641ce76..573cfc476127 100644 --- a/packages/cli/cli-v2/src/commands/sdk/generate/command.ts +++ b/packages/cli/cli-v2/src/commands/sdk/generate/command.ts @@ -587,7 +587,7 @@ export class GenerateCommand { } } -export function addGenerateCommand(cli: Argv, parentPath?: string): void { +export function addGenerateCommand(cli: Argv): void { const cmd = new GenerateCommand(); command( cli, @@ -668,7 +668,6 @@ export function addGenerateCommand(cli: Argv, parentPath?: string): type: "string", description: "Path to .fernignore file", hidden: true - }), - parentPath + }) ); } diff --git a/packages/cli/cli-v2/src/commands/sdk/preview/command.ts b/packages/cli/cli-v2/src/commands/sdk/preview/command.ts index be7a45e690e1..7dcfd9c8586f 100644 --- a/packages/cli/cli-v2/src/commands/sdk/preview/command.ts +++ b/packages/cli/cli-v2/src/commands/sdk/preview/command.ts @@ -21,7 +21,7 @@ export class PreviewCommand { } } -export function addPreviewCommand(cli: Argv, parentPath?: string): void { +export function addPreviewCommand(cli: Argv): void { const cmd = new PreviewCommand(); command( cli, @@ -96,7 +96,6 @@ export function addPreviewCommand(cli: Argv, parentPath?: string): v type: "string", description: "Path to .fernignore file", hidden: true - }), - parentPath + }) ); } diff --git a/packages/cli/cli-v2/src/commands/sdk/update/command.ts b/packages/cli/cli-v2/src/commands/sdk/update/command.ts index 649d45ad493c..e266b5ebfc24 100644 --- a/packages/cli/cli-v2/src/commands/sdk/update/command.ts +++ b/packages/cli/cli-v2/src/commands/sdk/update/command.ts @@ -89,7 +89,7 @@ export class UpdateCommand { }); const migrationInfo = new Map(); for (const update of selectedUpdates) { - editor.setTargetVersion(update.name, update.latestVersion); + await editor.setTargetVersion(update.name, update.latestVersion); // Run generator config migrations for this version upgrade. const target = targets.find((t) => t.name === update.name); @@ -219,7 +219,7 @@ export class UpdateCommand { } } -export function addUpdateCommand(cli: Argv, parentPath?: string): void { +export function addUpdateCommand(cli: Argv): void { const cmd = new UpdateCommand(); command( cli, @@ -246,7 +246,6 @@ export function addUpdateCommand(cli: Argv, parentPath?: string): vo type: "boolean", description: "Accept all defaults (non-interactive mode)", default: false - }), - parentPath + }) ); } diff --git a/packages/cli/cli-v2/src/commands/telemetry/disable/command.ts b/packages/cli/cli-v2/src/commands/telemetry/disable/command.ts index e0a3b9b3831c..f0c585202f9e 100644 --- a/packages/cli/cli-v2/src/commands/telemetry/disable/command.ts +++ b/packages/cli/cli-v2/src/commands/telemetry/disable/command.ts @@ -16,7 +16,7 @@ export class DisableCommand { } } -export function addDisableCommand(cli: Argv, parentPath?: string): void { +export function addDisableCommand(cli: Argv): void { const cmd = new DisableCommand(); - command(cli, "disable", "Disable telemetry", (context, args) => cmd.handle(context, args), undefined, parentPath); + command(cli, "disable", "Disable telemetry", (context, args) => cmd.handle(context, args)); } diff --git a/packages/cli/cli-v2/src/commands/telemetry/enable/command.ts b/packages/cli/cli-v2/src/commands/telemetry/enable/command.ts index 562d4486df39..6c4644f234e2 100644 --- a/packages/cli/cli-v2/src/commands/telemetry/enable/command.ts +++ b/packages/cli/cli-v2/src/commands/telemetry/enable/command.ts @@ -21,7 +21,7 @@ export class EnableCommand { } } -export function addEnableCommand(cli: Argv, parentPath?: string): void { +export function addEnableCommand(cli: Argv): void { const cmd = new EnableCommand(); - command(cli, "enable", "Enable telemetry", (context, args) => cmd.handle(context, args), undefined, parentPath); + command(cli, "enable", "Enable telemetry", (context, args) => cmd.handle(context, args)); } diff --git a/packages/cli/cli-v2/src/commands/telemetry/status/command.ts b/packages/cli/cli-v2/src/commands/telemetry/status/command.ts index 84eadaad9a0b..7273b37e39f6 100644 --- a/packages/cli/cli-v2/src/commands/telemetry/status/command.ts +++ b/packages/cli/cli-v2/src/commands/telemetry/status/command.ts @@ -54,14 +54,13 @@ export class StatusCommand { } } -export function addStatusCommand(cli: Argv, parentPath?: string): void { +export function addStatusCommand(cli: Argv): void { const cmd = new StatusCommand(); command( cli, "status", "Show telemetry status", (context, args) => cmd.handle(context, args), - (yargs) => yargs.option("json", { type: "boolean", default: false, description: "Output status as JSON" }), - parentPath + (yargs) => yargs.option("json", { type: "boolean", default: false, description: "Output status as JSON" }) ); } diff --git a/packages/cli/cli-v2/src/config/fern-yml/FernYmlEditor.ts b/packages/cli/cli-v2/src/config/fern-yml/FernYmlEditor.ts index 87437d1507eb..b71a8a633162 100644 --- a/packages/cli/cli-v2/src/config/fern-yml/FernYmlEditor.ts +++ b/packages/cli/cli-v2/src/config/fern-yml/FernYmlEditor.ts @@ -1,10 +1,29 @@ import type { schemas } from "@fern-api/config"; import { AbsoluteFilePath, dirname, doesPathExist, join, RelativeFilePath } from "@fern-api/fs-utils"; import { readFile, writeFile } from "fs/promises"; +import path from "path"; import { type Document, parseDocument } from "yaml"; import { CliError } from "../../errors/CliError.js"; import { FERN_YML_FILENAME, REF_KEY } from "./constants.js"; +export interface OverrideEdit { + /** 1-based line number where the edit occurred */ + line: number; +} + +export interface RemoveOverridesResult extends OverrideEdit { + removed: string[]; +} + +/** + * A resolved section of fern.yml that may live in a `$ref` file. + */ +interface ResolvedSection { + document: Document; + filePath: AbsoluteFilePath; + basePath: (string | number)[]; +} + export namespace FernYmlEditor { export interface Config { /** Path to the fern.yml file. */ @@ -23,33 +42,35 @@ export namespace FernYmlEditor { } /** - * Stateful editor for mutating fern.yml target configuration. + * Stateful editor for mutating fern.yml configuration. + * + * Supports editing both the `sdks` section (targets) and the `api` section + * (spec overrides/overlays). Sections are resolved lazily — if a section + * uses a `$ref`, the referenced file is loaded on first access. */ export class FernYmlEditor { - private readonly document: Document; - private readonly filePath: AbsoluteFilePath; - private readonly targetsPath: string[]; - - private constructor({ - document, - filePath, - targetsPath - }: { - document: Document; - filePath: AbsoluteFilePath; - targetsPath: string[]; - }) { - this.document = document; - this.filePath = filePath; - this.targetsPath = targetsPath; + private readonly rootDocument: Document; + private readonly rootFilePath: AbsoluteFilePath; + private readonly rootDoc: Record; + + /** Lazily resolved sections, keyed by top-level YAML key (e.g. "sdks", "api"). */ + private readonly sections = new Map(); + + /** Tracks which file paths have been mutated and need writing. */ + private readonly dirtyFiles = new Set(); + + /** Caches serialization for line-number lookups; invalidated on mutation. */ + private readonly cachedSerialization = new Map(); + + private constructor(rootDocument: Document, rootFilePath: AbsoluteFilePath) { + this.rootDocument = rootDocument; + this.rootFilePath = rootFilePath; + this.rootDoc = rootDocument.toJS() as Record; } /** - * Loads the YAML document that contains the SDK targets. - * - * If `sdks` in fern.yml uses a `$ref`, the referenced file is loaded - * instead. The resolved file path and targets path are determined once - * at load time. + * Loads fern.yml from the given path. + * Sections (`sdks`, `api`) are resolved lazily on first method call. */ public static async load(config: FernYmlEditor.Config): Promise { const content = await readFile(config.fernYmlPath, "utf-8"); @@ -62,68 +83,267 @@ export class FernYmlEditor { }); } - // Check if sdks uses a $ref. - const sdksValue = doc.sdks; - if (sdksValue != null && typeof sdksValue === "object" && REF_KEY in sdksValue) { - const refPath = (sdksValue as Record)[REF_KEY]; - if (typeof refPath === "string") { - const resolvedPath = join(dirname(config.fernYmlPath), RelativeFilePath.of(refPath)); - if (!(await doesPathExist(resolvedPath))) { - throw new CliError({ - message: `Referenced file '${refPath}' in ${FERN_YML_FILENAME} does not exist.` - }); - } - const refContent = await readFile(resolvedPath, "utf-8"); - const refDocument = parseDocument(refContent); - return new FernYmlEditor({ - document: refDocument, - filePath: resolvedPath, - targetsPath: ["targets"] - }); - } - } + return new FernYmlEditor(document, config.fernYmlPath); + } - return new FernYmlEditor({ - document, - filePath: config.fernYmlPath, - targetsPath: ["sdks", "targets"] - }); + /** The file path that contains the API specs section (after `$ref` resolution). */ + public async getApiFilePath(): Promise { + const section = await this.resolveSection("api", ["specs"]); + return section.filePath; } + // ─── SDK target methods ────────────────────────────────────────────── + /** * Adds a new target entry. Creates intermediate maps (sdks, targets) * if they don't exist. */ - public addTarget(name: string, value: FernYmlEditor.TargetSchema): void { - this.ensureMapPath(this.targetsPath); - this.document.setIn([...this.targetsPath, name], this.document.createNode(value)); + public async addTarget(name: string, value: FernYmlEditor.TargetSchema): Promise { + const { document, basePath, filePath } = await this.resolveSection("sdks", ["targets"]); + this.ensureMapPath(document, basePath); + document.setIn([...basePath, name], document.createNode(value)); + this.markDirty(filePath); } /** Sets the version field for an existing target. */ - public setTargetVersion(name: string, version: string): void { - this.assertTargetExists(name); - this.document.setIn([...this.targetsPath, name, "version"], version); + public async setTargetVersion(name: string, version: string): Promise { + const section = await this.resolveSection("sdks", ["targets"]); + this.assertTargetExists(section, name); + section.document.setIn([...section.basePath, name, "version"], version); + this.markDirty(section.filePath); } /** Sets the generator-specific config for an existing target. */ - public setTargetConfig(name: string, config: Record): void { - this.assertTargetExists(name); - this.document.setIn([...this.targetsPath, name, "config"], this.document.createNode(config)); + public async setTargetConfig(name: string, config: Record): Promise { + const section = await this.resolveSection("sdks", ["targets"]); + this.assertTargetExists(section, name); + section.document.setIn([...section.basePath, name, "config"], section.document.createNode(config)); + this.markDirty(section.filePath); } /** Removes the generator-specific config from an existing target. */ - public deleteTargetConfig(name: string): void { - this.assertTargetExists(name); - this.document.deleteIn([...this.targetsPath, name, "config"]); + public async deleteTargetConfig(name: string): Promise { + const section = await this.resolveSection("sdks", ["targets"]); + this.assertTargetExists(section, name); + section.document.deleteIn([...section.basePath, name, "config"]); + this.markDirty(section.filePath); + } + + // ─── API spec methods ──────────────────────────────────────────────── + + /** + * Adds an override path to the matching spec entry. + * Returns the edit location if a mutation was made, or undefined if no change. + */ + public async addOverride( + specFilePath: AbsoluteFilePath, + overrideAbsolutePath: AbsoluteFilePath + ): Promise { + const section = await this.resolveSection("api", ["specs"]); + const fileDir = dirname(section.filePath); + const relativeOverridePath = `./${path.relative(fileDir, overrideAbsolutePath)}`; + const index = this.findSpecIndex(section, specFilePath); + if (index < 0) { + return undefined; + } + + const overridesPath = [...section.basePath, index, "overrides"]; + const existing = nodeToJS(section.document.getIn(overridesPath)); + const normalizedNew = path.normalize(relativeOverridePath); + + if (existing == null) { + section.document.setIn(overridesPath, relativeOverridePath); + } else if (typeof existing === "string") { + if (path.normalize(existing) === normalizedNew) { + return undefined; + } + section.document.setIn(overridesPath, section.document.createNode([existing, relativeOverridePath])); + } else if (Array.isArray(existing)) { + if (existing.some((p: string) => path.normalize(p) === normalizedNew)) { + return undefined; + } + const updated = [...existing, relativeOverridePath]; + section.document.setIn(overridesPath, section.document.createNode(updated)); + } + + this.markDirty(section.filePath); + return { line: this.getLineOfPath(section, overridesPath) }; + } + + /** + * Removes all override references for the matching spec. + * Returns the removed paths and the edit location, or undefined if nothing was removed. + */ + public async removeOverrides(specFilePath: AbsoluteFilePath): Promise { + const section = await this.resolveSection("api", ["specs"]); + const index = this.findSpecIndex(section, specFilePath); + if (index < 0) { + return undefined; + } + + const overridesPath = [...section.basePath, index, "overrides"]; + const existing = nodeToJS(section.document.getIn(overridesPath)); + if (existing == null) { + return undefined; + } + const line = this.getLineOfPath(section, overridesPath); + const removed: string[] = typeof existing === "string" ? [existing] : [...(existing as string[])]; + section.document.deleteIn(overridesPath); + this.markDirty(section.filePath); + return { removed, line }; + } + + /** + * Returns the existing overlay path for the matching spec entry, or undefined. + */ + public async getOverlayPath(specFilePath: AbsoluteFilePath): Promise { + const section = await this.resolveSection("api", ["specs"]); + const index = this.findSpecIndex(section, specFilePath); + if (index < 0) { + return undefined; + } + const overlaysPath = [...section.basePath, index, "overlays"]; + const existing = nodeToJS(section.document.getIn(overlaysPath)); + if (typeof existing === "string") { + const fileDir = dirname(section.filePath); + return join(fileDir, RelativeFilePath.of(existing)); + } + return undefined; + } + + /** + * Adds an overlay path to the matching spec entry. + * Unlike overrides, overlays is a single path (not an array). + * Returns the edit location if a mutation was made, or undefined if no change. + * + * TODO (parser/ir): Support multiple overlays, in which case the add/removeOverrides + * and add/removeOverlays functions can be more easily collapsed. + */ + public async addOverlay( + specFilePath: AbsoluteFilePath, + overlayAbsolutePath: AbsoluteFilePath + ): Promise { + const section = await this.resolveSection("api", ["specs"]); + const fileDir = dirname(section.filePath); + const relativeOverlayPath = `./${path.relative(fileDir, overlayAbsolutePath)}`; + const index = this.findSpecIndex(section, specFilePath); + if (index < 0) { + return undefined; + } + + const overlaysPath = [...section.basePath, index, "overlays"]; + const existing = nodeToJS(section.document.getIn(overlaysPath)); + const normalizedNew = path.normalize(relativeOverlayPath); + + if (existing == null) { + section.document.setIn(overlaysPath, relativeOverlayPath); + } else if (typeof existing === "string") { + if (path.normalize(existing) === normalizedNew) { + return undefined; + } + section.document.setIn(overlaysPath, relativeOverlayPath); + } + + this.markDirty(section.filePath); + return { line: this.getLineOfPath(section, overlaysPath) }; + } + + /** + * Removes the overlay reference for the matching spec. + * Returns the edit location, or undefined if nothing was removed. + */ + public async removeOverlay(specFilePath: AbsoluteFilePath): Promise { + const section = await this.resolveSection("api", ["specs"]); + const index = this.findSpecIndex(section, specFilePath); + if (index < 0) { + return undefined; + } + + const overlaysPath = [...section.basePath, index, "overlays"]; + const existing = nodeToJS(section.document.getIn(overlaysPath)); + if (existing == null) { + return undefined; + } + const line = this.getLineOfPath(section, overlaysPath); + section.document.deleteIn(overlaysPath); + this.markDirty(section.filePath); + return { line }; } - /** Writes the document back to disk, preserving formatting. */ + // ─── Shared ────────────────────────────────────────────────────────── + + /** + * Writes all mutated documents back to disk, preserving YAML formatting. + */ public async save(): Promise { - await writeFile(this.filePath, this.document.toString(), "utf-8"); + if (this.dirtyFiles.size === 0) { + return; + } + + const writes: Promise[] = []; + const written = new Set(); + // Sections are guaranteed to be resolved if they were dirtied. + // Deduplicate: sections may share the same file when no $ref is used. + for (const section of this.sections.values()) { + if (this.dirtyFiles.has(section.filePath) && !written.has(section.filePath)) { + written.add(section.filePath); + writes.push(writeFile(section.filePath, section.document.toString(), "utf-8")); + } + } + await Promise.all(writes); + + this.dirtyFiles.clear(); + this.cachedSerialization.clear(); + } + + // ─── Private: section resolution ───────────────────────────────────── + + /** + * Lazily resolves a top-level section that may use a `$ref`. + * If the section has a `$ref`, loads the referenced file on first access. + * Otherwise returns a section pointing into the root document. + * Results are cached per section key. + */ + private async resolveSection(sectionKey: string, innerPath: (string | number)[]): Promise { + const cached = this.sections.get(sectionKey); + if (cached != null) { + return cached; + } + + const sectionValue = this.rootDoc[sectionKey]; + if (sectionValue != null && typeof sectionValue === "object" && REF_KEY in sectionValue) { + const refPath = (sectionValue as Record)[REF_KEY]; + if (typeof refPath === "string") { + const resolvedPath = join(dirname(this.rootFilePath), RelativeFilePath.of(refPath)); + if (!(await doesPathExist(resolvedPath))) { + throw new CliError({ + message: `Referenced file '${refPath}' in ${FERN_YML_FILENAME} does not exist.` + }); + } + const refContent = await readFile(resolvedPath, "utf-8"); + const refDocument = parseDocument(refContent); + const section: ResolvedSection = { + document: refDocument, + filePath: resolvedPath, + basePath: innerPath + }; + this.sections.set(sectionKey, section); + return section; + } + } + const section: ResolvedSection = { + document: this.rootDocument, + filePath: this.rootFilePath, + basePath: [sectionKey, ...innerPath] + }; + this.sections.set(sectionKey, section); + return section; } - private assertTargetExists(name: string): void { - const existing = this.document.getIn([...this.targetsPath, name]); + // ─── Private: SDK helpers ──────────────────────────────────────────── + + private assertTargetExists(section: ResolvedSection, name: string): void { + const existing = section.document.getIn([...section.basePath, name]); if (existing == null) { throw new CliError({ message: `Target '${name}' not found in SDK configuration.` @@ -135,13 +355,80 @@ export class FernYmlEditor { * Ensures that the map path exists in the document, creating intermediate * maps as needed. */ - private ensureMapPath(path: (string | number)[]): void { - for (let i = 1; i <= path.length; i++) { - const subPath = path.slice(0, i); - const existing = this.document.getIn(subPath); + private ensureMapPath(document: Document, mapPath: (string | number)[]): void { + for (let i = 1; i <= mapPath.length; i++) { + const subPath = mapPath.slice(0, i); + const existing = document.getIn(subPath); if (existing == null) { - this.document.setIn(subPath, this.document.createNode({})); + document.setIn(subPath, document.createNode({})); + } + } + } + + // ─── Private: API spec helpers ─────────────────────────────────────── + + /** + * Finds the index of the spec entry matching the given absolute path. + */ + private findSpecIndex(section: ResolvedSection, specFilePath: AbsoluteFilePath): number { + const specs = nodeToJS(section.document.getIn(section.basePath)) as unknown[]; + if (!Array.isArray(specs)) { + return -1; + } + + const fileDir = dirname(section.filePath); + const relativeSpecPath = path.normalize(path.relative(fileDir, specFilePath)); + + for (let i = 0; i < specs.length; i++) { + const spec = specs[i] as Record | undefined; + if (spec == null) { + continue; + } + const specPath = (spec.openapi ?? spec.asyncapi) as string | undefined; + if (specPath != null && path.normalize(specPath) === relativeSpecPath) { + return i; + } + } + return -1; + } + + // ─── Private: shared helpers ───────────────────────────────────────── + + private markDirty(filePath: AbsoluteFilePath): void { + this.dirtyFiles.add(filePath); + this.cachedSerialization.delete(filePath); + } + + /** + * Re-serializes the document and finds the 1-based line number of the node at `nodePath`. + * Caches the serialization to avoid redundant work when called multiple times between mutations. + */ + private getLineOfPath(section: ResolvedSection, nodePath: (string | number)[]): number { + let cached = this.cachedSerialization.get(section.filePath); + if (cached == null) { + const text = section.document.toString(); + cached = { text, parsed: parseDocument(text) }; + this.cachedSerialization.set(section.filePath, cached); + } + const { text, parsed } = cached; + const node = parsed.getIn(nodePath, true); + if (node != null && typeof node === "object" && "range" in node) { + const range = (node as { range?: [number, number, number] }).range; + if (range != null) { + return text.substring(0, range[0]).split("\n").length; } } + return 1; + } +} + +/** + * Converts a YAML Document node (YAMLMap, YAMLSeq, Scalar) to its plain JS value. + * Primitives and null/undefined pass through as-is. + */ +function nodeToJS(value: unknown): unknown { + if (value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + return value; } + return (value as { toJSON(): unknown }).toJSON(); } diff --git a/packages/cli/cli-v2/src/sdk/updater/GeneratorMigrator.ts b/packages/cli/cli-v2/src/sdk/updater/GeneratorMigrator.ts index 6872751083b9..2b33dbe2d990 100644 --- a/packages/cli/cli-v2/src/sdk/updater/GeneratorMigrator.ts +++ b/packages/cli/cli-v2/src/sdk/updater/GeneratorMigrator.ts @@ -92,7 +92,7 @@ export class GeneratorMigrator { return undefined; } - this.applyConfigChanges({ target, editor, migratedConfig: result.config.config }); + await this.applyConfigChanges({ target, editor, migratedConfig: result.config.config }); return { migrationsApplied: result.migrationsApplied, @@ -117,7 +117,7 @@ export class GeneratorMigrator { * the editor. If the migration removed config entirely, the key is * deleted from the target. */ - private applyConfigChanges({ + private async applyConfigChanges({ target, editor, migratedConfig @@ -125,13 +125,13 @@ export class GeneratorMigrator { target: Target; editor: FernYmlEditor; migratedConfig: unknown; - }): void { + }): Promise { if (migratedConfig != null && typeof migratedConfig === "object") { - editor.setTargetConfig(target.name, migratedConfig as Record); + await editor.setTargetConfig(target.name, migratedConfig as Record); return; } if (target.config != null && migratedConfig == null) { - editor.deleteTargetConfig(target.name); + await editor.deleteTargetConfig(target.name); } } diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index f89f01261d92..bb35fe754bdb 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -1,4 +1,18 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- version: 4.31.2 + changelogEntry: + - summary: | + Sanitize changelog entries to wrap type references containing angle + brackets (e.g. `Optional`, `Map`) in inline + code fences. This prevents MDX compilers from parsing these as + unclosed HTML/JSX tags when changelogs are rendered in documentation + sites. The sanitization is applied to both seed-based and + auto-versioned changelog generation paths, and the AI prompt now + instructs the model to wrap such references in backticks. + type: fix + createdAt: "2026-03-16" + irVersion: 65 + - version: 4.31.1 changelogEntry: - summary: | diff --git a/packages/cli/generation/local-generation/local-workspace-runner/src/LocalTaskHandler.ts b/packages/cli/generation/local-generation/local-workspace-runner/src/LocalTaskHandler.ts index 7896b9312177..ff6f6ef2291a 100644 --- a/packages/cli/generation/local-generation/local-workspace-runner/src/LocalTaskHandler.ts +++ b/packages/cli/generation/local-generation/local-workspace-runner/src/LocalTaskHandler.ts @@ -18,6 +18,7 @@ import { countFilesInDiff, formatSizeKB } from "./AutoVersioningService.js"; +import { sanitizeChangelogEntry } from "./sanitizeChangelogEntry.js"; import { isAutoVersion, MAX_AI_DIFF_BYTES, MAX_CHUNKS, MAX_RAW_DIFF_BYTES, maxVersionBump } from "./VersionUtils.js"; export declare namespace LocalTaskHandler { @@ -411,7 +412,9 @@ export class LocalTaskHandler { const commitMessage = this.isWhitelabel ? finalMessage : this.addFernBranding(finalMessage); // changelogEntry is populated for MINOR/MAJOR, undefined for PATCH (empty string from AI) - const changelogEntry = finalChangelogEntry?.trim() || undefined; + const changelogEntry = finalChangelogEntry?.trim() + ? sanitizeChangelogEntry(finalChangelogEntry.trim()) + : undefined; const prDescription = finalPrDescription?.trim() || undefined; const versionBumpReason = finalVersionBumpReason?.trim() || undefined; diff --git a/packages/cli/generation/local-generation/local-workspace-runner/src/sanitizeChangelogEntry.ts b/packages/cli/generation/local-generation/local-workspace-runner/src/sanitizeChangelogEntry.ts new file mode 100644 index 000000000000..021dc4455f38 --- /dev/null +++ b/packages/cli/generation/local-generation/local-workspace-runner/src/sanitizeChangelogEntry.ts @@ -0,0 +1,2 @@ +// Re-export from shared utility +export { sanitizeChangelogEntry } from "@fern-api/core-utils"; diff --git a/packages/commons/core-utils/src/__tests__/sanitizeChangelogEntry.test.ts b/packages/commons/core-utils/src/__tests__/sanitizeChangelogEntry.test.ts new file mode 100644 index 000000000000..96bb50bc8f9e --- /dev/null +++ b/packages/commons/core-utils/src/__tests__/sanitizeChangelogEntry.test.ts @@ -0,0 +1,77 @@ +import { sanitizeChangelogEntry } from "../sanitizeChangelogEntry.js"; + +describe("sanitizeChangelogEntry", () => { + it("wraps simple generic types in backticks", () => { + expect(sanitizeChangelogEntry("Added Optional support")).toBe("Added `Optional` support"); + }); + + it("wraps Map with two type parameters", () => { + expect(sanitizeChangelogEntry("Returns Map")).toBe("Returns `Map`"); + }); + + it("wraps single type parameter", () => { + expect(sanitizeChangelogEntry("Use List instead")).toBe("Use `List` instead"); + }); + + it("wraps two-level nested generics", () => { + expect(sanitizeChangelogEntry("Returns CompletableFuture>")).toBe( + "Returns `CompletableFuture>`" + ); + expect(sanitizeChangelogEntry("Takes Map>")).toBe("Takes `Map>`"); + }); + + it("wraps three-level nested generics", () => { + expect(sanitizeChangelogEntry("Uses Map>>")).toBe( + "Uses `Map>>`" + ); + }); + + it("does not double-wrap already backticked spans", () => { + expect(sanitizeChangelogEntry("Already wrapped `Optional` stays")).toBe( + "Already wrapped `Optional` stays" + ); + }); + + it("handles mixed backticked and unwrapped types", () => { + expect(sanitizeChangelogEntry("Already `List` and new Optional")).toBe( + "Already `List` and new `Optional`" + ); + }); + + it("wraps multiple type references in one line", () => { + expect(sanitizeChangelogEntry("Changed Optional to Map")).toBe( + "Changed `Optional` to `Map`" + ); + }); + + it("does not match bare angle brackets without identifier prefix", () => { + expect(sanitizeChangelogEntry("use
tags")).toBe("use
tags"); + expect(sanitizeChangelogEntry("")).toBe(""); + }); + + it("does not match comparison operators", () => { + expect(sanitizeChangelogEntry("a < b > c")).toBe("a < b > c"); + }); + + it("returns text unchanged when there are no angle brackets", () => { + expect(sanitizeChangelogEntry("No angle brackets here")).toBe("No angle brackets here"); + }); + + it("handles empty string", () => { + expect(sanitizeChangelogEntry("")).toBe(""); + }); + + it("handles type at start of string", () => { + expect(sanitizeChangelogEntry("Optional is now supported")).toBe("`Optional` is now supported"); + }); + + it("handles type at end of string", () => { + expect(sanitizeChangelogEntry("Now returns Optional")).toBe("Now returns `Optional`"); + }); + + it("handles multiline entries", () => { + const input = "Added Optional support.\nAlso changed Map handling."; + const expected = "Added `Optional` support.\nAlso changed `Map` handling."; + expect(sanitizeChangelogEntry(input)).toBe(expected); + }); +}); diff --git a/packages/commons/core-utils/src/index.ts b/packages/commons/core-utils/src/index.ts index 3dce58c8bcc8..29c0af80b812 100644 --- a/packages/commons/core-utils/src/index.ts +++ b/packages/commons/core-utils/src/index.ts @@ -39,6 +39,7 @@ export { PLATFORM, type Platform } from "./platform.js"; export { removeSuffix } from "./removeSuffix.js"; export { replaceEnvVariables } from "./replaceEnvVars.js"; export { SymbolRegistry, type SymbolRegistryOptions } from "./SymbolRegistry.js"; +export { sanitizeChangelogEntry } from "./sanitizeChangelogEntry.js"; export { SKIP_MARKER, sanitizeNullValues } from "./sanitizeNullValues.js"; export { diffSemverOrThrow, parseSemverOrThrow } from "./semverUtils.js"; export { type SetRequired } from "./setRequired.js"; diff --git a/packages/commons/core-utils/src/sanitizeChangelogEntry.ts b/packages/commons/core-utils/src/sanitizeChangelogEntry.ts new file mode 100644 index 000000000000..0dc424b59751 --- /dev/null +++ b/packages/commons/core-utils/src/sanitizeChangelogEntry.ts @@ -0,0 +1,74 @@ +/** + * Wraps type references containing angle brackets (e.g., `Optional`, + * `Map`) in inline code fences (backticks) so they are not + * parsed as HTML/JSX tags by MDX compilers. + * + * Uses a balanced-bracket parser to support arbitrary nesting depth + * (e.g., `Map>>`). + * + * Already-backticked spans are left untouched. + */ +export function sanitizeChangelogEntry(entry: string): string { + // Split the text into alternating segments: plain text and backtick-delimited code spans. + // Odd-indexed segments are inside backticks and must be preserved as-is. + const parts = entry.split(/(`[^`]+`)/g); + return parts + .map((part) => { + // If this part is a code span, leave it as-is + if (part.startsWith("`") && part.endsWith("`")) { + return part; + } + return wrapAngleBracketTypes(part); + }) + .join(""); +} + +/** + * Finds identifier-prefixed angle-bracket type references (e.g., Optional) + * and wraps each one in backticks. Handles arbitrary nesting depth by counting + * balanced `<` / `>` pairs. + */ +function wrapAngleBracketTypes(text: string): string { + let result = ""; + let i = 0; + while (i < text.length) { + // Check if we're at a word boundary followed by an identifier + '<' + const ch = text[i]; + const prev = i > 0 ? text[i - 1] : undefined; + if (ch != null && /[A-Za-z]/.test(ch) && (i === 0 || (prev != null && /\W/.test(prev)))) { + // Try to match an identifier (e.g., Optional, Map, List) + const identMatch = /^[A-Za-z]\w*/.exec(text.slice(i)); + if ( + identMatch != null && + i + identMatch[0].length < text.length && + text[i + identMatch[0].length] === "<" + ) { + const angleStart = i + identMatch[0].length; + // Find balanced closing '>' + let depth = 0; + let j = angleStart; + let balanced = false; + while (j < text.length) { + if (text[j] === "<") { + depth++; + } else if (text[j] === ">") { + depth--; + if (depth === 0) { + balanced = true; + break; + } + } + j++; + } + if (balanced) { + result += "`" + text.slice(i, j + 1) + "`"; + i = j + 1; + continue; + } + } + } + result += text[i]; + i++; + } + return result; +} diff --git a/packages/seed/src/commands/generate/writeChangelogEntries.ts b/packages/seed/src/commands/generate/writeChangelogEntries.ts index 7e947adb5b88..980c8a838e49 100644 --- a/packages/seed/src/commands/generate/writeChangelogEntries.ts +++ b/packages/seed/src/commands/generate/writeChangelogEntries.ts @@ -1,3 +1,4 @@ +import { sanitizeChangelogEntry } from "@fern-api/core-utils"; import { AbsoluteFilePath, join, RelativeFilePath } from "@fern-api/fs-utils"; import { ChangelogEntry } from "@fern-fern/generators-sdk/api/resources/generators"; import { writeFile } from "fs/promises"; @@ -41,15 +42,15 @@ export function writeChangelogEntries(version: string, entries: ChangelogEntry[] changelogString += "Nothing new to report!"; } else { entries.forEach((entry) => { - summary.push(`**\`(${entry.type}):\`** ${entry.summary}`); - added.push(...(entry.added ?? [])); - changed.push(...(entry.changed ?? [])); - deprecated.push(...(entry.deprecated ?? [])); - removed.push(...(entry.removed ?? [])); - fixed.push(...(entry.fixed ?? [])); + summary.push(`**\`(${entry.type}):\`** ${sanitizeChangelogEntry(entry.summary)}`); + added.push(...(entry.added ?? []).map(sanitizeChangelogEntry)); + changed.push(...(entry.changed ?? []).map(sanitizeChangelogEntry)); + deprecated.push(...(entry.deprecated ?? []).map(sanitizeChangelogEntry)); + removed.push(...(entry.removed ?? []).map(sanitizeChangelogEntry)); + fixed.push(...(entry.fixed ?? []).map(sanitizeChangelogEntry)); if (entry.upgradeNotes != null) { - upgradeNotes.push(entry.upgradeNotes); + upgradeNotes.push(sanitizeChangelogEntry(entry.upgradeNotes)); } }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4eba5bb081c7..c684f12865af 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5258,6 +5258,12 @@ importers: specifier: ^17.4.1 version: 17.7.2 + packages/cli/cli/dist/prod-unminified: + dependencies: + '@boundaryml/baml': + specifier: 'catalog:' + version: 0.219.0 + packages/cli/config: devDependencies: '@fern-api/configs': diff --git a/seed/java-model/exhaustive/src/main/java/com/seed/exhaustive/model/types/object/DocumentedUnknownType.java b/seed/java-model/exhaustive/src/main/java/com/seed/exhaustive/model/types/object/DocumentedUnknownType.java new file mode 100644 index 000000000000..04e9befd0b0a --- /dev/null +++ b/seed/java-model/exhaustive/src/main/java/com/seed/exhaustive/model/types/object/DocumentedUnknownType.java @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.exhaustive.model.types.object; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import java.lang.Object; +import java.lang.String; + +public final class DocumentedUnknownType { + private final Object value; + + private DocumentedUnknownType(Object value) { + this.value = value; + } + + @JsonValue + public Object get() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object other) { + return this == other || (other instanceof DocumentedUnknownType && this.value.equals(((DocumentedUnknownType) other).value)); + } + + @java.lang.Override + public int hashCode() { + return value.hashCode(); + } + + @java.lang.Override + public String toString() { + return value.toString(); + } + + @JsonCreator( + mode = JsonCreator.Mode.DELEGATING + ) + public static DocumentedUnknownType of(Object value) { + return new DocumentedUnknownType(value); + } +} diff --git a/seed/java-model/exhaustive/src/main/java/com/seed/exhaustive/model/types/object/ObjectWithDocumentedUnknownType.java b/seed/java-model/exhaustive/src/main/java/com/seed/exhaustive/model/types/object/ObjectWithDocumentedUnknownType.java index f13650e51324..84b9ad4fe284 100644 --- a/seed/java-model/exhaustive/src/main/java/com/seed/exhaustive/model/types/object/ObjectWithDocumentedUnknownType.java +++ b/seed/java-model/exhaustive/src/main/java/com/seed/exhaustive/model/types/object/ObjectWithDocumentedUnknownType.java @@ -19,14 +19,14 @@ builder = ObjectWithDocumentedUnknownType.Builder.class ) public final class ObjectWithDocumentedUnknownType { - private final Object documentedUnknownType; + private final DocumentedUnknownType documentedUnknownType; - private ObjectWithDocumentedUnknownType(Object documentedUnknownType) { + private ObjectWithDocumentedUnknownType(DocumentedUnknownType documentedUnknownType) { this.documentedUnknownType = documentedUnknownType; } @JsonProperty("documentedUnknownType") - public Object getDocumentedUnknownType() { + public DocumentedUnknownType getDocumentedUnknownType() { return documentedUnknownType; } @@ -55,7 +55,7 @@ public static DocumentedUnknownTypeStage builder() { } public interface DocumentedUnknownTypeStage { - _FinalStage documentedUnknownType(Object documentedUnknownType); + _FinalStage documentedUnknownType(DocumentedUnknownType documentedUnknownType); Builder from(ObjectWithDocumentedUnknownType other); } @@ -68,7 +68,7 @@ public interface _FinalStage { ignoreUnknown = true ) public static final class Builder implements DocumentedUnknownTypeStage, _FinalStage { - private Object documentedUnknownType; + private DocumentedUnknownType documentedUnknownType; private Builder() { } @@ -81,8 +81,8 @@ public Builder from(ObjectWithDocumentedUnknownType other) { @java.lang.Override @JsonSetter("documentedUnknownType") - public _FinalStage documentedUnknownType(Object documentedUnknownType) { - this.documentedUnknownType = documentedUnknownType; + public _FinalStage documentedUnknownType(DocumentedUnknownType documentedUnknownType) { + this.documentedUnknownType = Objects.requireNonNull(documentedUnknownType, "documentedUnknownType must not be null"); return this; } diff --git a/seed/java-model/unknown/src/main/java/com/seed/unknownAsAny/model/unknown/MyAlias.java b/seed/java-model/unknown/src/main/java/com/seed/unknownAsAny/model/unknown/MyAlias.java new file mode 100644 index 000000000000..91418d58ec96 --- /dev/null +++ b/seed/java-model/unknown/src/main/java/com/seed/unknownAsAny/model/unknown/MyAlias.java @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.seed.unknownAsAny.model.unknown; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import java.lang.Object; +import java.lang.String; + +public final class MyAlias { + private final Object value; + + private MyAlias(Object value) { + this.value = value; + } + + @JsonValue + public Object get() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object other) { + return this == other || (other instanceof MyAlias && this.value.equals(((MyAlias) other).value)); + } + + @java.lang.Override + public int hashCode() { + return value.hashCode(); + } + + @java.lang.Override + public String toString() { + return value.toString(); + } + + @JsonCreator( + mode = JsonCreator.Mode.DELEGATING + ) + public static MyAlias of(Object value) { + return new MyAlias(value); + } +} diff --git a/seed/java-sdk/exhaustive/custom-client-class-name/reference.md b/seed/java-sdk/exhaustive/custom-client-class-name/reference.md index f5e12e1b27ea..1cb3c5c467fc 100644 --- a/seed/java-sdk/exhaustive/custom-client-class-name/reference.md +++ b/seed/java-sdk/exhaustive/custom-client-class-name/reference.md @@ -1305,9 +1305,11 @@ client.endpoints().object().getAndReturnWithUnknownField( client.endpoints().object().getAndReturnWithDocumentedUnknownType( ObjectWithDocumentedUnknownType .builder() - .documentedUnknownType(new + .documentedUnknownType( + DocumentedUnknownType.of(new HashMap() {{put("key", "value"); }}) + ) .build() ); ``` @@ -1351,9 +1353,9 @@ client.endpoints().object().getAndReturnWithDocumentedUnknownType( ```java client.endpoints().object().getAndReturnMapOfDocumentedUnknownType( new HashMap() {{ - put("string", new + put("string", DocumentedUnknownType.of(new HashMap() {{put("key", "value"); - }}); + }})); }} ); ``` diff --git a/seed/java-sdk/exhaustive/custom-client-class-name/snippet.json b/seed/java-sdk/exhaustive/custom-client-class-name/snippet.json index 90409c5a244a..9695b88e503e 100644 --- a/seed/java-sdk/exhaustive/custom-client-class-name/snippet.json +++ b/seed/java-sdk/exhaustive/custom-client-class-name/snippet.json @@ -321,8 +321,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.Best;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n Best client = Best\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.Best;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n Best client = Best\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.Best;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n Best client = Best\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.Best;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n Best client = Best\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n" } }, { @@ -334,8 +334,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.Best;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n Best client = Best\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.Best;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n Best client = Best\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.Best;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n Best client = Best\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.Best;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n Best client = Best\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n" } }, { diff --git a/seed/java-sdk/exhaustive/custom-client-class-name/src/main/java/com/seed/exhaustive/core/WrappedAlias.java b/seed/java-sdk/exhaustive/custom-client-class-name/src/main/java/com/seed/exhaustive/core/WrappedAlias.java new file mode 100644 index 000000000000..8cca824d16c6 --- /dev/null +++ b/seed/java-sdk/exhaustive/custom-client-class-name/src/main/java/com/seed/exhaustive/core/WrappedAlias.java @@ -0,0 +1,8 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.core; + +public interface WrappedAlias { + Object get(); +} diff --git a/seed/java-sdk/exhaustive/custom-client-class-name/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java b/seed/java-sdk/exhaustive/custom-client-class-name/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java new file mode 100644 index 000000000000..2c678d4b52d9 --- /dev/null +++ b/seed/java-sdk/exhaustive/custom-client-class-name/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java @@ -0,0 +1,42 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.resources.types.object.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.seed.exhaustive.core.WrappedAlias; + +public final class DocumentedUnknownType implements WrappedAlias { + private final Object value; + + private DocumentedUnknownType(Object value) { + this.value = value; + } + + @JsonValue + public Object get() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object other) { + return this == other + || (other instanceof DocumentedUnknownType && this.value.equals(((DocumentedUnknownType) other).value)); + } + + @java.lang.Override + public int hashCode() { + return value.hashCode(); + } + + @java.lang.Override + public String toString() { + return value.toString(); + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DocumentedUnknownType of(Object value) { + return new DocumentedUnknownType(value); + } +} diff --git a/seed/java-sdk/exhaustive/custom-client-class-name/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java b/seed/java-sdk/exhaustive/custom-client-class-name/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java index 8f43716682d1..4588cd4d078e 100644 --- a/seed/java-sdk/exhaustive/custom-client-class-name/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java +++ b/seed/java-sdk/exhaustive/custom-client-class-name/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java @@ -14,21 +14,23 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = ObjectWithDocumentedUnknownType.Builder.class) public final class ObjectWithDocumentedUnknownType { - private final Object documentedUnknownType; + private final DocumentedUnknownType documentedUnknownType; private final Map additionalProperties; - private ObjectWithDocumentedUnknownType(Object documentedUnknownType, Map additionalProperties) { + private ObjectWithDocumentedUnknownType( + DocumentedUnknownType documentedUnknownType, Map additionalProperties) { this.documentedUnknownType = documentedUnknownType; this.additionalProperties = additionalProperties; } @JsonProperty("documentedUnknownType") - public Object getDocumentedUnknownType() { + public DocumentedUnknownType getDocumentedUnknownType() { return documentedUnknownType; } @@ -62,7 +64,7 @@ public static DocumentedUnknownTypeStage builder() { } public interface DocumentedUnknownTypeStage { - _FinalStage documentedUnknownType(Object documentedUnknownType); + _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType); Builder from(ObjectWithDocumentedUnknownType other); } @@ -77,7 +79,7 @@ public interface _FinalStage { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder implements DocumentedUnknownTypeStage, _FinalStage { - private Object documentedUnknownType; + private DocumentedUnknownType documentedUnknownType; @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -92,8 +94,9 @@ public Builder from(ObjectWithDocumentedUnknownType other) { @java.lang.Override @JsonSetter("documentedUnknownType") - public _FinalStage documentedUnknownType(Object documentedUnknownType) { - this.documentedUnknownType = documentedUnknownType; + public _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType) { + this.documentedUnknownType = + Objects.requireNonNull(documentedUnknownType, "documentedUnknownType must not be null"); return this; } diff --git a/seed/java-sdk/exhaustive/custom-client-class-name/src/main/java/com/snippets/Example24.java b/seed/java-sdk/exhaustive/custom-client-class-name/src/main/java/com/snippets/Example24.java index 7f2180de62c2..65d8cf571a9b 100644 --- a/seed/java-sdk/exhaustive/custom-client-class-name/src/main/java/com/snippets/Example24.java +++ b/seed/java-sdk/exhaustive/custom-client-class-name/src/main/java/com/snippets/Example24.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.Best; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType; import java.util.HashMap; @@ -12,11 +13,11 @@ public static void main(String[] args) { client.endpoints() .object() .getAndReturnWithDocumentedUnknownType(ObjectWithDocumentedUnknownType.builder() - .documentedUnknownType(new HashMap() { + .documentedUnknownType(DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }) + })) .build()); } } diff --git a/seed/java-sdk/exhaustive/custom-client-class-name/src/main/java/com/snippets/Example25.java b/seed/java-sdk/exhaustive/custom-client-class-name/src/main/java/com/snippets/Example25.java index 1a49314bb741..86d105cce975 100644 --- a/seed/java-sdk/exhaustive/custom-client-class-name/src/main/java/com/snippets/Example25.java +++ b/seed/java-sdk/exhaustive/custom-client-class-name/src/main/java/com/snippets/Example25.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.Best; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import java.util.HashMap; public class Example25 { @@ -10,11 +11,11 @@ public static void main(String[] args) { client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(new HashMap() { { - put("string", new HashMap() { + put("string", DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }); + })); } }); } diff --git a/seed/java-sdk/exhaustive/custom-dependency/reference.md b/seed/java-sdk/exhaustive/custom-dependency/reference.md index f5e12e1b27ea..1cb3c5c467fc 100644 --- a/seed/java-sdk/exhaustive/custom-dependency/reference.md +++ b/seed/java-sdk/exhaustive/custom-dependency/reference.md @@ -1305,9 +1305,11 @@ client.endpoints().object().getAndReturnWithUnknownField( client.endpoints().object().getAndReturnWithDocumentedUnknownType( ObjectWithDocumentedUnknownType .builder() - .documentedUnknownType(new + .documentedUnknownType( + DocumentedUnknownType.of(new HashMap() {{put("key", "value"); }}) + ) .build() ); ``` @@ -1351,9 +1353,9 @@ client.endpoints().object().getAndReturnWithDocumentedUnknownType( ```java client.endpoints().object().getAndReturnMapOfDocumentedUnknownType( new HashMap() {{ - put("string", new + put("string", DocumentedUnknownType.of(new HashMap() {{put("key", "value"); - }}); + }})); }} ); ``` diff --git a/seed/java-sdk/exhaustive/custom-dependency/snippet.json b/seed/java-sdk/exhaustive/custom-dependency/snippet.json index 14b83a1f428d..b5e4f4db8655 100644 --- a/seed/java-sdk/exhaustive/custom-dependency/snippet.json +++ b/seed/java-sdk/exhaustive/custom-dependency/snippet.json @@ -321,8 +321,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n" } }, { @@ -334,8 +334,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n" } }, { diff --git a/seed/java-sdk/exhaustive/custom-dependency/src/main/java/com/seed/exhaustive/core/WrappedAlias.java b/seed/java-sdk/exhaustive/custom-dependency/src/main/java/com/seed/exhaustive/core/WrappedAlias.java new file mode 100644 index 000000000000..8cca824d16c6 --- /dev/null +++ b/seed/java-sdk/exhaustive/custom-dependency/src/main/java/com/seed/exhaustive/core/WrappedAlias.java @@ -0,0 +1,8 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.core; + +public interface WrappedAlias { + Object get(); +} diff --git a/seed/java-sdk/exhaustive/custom-dependency/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java b/seed/java-sdk/exhaustive/custom-dependency/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java new file mode 100644 index 000000000000..2c678d4b52d9 --- /dev/null +++ b/seed/java-sdk/exhaustive/custom-dependency/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java @@ -0,0 +1,42 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.resources.types.object.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.seed.exhaustive.core.WrappedAlias; + +public final class DocumentedUnknownType implements WrappedAlias { + private final Object value; + + private DocumentedUnknownType(Object value) { + this.value = value; + } + + @JsonValue + public Object get() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object other) { + return this == other + || (other instanceof DocumentedUnknownType && this.value.equals(((DocumentedUnknownType) other).value)); + } + + @java.lang.Override + public int hashCode() { + return value.hashCode(); + } + + @java.lang.Override + public String toString() { + return value.toString(); + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DocumentedUnknownType of(Object value) { + return new DocumentedUnknownType(value); + } +} diff --git a/seed/java-sdk/exhaustive/custom-dependency/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java b/seed/java-sdk/exhaustive/custom-dependency/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java index 8f43716682d1..4588cd4d078e 100644 --- a/seed/java-sdk/exhaustive/custom-dependency/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java +++ b/seed/java-sdk/exhaustive/custom-dependency/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java @@ -14,21 +14,23 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = ObjectWithDocumentedUnknownType.Builder.class) public final class ObjectWithDocumentedUnknownType { - private final Object documentedUnknownType; + private final DocumentedUnknownType documentedUnknownType; private final Map additionalProperties; - private ObjectWithDocumentedUnknownType(Object documentedUnknownType, Map additionalProperties) { + private ObjectWithDocumentedUnknownType( + DocumentedUnknownType documentedUnknownType, Map additionalProperties) { this.documentedUnknownType = documentedUnknownType; this.additionalProperties = additionalProperties; } @JsonProperty("documentedUnknownType") - public Object getDocumentedUnknownType() { + public DocumentedUnknownType getDocumentedUnknownType() { return documentedUnknownType; } @@ -62,7 +64,7 @@ public static DocumentedUnknownTypeStage builder() { } public interface DocumentedUnknownTypeStage { - _FinalStage documentedUnknownType(Object documentedUnknownType); + _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType); Builder from(ObjectWithDocumentedUnknownType other); } @@ -77,7 +79,7 @@ public interface _FinalStage { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder implements DocumentedUnknownTypeStage, _FinalStage { - private Object documentedUnknownType; + private DocumentedUnknownType documentedUnknownType; @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -92,8 +94,9 @@ public Builder from(ObjectWithDocumentedUnknownType other) { @java.lang.Override @JsonSetter("documentedUnknownType") - public _FinalStage documentedUnknownType(Object documentedUnknownType) { - this.documentedUnknownType = documentedUnknownType; + public _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType) { + this.documentedUnknownType = + Objects.requireNonNull(documentedUnknownType, "documentedUnknownType must not be null"); return this; } diff --git a/seed/java-sdk/exhaustive/custom-dependency/src/main/java/com/snippets/Example24.java b/seed/java-sdk/exhaustive/custom-dependency/src/main/java/com/snippets/Example24.java index 6a53c41d7107..f667d8f4e99a 100644 --- a/seed/java-sdk/exhaustive/custom-dependency/src/main/java/com/snippets/Example24.java +++ b/seed/java-sdk/exhaustive/custom-dependency/src/main/java/com/snippets/Example24.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType; import java.util.HashMap; @@ -14,11 +15,11 @@ public static void main(String[] args) { client.endpoints() .object() .getAndReturnWithDocumentedUnknownType(ObjectWithDocumentedUnknownType.builder() - .documentedUnknownType(new HashMap() { + .documentedUnknownType(DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }) + })) .build()); } } diff --git a/seed/java-sdk/exhaustive/custom-dependency/src/main/java/com/snippets/Example25.java b/seed/java-sdk/exhaustive/custom-dependency/src/main/java/com/snippets/Example25.java index 647c450adce3..4432fc0557f0 100644 --- a/seed/java-sdk/exhaustive/custom-dependency/src/main/java/com/snippets/Example25.java +++ b/seed/java-sdk/exhaustive/custom-dependency/src/main/java/com/snippets/Example25.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import java.util.HashMap; public class Example25 { @@ -12,11 +13,11 @@ public static void main(String[] args) { client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(new HashMap() { { - put("string", new HashMap() { + put("string", DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }); + })); } }); } diff --git a/seed/java-sdk/exhaustive/custom-error-names/reference.md b/seed/java-sdk/exhaustive/custom-error-names/reference.md index f5e12e1b27ea..1cb3c5c467fc 100644 --- a/seed/java-sdk/exhaustive/custom-error-names/reference.md +++ b/seed/java-sdk/exhaustive/custom-error-names/reference.md @@ -1305,9 +1305,11 @@ client.endpoints().object().getAndReturnWithUnknownField( client.endpoints().object().getAndReturnWithDocumentedUnknownType( ObjectWithDocumentedUnknownType .builder() - .documentedUnknownType(new + .documentedUnknownType( + DocumentedUnknownType.of(new HashMap() {{put("key", "value"); }}) + ) .build() ); ``` @@ -1351,9 +1353,9 @@ client.endpoints().object().getAndReturnWithDocumentedUnknownType( ```java client.endpoints().object().getAndReturnMapOfDocumentedUnknownType( new HashMap() {{ - put("string", new + put("string", DocumentedUnknownType.of(new HashMap() {{put("key", "value"); - }}); + }})); }} ); ``` diff --git a/seed/java-sdk/exhaustive/custom-error-names/snippet.json b/seed/java-sdk/exhaustive/custom-error-names/snippet.json index 14b83a1f428d..b5e4f4db8655 100644 --- a/seed/java-sdk/exhaustive/custom-error-names/snippet.json +++ b/seed/java-sdk/exhaustive/custom-error-names/snippet.json @@ -321,8 +321,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n" } }, { @@ -334,8 +334,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n" } }, { diff --git a/seed/java-sdk/exhaustive/custom-error-names/src/main/java/com/seed/exhaustive/core/WrappedAlias.java b/seed/java-sdk/exhaustive/custom-error-names/src/main/java/com/seed/exhaustive/core/WrappedAlias.java new file mode 100644 index 000000000000..8cca824d16c6 --- /dev/null +++ b/seed/java-sdk/exhaustive/custom-error-names/src/main/java/com/seed/exhaustive/core/WrappedAlias.java @@ -0,0 +1,8 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.core; + +public interface WrappedAlias { + Object get(); +} diff --git a/seed/java-sdk/exhaustive/custom-error-names/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java b/seed/java-sdk/exhaustive/custom-error-names/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java new file mode 100644 index 000000000000..2c678d4b52d9 --- /dev/null +++ b/seed/java-sdk/exhaustive/custom-error-names/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java @@ -0,0 +1,42 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.resources.types.object.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.seed.exhaustive.core.WrappedAlias; + +public final class DocumentedUnknownType implements WrappedAlias { + private final Object value; + + private DocumentedUnknownType(Object value) { + this.value = value; + } + + @JsonValue + public Object get() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object other) { + return this == other + || (other instanceof DocumentedUnknownType && this.value.equals(((DocumentedUnknownType) other).value)); + } + + @java.lang.Override + public int hashCode() { + return value.hashCode(); + } + + @java.lang.Override + public String toString() { + return value.toString(); + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DocumentedUnknownType of(Object value) { + return new DocumentedUnknownType(value); + } +} diff --git a/seed/java-sdk/exhaustive/custom-error-names/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java b/seed/java-sdk/exhaustive/custom-error-names/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java index 8f43716682d1..4588cd4d078e 100644 --- a/seed/java-sdk/exhaustive/custom-error-names/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java +++ b/seed/java-sdk/exhaustive/custom-error-names/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java @@ -14,21 +14,23 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = ObjectWithDocumentedUnknownType.Builder.class) public final class ObjectWithDocumentedUnknownType { - private final Object documentedUnknownType; + private final DocumentedUnknownType documentedUnknownType; private final Map additionalProperties; - private ObjectWithDocumentedUnknownType(Object documentedUnknownType, Map additionalProperties) { + private ObjectWithDocumentedUnknownType( + DocumentedUnknownType documentedUnknownType, Map additionalProperties) { this.documentedUnknownType = documentedUnknownType; this.additionalProperties = additionalProperties; } @JsonProperty("documentedUnknownType") - public Object getDocumentedUnknownType() { + public DocumentedUnknownType getDocumentedUnknownType() { return documentedUnknownType; } @@ -62,7 +64,7 @@ public static DocumentedUnknownTypeStage builder() { } public interface DocumentedUnknownTypeStage { - _FinalStage documentedUnknownType(Object documentedUnknownType); + _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType); Builder from(ObjectWithDocumentedUnknownType other); } @@ -77,7 +79,7 @@ public interface _FinalStage { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder implements DocumentedUnknownTypeStage, _FinalStage { - private Object documentedUnknownType; + private DocumentedUnknownType documentedUnknownType; @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -92,8 +94,9 @@ public Builder from(ObjectWithDocumentedUnknownType other) { @java.lang.Override @JsonSetter("documentedUnknownType") - public _FinalStage documentedUnknownType(Object documentedUnknownType) { - this.documentedUnknownType = documentedUnknownType; + public _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType) { + this.documentedUnknownType = + Objects.requireNonNull(documentedUnknownType, "documentedUnknownType must not be null"); return this; } diff --git a/seed/java-sdk/exhaustive/custom-error-names/src/main/java/com/snippets/Example24.java b/seed/java-sdk/exhaustive/custom-error-names/src/main/java/com/snippets/Example24.java index 6a53c41d7107..f667d8f4e99a 100644 --- a/seed/java-sdk/exhaustive/custom-error-names/src/main/java/com/snippets/Example24.java +++ b/seed/java-sdk/exhaustive/custom-error-names/src/main/java/com/snippets/Example24.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType; import java.util.HashMap; @@ -14,11 +15,11 @@ public static void main(String[] args) { client.endpoints() .object() .getAndReturnWithDocumentedUnknownType(ObjectWithDocumentedUnknownType.builder() - .documentedUnknownType(new HashMap() { + .documentedUnknownType(DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }) + })) .build()); } } diff --git a/seed/java-sdk/exhaustive/custom-error-names/src/main/java/com/snippets/Example25.java b/seed/java-sdk/exhaustive/custom-error-names/src/main/java/com/snippets/Example25.java index 647c450adce3..4432fc0557f0 100644 --- a/seed/java-sdk/exhaustive/custom-error-names/src/main/java/com/snippets/Example25.java +++ b/seed/java-sdk/exhaustive/custom-error-names/src/main/java/com/snippets/Example25.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import java.util.HashMap; public class Example25 { @@ -12,11 +13,11 @@ public static void main(String[] args) { client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(new HashMap() { { - put("string", new HashMap() { + put("string", DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }); + })); } }); } diff --git a/seed/java-sdk/exhaustive/custom-interceptors/reference.md b/seed/java-sdk/exhaustive/custom-interceptors/reference.md index 78bb1dc37e27..72a2baa499f7 100644 --- a/seed/java-sdk/exhaustive/custom-interceptors/reference.md +++ b/seed/java-sdk/exhaustive/custom-interceptors/reference.md @@ -1305,9 +1305,11 @@ client.endpoints().object().getAndReturnWithUnknownField( client.endpoints().object().getAndReturnWithDocumentedUnknownType( ObjectWithDocumentedUnknownType .builder() - .documentedUnknownType(new + .documentedUnknownType( + DocumentedUnknownType.of(new HashMap() {{put("key", "value"); }}) + ) .build() ); ``` @@ -1351,9 +1353,9 @@ client.endpoints().object().getAndReturnWithDocumentedUnknownType( ```java client.endpoints().object().getAndReturnMapOfDocumentedUnknownType( new HashMap() {{ - put("string", new + put("string", DocumentedUnknownType.of(new HashMap() {{put("key", "value"); - }}); + }})); }} ); ``` diff --git a/seed/java-sdk/exhaustive/custom-interceptors/snippet.json b/seed/java-sdk/exhaustive/custom-interceptors/snippet.json index dda9334c8aae..57f312c65e28 100644 --- a/seed/java-sdk/exhaustive/custom-interceptors/snippet.json +++ b/seed/java-sdk/exhaustive/custom-interceptors/snippet.json @@ -321,8 +321,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n" } }, { @@ -334,8 +334,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n" } }, { diff --git a/seed/java-sdk/exhaustive/custom-interceptors/src/main/java/com/seed/exhaustive/core/WrappedAlias.java b/seed/java-sdk/exhaustive/custom-interceptors/src/main/java/com/seed/exhaustive/core/WrappedAlias.java new file mode 100644 index 000000000000..8cca824d16c6 --- /dev/null +++ b/seed/java-sdk/exhaustive/custom-interceptors/src/main/java/com/seed/exhaustive/core/WrappedAlias.java @@ -0,0 +1,8 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.core; + +public interface WrappedAlias { + Object get(); +} diff --git a/seed/java-sdk/exhaustive/custom-interceptors/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java b/seed/java-sdk/exhaustive/custom-interceptors/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java new file mode 100644 index 000000000000..2c678d4b52d9 --- /dev/null +++ b/seed/java-sdk/exhaustive/custom-interceptors/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java @@ -0,0 +1,42 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.resources.types.object.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.seed.exhaustive.core.WrappedAlias; + +public final class DocumentedUnknownType implements WrappedAlias { + private final Object value; + + private DocumentedUnknownType(Object value) { + this.value = value; + } + + @JsonValue + public Object get() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object other) { + return this == other + || (other instanceof DocumentedUnknownType && this.value.equals(((DocumentedUnknownType) other).value)); + } + + @java.lang.Override + public int hashCode() { + return value.hashCode(); + } + + @java.lang.Override + public String toString() { + return value.toString(); + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DocumentedUnknownType of(Object value) { + return new DocumentedUnknownType(value); + } +} diff --git a/seed/java-sdk/exhaustive/custom-interceptors/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java b/seed/java-sdk/exhaustive/custom-interceptors/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java index 8f43716682d1..4588cd4d078e 100644 --- a/seed/java-sdk/exhaustive/custom-interceptors/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java +++ b/seed/java-sdk/exhaustive/custom-interceptors/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java @@ -14,21 +14,23 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = ObjectWithDocumentedUnknownType.Builder.class) public final class ObjectWithDocumentedUnknownType { - private final Object documentedUnknownType; + private final DocumentedUnknownType documentedUnknownType; private final Map additionalProperties; - private ObjectWithDocumentedUnknownType(Object documentedUnknownType, Map additionalProperties) { + private ObjectWithDocumentedUnknownType( + DocumentedUnknownType documentedUnknownType, Map additionalProperties) { this.documentedUnknownType = documentedUnknownType; this.additionalProperties = additionalProperties; } @JsonProperty("documentedUnknownType") - public Object getDocumentedUnknownType() { + public DocumentedUnknownType getDocumentedUnknownType() { return documentedUnknownType; } @@ -62,7 +64,7 @@ public static DocumentedUnknownTypeStage builder() { } public interface DocumentedUnknownTypeStage { - _FinalStage documentedUnknownType(Object documentedUnknownType); + _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType); Builder from(ObjectWithDocumentedUnknownType other); } @@ -77,7 +79,7 @@ public interface _FinalStage { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder implements DocumentedUnknownTypeStage, _FinalStage { - private Object documentedUnknownType; + private DocumentedUnknownType documentedUnknownType; @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -92,8 +94,9 @@ public Builder from(ObjectWithDocumentedUnknownType other) { @java.lang.Override @JsonSetter("documentedUnknownType") - public _FinalStage documentedUnknownType(Object documentedUnknownType) { - this.documentedUnknownType = documentedUnknownType; + public _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType) { + this.documentedUnknownType = + Objects.requireNonNull(documentedUnknownType, "documentedUnknownType must not be null"); return this; } diff --git a/seed/java-sdk/exhaustive/custom-interceptors/src/main/java/com/snippets/Example24.java b/seed/java-sdk/exhaustive/custom-interceptors/src/main/java/com/snippets/Example24.java index 6a53c41d7107..f667d8f4e99a 100644 --- a/seed/java-sdk/exhaustive/custom-interceptors/src/main/java/com/snippets/Example24.java +++ b/seed/java-sdk/exhaustive/custom-interceptors/src/main/java/com/snippets/Example24.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType; import java.util.HashMap; @@ -14,11 +15,11 @@ public static void main(String[] args) { client.endpoints() .object() .getAndReturnWithDocumentedUnknownType(ObjectWithDocumentedUnknownType.builder() - .documentedUnknownType(new HashMap() { + .documentedUnknownType(DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }) + })) .build()); } } diff --git a/seed/java-sdk/exhaustive/custom-interceptors/src/main/java/com/snippets/Example25.java b/seed/java-sdk/exhaustive/custom-interceptors/src/main/java/com/snippets/Example25.java index 647c450adce3..4432fc0557f0 100644 --- a/seed/java-sdk/exhaustive/custom-interceptors/src/main/java/com/snippets/Example25.java +++ b/seed/java-sdk/exhaustive/custom-interceptors/src/main/java/com/snippets/Example25.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import java.util.HashMap; public class Example25 { @@ -12,11 +13,11 @@ public static void main(String[] args) { client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(new HashMap() { { - put("string", new HashMap() { + put("string", DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }); + })); } }); } diff --git a/seed/java-sdk/exhaustive/custom-license/reference.md b/seed/java-sdk/exhaustive/custom-license/reference.md index 78bb1dc37e27..72a2baa499f7 100644 --- a/seed/java-sdk/exhaustive/custom-license/reference.md +++ b/seed/java-sdk/exhaustive/custom-license/reference.md @@ -1305,9 +1305,11 @@ client.endpoints().object().getAndReturnWithUnknownField( client.endpoints().object().getAndReturnWithDocumentedUnknownType( ObjectWithDocumentedUnknownType .builder() - .documentedUnknownType(new + .documentedUnknownType( + DocumentedUnknownType.of(new HashMap() {{put("key", "value"); }}) + ) .build() ); ``` @@ -1351,9 +1353,9 @@ client.endpoints().object().getAndReturnWithDocumentedUnknownType( ```java client.endpoints().object().getAndReturnMapOfDocumentedUnknownType( new HashMap() {{ - put("string", new + put("string", DocumentedUnknownType.of(new HashMap() {{put("key", "value"); - }}); + }})); }} ); ``` diff --git a/seed/java-sdk/exhaustive/custom-license/snippet.json b/seed/java-sdk/exhaustive/custom-license/snippet.json index dda9334c8aae..57f312c65e28 100644 --- a/seed/java-sdk/exhaustive/custom-license/snippet.json +++ b/seed/java-sdk/exhaustive/custom-license/snippet.json @@ -321,8 +321,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n" } }, { @@ -334,8 +334,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n" } }, { diff --git a/seed/java-sdk/exhaustive/custom-license/src/main/java/com/seed/exhaustive/core/WrappedAlias.java b/seed/java-sdk/exhaustive/custom-license/src/main/java/com/seed/exhaustive/core/WrappedAlias.java new file mode 100644 index 000000000000..8cca824d16c6 --- /dev/null +++ b/seed/java-sdk/exhaustive/custom-license/src/main/java/com/seed/exhaustive/core/WrappedAlias.java @@ -0,0 +1,8 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.core; + +public interface WrappedAlias { + Object get(); +} diff --git a/seed/java-sdk/exhaustive/custom-license/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java b/seed/java-sdk/exhaustive/custom-license/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java new file mode 100644 index 000000000000..2c678d4b52d9 --- /dev/null +++ b/seed/java-sdk/exhaustive/custom-license/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java @@ -0,0 +1,42 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.resources.types.object.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.seed.exhaustive.core.WrappedAlias; + +public final class DocumentedUnknownType implements WrappedAlias { + private final Object value; + + private DocumentedUnknownType(Object value) { + this.value = value; + } + + @JsonValue + public Object get() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object other) { + return this == other + || (other instanceof DocumentedUnknownType && this.value.equals(((DocumentedUnknownType) other).value)); + } + + @java.lang.Override + public int hashCode() { + return value.hashCode(); + } + + @java.lang.Override + public String toString() { + return value.toString(); + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DocumentedUnknownType of(Object value) { + return new DocumentedUnknownType(value); + } +} diff --git a/seed/java-sdk/exhaustive/custom-license/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java b/seed/java-sdk/exhaustive/custom-license/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java index 8f43716682d1..4588cd4d078e 100644 --- a/seed/java-sdk/exhaustive/custom-license/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java +++ b/seed/java-sdk/exhaustive/custom-license/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java @@ -14,21 +14,23 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = ObjectWithDocumentedUnknownType.Builder.class) public final class ObjectWithDocumentedUnknownType { - private final Object documentedUnknownType; + private final DocumentedUnknownType documentedUnknownType; private final Map additionalProperties; - private ObjectWithDocumentedUnknownType(Object documentedUnknownType, Map additionalProperties) { + private ObjectWithDocumentedUnknownType( + DocumentedUnknownType documentedUnknownType, Map additionalProperties) { this.documentedUnknownType = documentedUnknownType; this.additionalProperties = additionalProperties; } @JsonProperty("documentedUnknownType") - public Object getDocumentedUnknownType() { + public DocumentedUnknownType getDocumentedUnknownType() { return documentedUnknownType; } @@ -62,7 +64,7 @@ public static DocumentedUnknownTypeStage builder() { } public interface DocumentedUnknownTypeStage { - _FinalStage documentedUnknownType(Object documentedUnknownType); + _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType); Builder from(ObjectWithDocumentedUnknownType other); } @@ -77,7 +79,7 @@ public interface _FinalStage { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder implements DocumentedUnknownTypeStage, _FinalStage { - private Object documentedUnknownType; + private DocumentedUnknownType documentedUnknownType; @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -92,8 +94,9 @@ public Builder from(ObjectWithDocumentedUnknownType other) { @java.lang.Override @JsonSetter("documentedUnknownType") - public _FinalStage documentedUnknownType(Object documentedUnknownType) { - this.documentedUnknownType = documentedUnknownType; + public _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType) { + this.documentedUnknownType = + Objects.requireNonNull(documentedUnknownType, "documentedUnknownType must not be null"); return this; } diff --git a/seed/java-sdk/exhaustive/custom-license/src/main/java/com/snippets/Example24.java b/seed/java-sdk/exhaustive/custom-license/src/main/java/com/snippets/Example24.java index 6a53c41d7107..f667d8f4e99a 100644 --- a/seed/java-sdk/exhaustive/custom-license/src/main/java/com/snippets/Example24.java +++ b/seed/java-sdk/exhaustive/custom-license/src/main/java/com/snippets/Example24.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType; import java.util.HashMap; @@ -14,11 +15,11 @@ public static void main(String[] args) { client.endpoints() .object() .getAndReturnWithDocumentedUnknownType(ObjectWithDocumentedUnknownType.builder() - .documentedUnknownType(new HashMap() { + .documentedUnknownType(DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }) + })) .build()); } } diff --git a/seed/java-sdk/exhaustive/custom-license/src/main/java/com/snippets/Example25.java b/seed/java-sdk/exhaustive/custom-license/src/main/java/com/snippets/Example25.java index 647c450adce3..4432fc0557f0 100644 --- a/seed/java-sdk/exhaustive/custom-license/src/main/java/com/snippets/Example25.java +++ b/seed/java-sdk/exhaustive/custom-license/src/main/java/com/snippets/Example25.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import java.util.HashMap; public class Example25 { @@ -12,11 +13,11 @@ public static void main(String[] args) { client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(new HashMap() { { - put("string", new HashMap() { + put("string", DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }); + })); } }); } diff --git a/seed/java-sdk/exhaustive/custom-plugins/reference.md b/seed/java-sdk/exhaustive/custom-plugins/reference.md index f5e12e1b27ea..1cb3c5c467fc 100644 --- a/seed/java-sdk/exhaustive/custom-plugins/reference.md +++ b/seed/java-sdk/exhaustive/custom-plugins/reference.md @@ -1305,9 +1305,11 @@ client.endpoints().object().getAndReturnWithUnknownField( client.endpoints().object().getAndReturnWithDocumentedUnknownType( ObjectWithDocumentedUnknownType .builder() - .documentedUnknownType(new + .documentedUnknownType( + DocumentedUnknownType.of(new HashMap() {{put("key", "value"); }}) + ) .build() ); ``` @@ -1351,9 +1353,9 @@ client.endpoints().object().getAndReturnWithDocumentedUnknownType( ```java client.endpoints().object().getAndReturnMapOfDocumentedUnknownType( new HashMap() {{ - put("string", new + put("string", DocumentedUnknownType.of(new HashMap() {{put("key", "value"); - }}); + }})); }} ); ``` diff --git a/seed/java-sdk/exhaustive/custom-plugins/snippet.json b/seed/java-sdk/exhaustive/custom-plugins/snippet.json index 14b83a1f428d..b5e4f4db8655 100644 --- a/seed/java-sdk/exhaustive/custom-plugins/snippet.json +++ b/seed/java-sdk/exhaustive/custom-plugins/snippet.json @@ -321,8 +321,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n" } }, { @@ -334,8 +334,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n" } }, { diff --git a/seed/java-sdk/exhaustive/custom-plugins/src/main/java/com/seed/exhaustive/core/WrappedAlias.java b/seed/java-sdk/exhaustive/custom-plugins/src/main/java/com/seed/exhaustive/core/WrappedAlias.java new file mode 100644 index 000000000000..8cca824d16c6 --- /dev/null +++ b/seed/java-sdk/exhaustive/custom-plugins/src/main/java/com/seed/exhaustive/core/WrappedAlias.java @@ -0,0 +1,8 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.core; + +public interface WrappedAlias { + Object get(); +} diff --git a/seed/java-sdk/exhaustive/custom-plugins/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java b/seed/java-sdk/exhaustive/custom-plugins/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java new file mode 100644 index 000000000000..2c678d4b52d9 --- /dev/null +++ b/seed/java-sdk/exhaustive/custom-plugins/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java @@ -0,0 +1,42 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.resources.types.object.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.seed.exhaustive.core.WrappedAlias; + +public final class DocumentedUnknownType implements WrappedAlias { + private final Object value; + + private DocumentedUnknownType(Object value) { + this.value = value; + } + + @JsonValue + public Object get() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object other) { + return this == other + || (other instanceof DocumentedUnknownType && this.value.equals(((DocumentedUnknownType) other).value)); + } + + @java.lang.Override + public int hashCode() { + return value.hashCode(); + } + + @java.lang.Override + public String toString() { + return value.toString(); + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DocumentedUnknownType of(Object value) { + return new DocumentedUnknownType(value); + } +} diff --git a/seed/java-sdk/exhaustive/custom-plugins/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java b/seed/java-sdk/exhaustive/custom-plugins/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java index 8f43716682d1..4588cd4d078e 100644 --- a/seed/java-sdk/exhaustive/custom-plugins/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java +++ b/seed/java-sdk/exhaustive/custom-plugins/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java @@ -14,21 +14,23 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = ObjectWithDocumentedUnknownType.Builder.class) public final class ObjectWithDocumentedUnknownType { - private final Object documentedUnknownType; + private final DocumentedUnknownType documentedUnknownType; private final Map additionalProperties; - private ObjectWithDocumentedUnknownType(Object documentedUnknownType, Map additionalProperties) { + private ObjectWithDocumentedUnknownType( + DocumentedUnknownType documentedUnknownType, Map additionalProperties) { this.documentedUnknownType = documentedUnknownType; this.additionalProperties = additionalProperties; } @JsonProperty("documentedUnknownType") - public Object getDocumentedUnknownType() { + public DocumentedUnknownType getDocumentedUnknownType() { return documentedUnknownType; } @@ -62,7 +64,7 @@ public static DocumentedUnknownTypeStage builder() { } public interface DocumentedUnknownTypeStage { - _FinalStage documentedUnknownType(Object documentedUnknownType); + _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType); Builder from(ObjectWithDocumentedUnknownType other); } @@ -77,7 +79,7 @@ public interface _FinalStage { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder implements DocumentedUnknownTypeStage, _FinalStage { - private Object documentedUnknownType; + private DocumentedUnknownType documentedUnknownType; @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -92,8 +94,9 @@ public Builder from(ObjectWithDocumentedUnknownType other) { @java.lang.Override @JsonSetter("documentedUnknownType") - public _FinalStage documentedUnknownType(Object documentedUnknownType) { - this.documentedUnknownType = documentedUnknownType; + public _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType) { + this.documentedUnknownType = + Objects.requireNonNull(documentedUnknownType, "documentedUnknownType must not be null"); return this; } diff --git a/seed/java-sdk/exhaustive/custom-plugins/src/main/java/com/snippets/Example24.java b/seed/java-sdk/exhaustive/custom-plugins/src/main/java/com/snippets/Example24.java index 6a53c41d7107..f667d8f4e99a 100644 --- a/seed/java-sdk/exhaustive/custom-plugins/src/main/java/com/snippets/Example24.java +++ b/seed/java-sdk/exhaustive/custom-plugins/src/main/java/com/snippets/Example24.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType; import java.util.HashMap; @@ -14,11 +15,11 @@ public static void main(String[] args) { client.endpoints() .object() .getAndReturnWithDocumentedUnknownType(ObjectWithDocumentedUnknownType.builder() - .documentedUnknownType(new HashMap() { + .documentedUnknownType(DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }) + })) .build()); } } diff --git a/seed/java-sdk/exhaustive/custom-plugins/src/main/java/com/snippets/Example25.java b/seed/java-sdk/exhaustive/custom-plugins/src/main/java/com/snippets/Example25.java index 647c450adce3..4432fc0557f0 100644 --- a/seed/java-sdk/exhaustive/custom-plugins/src/main/java/com/snippets/Example25.java +++ b/seed/java-sdk/exhaustive/custom-plugins/src/main/java/com/snippets/Example25.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import java.util.HashMap; public class Example25 { @@ -12,11 +13,11 @@ public static void main(String[] args) { client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(new HashMap() { { - put("string", new HashMap() { + put("string", DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }); + })); } }); } diff --git a/seed/java-sdk/exhaustive/enable-public-constructors/reference.md b/seed/java-sdk/exhaustive/enable-public-constructors/reference.md index f5e12e1b27ea..1cb3c5c467fc 100644 --- a/seed/java-sdk/exhaustive/enable-public-constructors/reference.md +++ b/seed/java-sdk/exhaustive/enable-public-constructors/reference.md @@ -1305,9 +1305,11 @@ client.endpoints().object().getAndReturnWithUnknownField( client.endpoints().object().getAndReturnWithDocumentedUnknownType( ObjectWithDocumentedUnknownType .builder() - .documentedUnknownType(new + .documentedUnknownType( + DocumentedUnknownType.of(new HashMap() {{put("key", "value"); }}) + ) .build() ); ``` @@ -1351,9 +1353,9 @@ client.endpoints().object().getAndReturnWithDocumentedUnknownType( ```java client.endpoints().object().getAndReturnMapOfDocumentedUnknownType( new HashMap() {{ - put("string", new + put("string", DocumentedUnknownType.of(new HashMap() {{put("key", "value"); - }}); + }})); }} ); ``` diff --git a/seed/java-sdk/exhaustive/enable-public-constructors/snippet.json b/seed/java-sdk/exhaustive/enable-public-constructors/snippet.json index 14b83a1f428d..b5e4f4db8655 100644 --- a/seed/java-sdk/exhaustive/enable-public-constructors/snippet.json +++ b/seed/java-sdk/exhaustive/enable-public-constructors/snippet.json @@ -321,8 +321,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n" } }, { @@ -334,8 +334,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n" } }, { diff --git a/seed/java-sdk/exhaustive/enable-public-constructors/src/main/java/com/seed/exhaustive/core/WrappedAlias.java b/seed/java-sdk/exhaustive/enable-public-constructors/src/main/java/com/seed/exhaustive/core/WrappedAlias.java new file mode 100644 index 000000000000..8cca824d16c6 --- /dev/null +++ b/seed/java-sdk/exhaustive/enable-public-constructors/src/main/java/com/seed/exhaustive/core/WrappedAlias.java @@ -0,0 +1,8 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.core; + +public interface WrappedAlias { + Object get(); +} diff --git a/seed/java-sdk/exhaustive/enable-public-constructors/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java b/seed/java-sdk/exhaustive/enable-public-constructors/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java new file mode 100644 index 000000000000..5b8a9f9ba4c1 --- /dev/null +++ b/seed/java-sdk/exhaustive/enable-public-constructors/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java @@ -0,0 +1,42 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.resources.types.object.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.seed.exhaustive.core.WrappedAlias; + +public final class DocumentedUnknownType implements WrappedAlias { + private final Object value; + + public DocumentedUnknownType(Object value) { + this.value = value; + } + + @JsonValue + public Object get() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object other) { + return this == other + || (other instanceof DocumentedUnknownType && this.value.equals(((DocumentedUnknownType) other).value)); + } + + @java.lang.Override + public int hashCode() { + return value.hashCode(); + } + + @java.lang.Override + public String toString() { + return value.toString(); + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DocumentedUnknownType of(Object value) { + return new DocumentedUnknownType(value); + } +} diff --git a/seed/java-sdk/exhaustive/enable-public-constructors/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java b/seed/java-sdk/exhaustive/enable-public-constructors/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java index ec66fcc387a8..b6433e5684d9 100644 --- a/seed/java-sdk/exhaustive/enable-public-constructors/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java +++ b/seed/java-sdk/exhaustive/enable-public-constructors/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java @@ -14,21 +14,23 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = ObjectWithDocumentedUnknownType.Builder.class) public final class ObjectWithDocumentedUnknownType { - private final Object documentedUnknownType; + private final DocumentedUnknownType documentedUnknownType; private final Map additionalProperties; - public ObjectWithDocumentedUnknownType(Object documentedUnknownType, Map additionalProperties) { + public ObjectWithDocumentedUnknownType( + DocumentedUnknownType documentedUnknownType, Map additionalProperties) { this.documentedUnknownType = documentedUnknownType; this.additionalProperties = additionalProperties; } @JsonProperty("documentedUnknownType") - public Object getDocumentedUnknownType() { + public DocumentedUnknownType getDocumentedUnknownType() { return documentedUnknownType; } @@ -62,7 +64,7 @@ public static DocumentedUnknownTypeStage builder() { } public interface DocumentedUnknownTypeStage { - _FinalStage documentedUnknownType(Object documentedUnknownType); + _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType); Builder from(ObjectWithDocumentedUnknownType other); } @@ -77,7 +79,7 @@ public interface _FinalStage { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder implements DocumentedUnknownTypeStage, _FinalStage { - private Object documentedUnknownType; + private DocumentedUnknownType documentedUnknownType; @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -92,8 +94,9 @@ public Builder from(ObjectWithDocumentedUnknownType other) { @java.lang.Override @JsonSetter("documentedUnknownType") - public _FinalStage documentedUnknownType(Object documentedUnknownType) { - this.documentedUnknownType = documentedUnknownType; + public _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType) { + this.documentedUnknownType = + Objects.requireNonNull(documentedUnknownType, "documentedUnknownType must not be null"); return this; } diff --git a/seed/java-sdk/exhaustive/enable-public-constructors/src/main/java/com/snippets/Example24.java b/seed/java-sdk/exhaustive/enable-public-constructors/src/main/java/com/snippets/Example24.java index 6a53c41d7107..f667d8f4e99a 100644 --- a/seed/java-sdk/exhaustive/enable-public-constructors/src/main/java/com/snippets/Example24.java +++ b/seed/java-sdk/exhaustive/enable-public-constructors/src/main/java/com/snippets/Example24.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType; import java.util.HashMap; @@ -14,11 +15,11 @@ public static void main(String[] args) { client.endpoints() .object() .getAndReturnWithDocumentedUnknownType(ObjectWithDocumentedUnknownType.builder() - .documentedUnknownType(new HashMap() { + .documentedUnknownType(DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }) + })) .build()); } } diff --git a/seed/java-sdk/exhaustive/enable-public-constructors/src/main/java/com/snippets/Example25.java b/seed/java-sdk/exhaustive/enable-public-constructors/src/main/java/com/snippets/Example25.java index 647c450adce3..4432fc0557f0 100644 --- a/seed/java-sdk/exhaustive/enable-public-constructors/src/main/java/com/snippets/Example25.java +++ b/seed/java-sdk/exhaustive/enable-public-constructors/src/main/java/com/snippets/Example25.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import java.util.HashMap; public class Example25 { @@ -12,11 +13,11 @@ public static void main(String[] args) { client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(new HashMap() { { - put("string", new HashMap() { + put("string", DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }); + })); } }); } diff --git a/seed/java-sdk/exhaustive/flat-package-layout/reference.md b/seed/java-sdk/exhaustive/flat-package-layout/reference.md index 78bb1dc37e27..72a2baa499f7 100644 --- a/seed/java-sdk/exhaustive/flat-package-layout/reference.md +++ b/seed/java-sdk/exhaustive/flat-package-layout/reference.md @@ -1305,9 +1305,11 @@ client.endpoints().object().getAndReturnWithUnknownField( client.endpoints().object().getAndReturnWithDocumentedUnknownType( ObjectWithDocumentedUnknownType .builder() - .documentedUnknownType(new + .documentedUnknownType( + DocumentedUnknownType.of(new HashMap() {{put("key", "value"); }}) + ) .build() ); ``` @@ -1351,9 +1353,9 @@ client.endpoints().object().getAndReturnWithDocumentedUnknownType( ```java client.endpoints().object().getAndReturnMapOfDocumentedUnknownType( new HashMap() {{ - put("string", new + put("string", DocumentedUnknownType.of(new HashMap() {{put("key", "value"); - }}); + }})); }} ); ``` diff --git a/seed/java-sdk/exhaustive/flat-package-layout/snippet.json b/seed/java-sdk/exhaustive/flat-package-layout/snippet.json index 33e36ad59151..7d6d363128de 100644 --- a/seed/java-sdk/exhaustive/flat-package-layout/snippet.json +++ b/seed/java-sdk/exhaustive/flat-package-layout/snippet.json @@ -321,8 +321,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.types.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.types.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.types.types.DocumentedUnknownType;\nimport com.seed.exhaustive.types.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.types.types.DocumentedUnknownType;\nimport com.seed.exhaustive.types.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n" } }, { @@ -334,8 +334,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.types.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.types.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n" } }, { diff --git a/seed/java-sdk/exhaustive/flat-package-layout/src/main/java/com/seed/exhaustive/core/WrappedAlias.java b/seed/java-sdk/exhaustive/flat-package-layout/src/main/java/com/seed/exhaustive/core/WrappedAlias.java new file mode 100644 index 000000000000..8cca824d16c6 --- /dev/null +++ b/seed/java-sdk/exhaustive/flat-package-layout/src/main/java/com/seed/exhaustive/core/WrappedAlias.java @@ -0,0 +1,8 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.core; + +public interface WrappedAlias { + Object get(); +} diff --git a/seed/java-sdk/exhaustive/flat-package-layout/src/main/java/com/seed/exhaustive/types/types/DocumentedUnknownType.java b/seed/java-sdk/exhaustive/flat-package-layout/src/main/java/com/seed/exhaustive/types/types/DocumentedUnknownType.java new file mode 100644 index 000000000000..7d1842df0a16 --- /dev/null +++ b/seed/java-sdk/exhaustive/flat-package-layout/src/main/java/com/seed/exhaustive/types/types/DocumentedUnknownType.java @@ -0,0 +1,42 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.types.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.seed.exhaustive.core.WrappedAlias; + +public final class DocumentedUnknownType implements WrappedAlias { + private final Object value; + + private DocumentedUnknownType(Object value) { + this.value = value; + } + + @JsonValue + public Object get() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object other) { + return this == other + || (other instanceof DocumentedUnknownType && this.value.equals(((DocumentedUnknownType) other).value)); + } + + @java.lang.Override + public int hashCode() { + return value.hashCode(); + } + + @java.lang.Override + public String toString() { + return value.toString(); + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DocumentedUnknownType of(Object value) { + return new DocumentedUnknownType(value); + } +} diff --git a/seed/java-sdk/exhaustive/flat-package-layout/src/main/java/com/seed/exhaustive/types/types/ObjectWithDocumentedUnknownType.java b/seed/java-sdk/exhaustive/flat-package-layout/src/main/java/com/seed/exhaustive/types/types/ObjectWithDocumentedUnknownType.java index 25e14cff4793..ef962ae476a9 100644 --- a/seed/java-sdk/exhaustive/flat-package-layout/src/main/java/com/seed/exhaustive/types/types/ObjectWithDocumentedUnknownType.java +++ b/seed/java-sdk/exhaustive/flat-package-layout/src/main/java/com/seed/exhaustive/types/types/ObjectWithDocumentedUnknownType.java @@ -14,21 +14,23 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = ObjectWithDocumentedUnknownType.Builder.class) public final class ObjectWithDocumentedUnknownType { - private final Object documentedUnknownType; + private final DocumentedUnknownType documentedUnknownType; private final Map additionalProperties; - private ObjectWithDocumentedUnknownType(Object documentedUnknownType, Map additionalProperties) { + private ObjectWithDocumentedUnknownType( + DocumentedUnknownType documentedUnknownType, Map additionalProperties) { this.documentedUnknownType = documentedUnknownType; this.additionalProperties = additionalProperties; } @JsonProperty("documentedUnknownType") - public Object getDocumentedUnknownType() { + public DocumentedUnknownType getDocumentedUnknownType() { return documentedUnknownType; } @@ -62,7 +64,7 @@ public static DocumentedUnknownTypeStage builder() { } public interface DocumentedUnknownTypeStage { - _FinalStage documentedUnknownType(Object documentedUnknownType); + _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType); Builder from(ObjectWithDocumentedUnknownType other); } @@ -77,7 +79,7 @@ public interface _FinalStage { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder implements DocumentedUnknownTypeStage, _FinalStage { - private Object documentedUnknownType; + private DocumentedUnknownType documentedUnknownType; @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -92,8 +94,9 @@ public Builder from(ObjectWithDocumentedUnknownType other) { @java.lang.Override @JsonSetter("documentedUnknownType") - public _FinalStage documentedUnknownType(Object documentedUnknownType) { - this.documentedUnknownType = documentedUnknownType; + public _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType) { + this.documentedUnknownType = + Objects.requireNonNull(documentedUnknownType, "documentedUnknownType must not be null"); return this; } diff --git a/seed/java-sdk/exhaustive/flat-package-layout/src/main/java/com/snippets/Example24.java b/seed/java-sdk/exhaustive/flat-package-layout/src/main/java/com/snippets/Example24.java index e7c7d5a09db0..50fad7fcd7ae 100644 --- a/seed/java-sdk/exhaustive/flat-package-layout/src/main/java/com/snippets/Example24.java +++ b/seed/java-sdk/exhaustive/flat-package-layout/src/main/java/com/snippets/Example24.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.types.types.DocumentedUnknownType; import com.seed.exhaustive.types.types.ObjectWithDocumentedUnknownType; import java.util.HashMap; @@ -14,11 +15,11 @@ public static void main(String[] args) { client.endpoints() .object() .getAndReturnWithDocumentedUnknownType(ObjectWithDocumentedUnknownType.builder() - .documentedUnknownType(new HashMap() { + .documentedUnknownType(DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }) + })) .build()); } } diff --git a/seed/java-sdk/exhaustive/flat-package-layout/src/main/java/com/snippets/Example25.java b/seed/java-sdk/exhaustive/flat-package-layout/src/main/java/com/snippets/Example25.java index 647c450adce3..83c4c3137e52 100644 --- a/seed/java-sdk/exhaustive/flat-package-layout/src/main/java/com/snippets/Example25.java +++ b/seed/java-sdk/exhaustive/flat-package-layout/src/main/java/com/snippets/Example25.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.types.types.DocumentedUnknownType; import java.util.HashMap; public class Example25 { @@ -12,11 +13,11 @@ public static void main(String[] args) { client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(new HashMap() { { - put("string", new HashMap() { + put("string", DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }); + })); } }); } diff --git a/seed/java-sdk/exhaustive/forward-compatible-enums/reference.md b/seed/java-sdk/exhaustive/forward-compatible-enums/reference.md index f5e12e1b27ea..1cb3c5c467fc 100644 --- a/seed/java-sdk/exhaustive/forward-compatible-enums/reference.md +++ b/seed/java-sdk/exhaustive/forward-compatible-enums/reference.md @@ -1305,9 +1305,11 @@ client.endpoints().object().getAndReturnWithUnknownField( client.endpoints().object().getAndReturnWithDocumentedUnknownType( ObjectWithDocumentedUnknownType .builder() - .documentedUnknownType(new + .documentedUnknownType( + DocumentedUnknownType.of(new HashMap() {{put("key", "value"); }}) + ) .build() ); ``` @@ -1351,9 +1353,9 @@ client.endpoints().object().getAndReturnWithDocumentedUnknownType( ```java client.endpoints().object().getAndReturnMapOfDocumentedUnknownType( new HashMap() {{ - put("string", new + put("string", DocumentedUnknownType.of(new HashMap() {{put("key", "value"); - }}); + }})); }} ); ``` diff --git a/seed/java-sdk/exhaustive/forward-compatible-enums/snippet.json b/seed/java-sdk/exhaustive/forward-compatible-enums/snippet.json index 14b83a1f428d..b5e4f4db8655 100644 --- a/seed/java-sdk/exhaustive/forward-compatible-enums/snippet.json +++ b/seed/java-sdk/exhaustive/forward-compatible-enums/snippet.json @@ -321,8 +321,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n" } }, { @@ -334,8 +334,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n" } }, { diff --git a/seed/java-sdk/exhaustive/forward-compatible-enums/src/main/java/com/seed/exhaustive/core/WrappedAlias.java b/seed/java-sdk/exhaustive/forward-compatible-enums/src/main/java/com/seed/exhaustive/core/WrappedAlias.java new file mode 100644 index 000000000000..8cca824d16c6 --- /dev/null +++ b/seed/java-sdk/exhaustive/forward-compatible-enums/src/main/java/com/seed/exhaustive/core/WrappedAlias.java @@ -0,0 +1,8 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.core; + +public interface WrappedAlias { + Object get(); +} diff --git a/seed/java-sdk/exhaustive/forward-compatible-enums/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java b/seed/java-sdk/exhaustive/forward-compatible-enums/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java new file mode 100644 index 000000000000..2c678d4b52d9 --- /dev/null +++ b/seed/java-sdk/exhaustive/forward-compatible-enums/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java @@ -0,0 +1,42 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.resources.types.object.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.seed.exhaustive.core.WrappedAlias; + +public final class DocumentedUnknownType implements WrappedAlias { + private final Object value; + + private DocumentedUnknownType(Object value) { + this.value = value; + } + + @JsonValue + public Object get() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object other) { + return this == other + || (other instanceof DocumentedUnknownType && this.value.equals(((DocumentedUnknownType) other).value)); + } + + @java.lang.Override + public int hashCode() { + return value.hashCode(); + } + + @java.lang.Override + public String toString() { + return value.toString(); + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DocumentedUnknownType of(Object value) { + return new DocumentedUnknownType(value); + } +} diff --git a/seed/java-sdk/exhaustive/forward-compatible-enums/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java b/seed/java-sdk/exhaustive/forward-compatible-enums/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java index 8f43716682d1..4588cd4d078e 100644 --- a/seed/java-sdk/exhaustive/forward-compatible-enums/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java +++ b/seed/java-sdk/exhaustive/forward-compatible-enums/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java @@ -14,21 +14,23 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = ObjectWithDocumentedUnknownType.Builder.class) public final class ObjectWithDocumentedUnknownType { - private final Object documentedUnknownType; + private final DocumentedUnknownType documentedUnknownType; private final Map additionalProperties; - private ObjectWithDocumentedUnknownType(Object documentedUnknownType, Map additionalProperties) { + private ObjectWithDocumentedUnknownType( + DocumentedUnknownType documentedUnknownType, Map additionalProperties) { this.documentedUnknownType = documentedUnknownType; this.additionalProperties = additionalProperties; } @JsonProperty("documentedUnknownType") - public Object getDocumentedUnknownType() { + public DocumentedUnknownType getDocumentedUnknownType() { return documentedUnknownType; } @@ -62,7 +64,7 @@ public static DocumentedUnknownTypeStage builder() { } public interface DocumentedUnknownTypeStage { - _FinalStage documentedUnknownType(Object documentedUnknownType); + _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType); Builder from(ObjectWithDocumentedUnknownType other); } @@ -77,7 +79,7 @@ public interface _FinalStage { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder implements DocumentedUnknownTypeStage, _FinalStage { - private Object documentedUnknownType; + private DocumentedUnknownType documentedUnknownType; @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -92,8 +94,9 @@ public Builder from(ObjectWithDocumentedUnknownType other) { @java.lang.Override @JsonSetter("documentedUnknownType") - public _FinalStage documentedUnknownType(Object documentedUnknownType) { - this.documentedUnknownType = documentedUnknownType; + public _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType) { + this.documentedUnknownType = + Objects.requireNonNull(documentedUnknownType, "documentedUnknownType must not be null"); return this; } diff --git a/seed/java-sdk/exhaustive/forward-compatible-enums/src/main/java/com/snippets/Example24.java b/seed/java-sdk/exhaustive/forward-compatible-enums/src/main/java/com/snippets/Example24.java index 6a53c41d7107..f667d8f4e99a 100644 --- a/seed/java-sdk/exhaustive/forward-compatible-enums/src/main/java/com/snippets/Example24.java +++ b/seed/java-sdk/exhaustive/forward-compatible-enums/src/main/java/com/snippets/Example24.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType; import java.util.HashMap; @@ -14,11 +15,11 @@ public static void main(String[] args) { client.endpoints() .object() .getAndReturnWithDocumentedUnknownType(ObjectWithDocumentedUnknownType.builder() - .documentedUnknownType(new HashMap() { + .documentedUnknownType(DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }) + })) .build()); } } diff --git a/seed/java-sdk/exhaustive/forward-compatible-enums/src/main/java/com/snippets/Example25.java b/seed/java-sdk/exhaustive/forward-compatible-enums/src/main/java/com/snippets/Example25.java index 647c450adce3..4432fc0557f0 100644 --- a/seed/java-sdk/exhaustive/forward-compatible-enums/src/main/java/com/snippets/Example25.java +++ b/seed/java-sdk/exhaustive/forward-compatible-enums/src/main/java/com/snippets/Example25.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import java.util.HashMap; public class Example25 { @@ -12,11 +13,11 @@ public static void main(String[] args) { client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(new HashMap() { { - put("string", new HashMap() { + put("string", DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }); + })); } }); } diff --git a/seed/java-sdk/exhaustive/json-include-non-empty/reference.md b/seed/java-sdk/exhaustive/json-include-non-empty/reference.md index f5e12e1b27ea..1cb3c5c467fc 100644 --- a/seed/java-sdk/exhaustive/json-include-non-empty/reference.md +++ b/seed/java-sdk/exhaustive/json-include-non-empty/reference.md @@ -1305,9 +1305,11 @@ client.endpoints().object().getAndReturnWithUnknownField( client.endpoints().object().getAndReturnWithDocumentedUnknownType( ObjectWithDocumentedUnknownType .builder() - .documentedUnknownType(new + .documentedUnknownType( + DocumentedUnknownType.of(new HashMap() {{put("key", "value"); }}) + ) .build() ); ``` @@ -1351,9 +1353,9 @@ client.endpoints().object().getAndReturnWithDocumentedUnknownType( ```java client.endpoints().object().getAndReturnMapOfDocumentedUnknownType( new HashMap() {{ - put("string", new + put("string", DocumentedUnknownType.of(new HashMap() {{put("key", "value"); - }}); + }})); }} ); ``` diff --git a/seed/java-sdk/exhaustive/json-include-non-empty/snippet.json b/seed/java-sdk/exhaustive/json-include-non-empty/snippet.json index 14b83a1f428d..b5e4f4db8655 100644 --- a/seed/java-sdk/exhaustive/json-include-non-empty/snippet.json +++ b/seed/java-sdk/exhaustive/json-include-non-empty/snippet.json @@ -321,8 +321,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n" } }, { @@ -334,8 +334,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n" } }, { diff --git a/seed/java-sdk/exhaustive/json-include-non-empty/src/main/java/com/seed/exhaustive/core/WrappedAlias.java b/seed/java-sdk/exhaustive/json-include-non-empty/src/main/java/com/seed/exhaustive/core/WrappedAlias.java new file mode 100644 index 000000000000..8cca824d16c6 --- /dev/null +++ b/seed/java-sdk/exhaustive/json-include-non-empty/src/main/java/com/seed/exhaustive/core/WrappedAlias.java @@ -0,0 +1,8 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.core; + +public interface WrappedAlias { + Object get(); +} diff --git a/seed/java-sdk/exhaustive/json-include-non-empty/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java b/seed/java-sdk/exhaustive/json-include-non-empty/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java new file mode 100644 index 000000000000..2c678d4b52d9 --- /dev/null +++ b/seed/java-sdk/exhaustive/json-include-non-empty/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java @@ -0,0 +1,42 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.resources.types.object.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.seed.exhaustive.core.WrappedAlias; + +public final class DocumentedUnknownType implements WrappedAlias { + private final Object value; + + private DocumentedUnknownType(Object value) { + this.value = value; + } + + @JsonValue + public Object get() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object other) { + return this == other + || (other instanceof DocumentedUnknownType && this.value.equals(((DocumentedUnknownType) other).value)); + } + + @java.lang.Override + public int hashCode() { + return value.hashCode(); + } + + @java.lang.Override + public String toString() { + return value.toString(); + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DocumentedUnknownType of(Object value) { + return new DocumentedUnknownType(value); + } +} diff --git a/seed/java-sdk/exhaustive/json-include-non-empty/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java b/seed/java-sdk/exhaustive/json-include-non-empty/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java index 07e101551264..e6c99b146fd9 100644 --- a/seed/java-sdk/exhaustive/json-include-non-empty/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java +++ b/seed/java-sdk/exhaustive/json-include-non-empty/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java @@ -14,21 +14,23 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_EMPTY) @JsonDeserialize(builder = ObjectWithDocumentedUnknownType.Builder.class) public final class ObjectWithDocumentedUnknownType { - private final Object documentedUnknownType; + private final DocumentedUnknownType documentedUnknownType; private final Map additionalProperties; - private ObjectWithDocumentedUnknownType(Object documentedUnknownType, Map additionalProperties) { + private ObjectWithDocumentedUnknownType( + DocumentedUnknownType documentedUnknownType, Map additionalProperties) { this.documentedUnknownType = documentedUnknownType; this.additionalProperties = additionalProperties; } @JsonProperty("documentedUnknownType") - public Object getDocumentedUnknownType() { + public DocumentedUnknownType getDocumentedUnknownType() { return documentedUnknownType; } @@ -62,7 +64,7 @@ public static DocumentedUnknownTypeStage builder() { } public interface DocumentedUnknownTypeStage { - _FinalStage documentedUnknownType(Object documentedUnknownType); + _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType); Builder from(ObjectWithDocumentedUnknownType other); } @@ -77,7 +79,7 @@ public interface _FinalStage { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder implements DocumentedUnknownTypeStage, _FinalStage { - private Object documentedUnknownType; + private DocumentedUnknownType documentedUnknownType; @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -92,8 +94,9 @@ public Builder from(ObjectWithDocumentedUnknownType other) { @java.lang.Override @JsonSetter("documentedUnknownType") - public _FinalStage documentedUnknownType(Object documentedUnknownType) { - this.documentedUnknownType = documentedUnknownType; + public _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType) { + this.documentedUnknownType = + Objects.requireNonNull(documentedUnknownType, "documentedUnknownType must not be null"); return this; } diff --git a/seed/java-sdk/exhaustive/json-include-non-empty/src/main/java/com/snippets/Example24.java b/seed/java-sdk/exhaustive/json-include-non-empty/src/main/java/com/snippets/Example24.java index 6a53c41d7107..f667d8f4e99a 100644 --- a/seed/java-sdk/exhaustive/json-include-non-empty/src/main/java/com/snippets/Example24.java +++ b/seed/java-sdk/exhaustive/json-include-non-empty/src/main/java/com/snippets/Example24.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType; import java.util.HashMap; @@ -14,11 +15,11 @@ public static void main(String[] args) { client.endpoints() .object() .getAndReturnWithDocumentedUnknownType(ObjectWithDocumentedUnknownType.builder() - .documentedUnknownType(new HashMap() { + .documentedUnknownType(DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }) + })) .build()); } } diff --git a/seed/java-sdk/exhaustive/json-include-non-empty/src/main/java/com/snippets/Example25.java b/seed/java-sdk/exhaustive/json-include-non-empty/src/main/java/com/snippets/Example25.java index 647c450adce3..4432fc0557f0 100644 --- a/seed/java-sdk/exhaustive/json-include-non-empty/src/main/java/com/snippets/Example25.java +++ b/seed/java-sdk/exhaustive/json-include-non-empty/src/main/java/com/snippets/Example25.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import java.util.HashMap; public class Example25 { @@ -12,11 +13,11 @@ public static void main(String[] args) { client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(new HashMap() { { - put("string", new HashMap() { + put("string", DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }); + })); } }); } diff --git a/seed/java-sdk/exhaustive/local-files/reference.md b/seed/java-sdk/exhaustive/local-files/reference.md index f5e12e1b27ea..1cb3c5c467fc 100644 --- a/seed/java-sdk/exhaustive/local-files/reference.md +++ b/seed/java-sdk/exhaustive/local-files/reference.md @@ -1305,9 +1305,11 @@ client.endpoints().object().getAndReturnWithUnknownField( client.endpoints().object().getAndReturnWithDocumentedUnknownType( ObjectWithDocumentedUnknownType .builder() - .documentedUnknownType(new + .documentedUnknownType( + DocumentedUnknownType.of(new HashMap() {{put("key", "value"); }}) + ) .build() ); ``` @@ -1351,9 +1353,9 @@ client.endpoints().object().getAndReturnWithDocumentedUnknownType( ```java client.endpoints().object().getAndReturnMapOfDocumentedUnknownType( new HashMap() {{ - put("string", new + put("string", DocumentedUnknownType.of(new HashMap() {{put("key", "value"); - }}); + }})); }} ); ``` diff --git a/seed/java-sdk/exhaustive/local-files/snippet.json b/seed/java-sdk/exhaustive/local-files/snippet.json index 991e6e4ccc13..122755b6ec23 100644 --- a/seed/java-sdk/exhaustive/local-files/snippet.json +++ b/seed/java-sdk/exhaustive/local-files/snippet.json @@ -321,8 +321,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.fern.sdk.SeedExhaustiveClient;\nimport com.fern.sdk.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.fern.sdk.SeedExhaustiveClient;\nimport com.fern.sdk.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.fern.sdk.SeedExhaustiveClient;\nimport com.fern.sdk.resources.types.object.types.DocumentedUnknownType;\nimport com.fern.sdk.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.fern.sdk.SeedExhaustiveClient;\nimport com.fern.sdk.resources.types.object.types.DocumentedUnknownType;\nimport com.fern.sdk.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n" } }, { @@ -334,8 +334,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.fern.sdk.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.fern.sdk.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.fern.sdk.SeedExhaustiveClient;\nimport com.fern.sdk.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.fern.sdk.SeedExhaustiveClient;\nimport com.fern.sdk.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n" } }, { diff --git a/seed/java-sdk/exhaustive/local-files/src/main/java/com/snippets/Example24.java b/seed/java-sdk/exhaustive/local-files/src/main/java/com/snippets/Example24.java index 8ab94cad12df..414682caa131 100644 --- a/seed/java-sdk/exhaustive/local-files/src/main/java/com/snippets/Example24.java +++ b/seed/java-sdk/exhaustive/local-files/src/main/java/com/snippets/Example24.java @@ -1,6 +1,7 @@ package com.snippets; import com.fern.sdk.SeedExhaustiveClient; +import com.fern.sdk.resources.types.object.types.DocumentedUnknownType; import com.fern.sdk.resources.types.object.types.ObjectWithDocumentedUnknownType; import java.util.HashMap; @@ -15,9 +16,11 @@ public static void main(String[] args) { client.endpoints().object().getAndReturnWithDocumentedUnknownType( ObjectWithDocumentedUnknownType .builder() - .documentedUnknownType(new + .documentedUnknownType( + DocumentedUnknownType.of(new HashMap() {{put("key", "value"); }}) + ) .build() ); } diff --git a/seed/java-sdk/exhaustive/local-files/src/main/java/com/snippets/Example25.java b/seed/java-sdk/exhaustive/local-files/src/main/java/com/snippets/Example25.java index da73903ae211..61fed994310a 100644 --- a/seed/java-sdk/exhaustive/local-files/src/main/java/com/snippets/Example25.java +++ b/seed/java-sdk/exhaustive/local-files/src/main/java/com/snippets/Example25.java @@ -1,6 +1,7 @@ package com.snippets; import com.fern.sdk.SeedExhaustiveClient; +import com.fern.sdk.resources.types.object.types.DocumentedUnknownType; import java.util.HashMap; public class Example25 { @@ -13,9 +14,9 @@ public static void main(String[] args) { client.endpoints().object().getAndReturnMapOfDocumentedUnknownType( new HashMap() {{ - put("string", new + put("string", DocumentedUnknownType.of(new HashMap() {{put("key", "value"); - }}); + }})); }} ); } diff --git a/seed/java-sdk/exhaustive/local-files/src/main/java/core/WrappedAlias.java b/seed/java-sdk/exhaustive/local-files/src/main/java/core/WrappedAlias.java new file mode 100644 index 000000000000..7b8732aee505 --- /dev/null +++ b/seed/java-sdk/exhaustive/local-files/src/main/java/core/WrappedAlias.java @@ -0,0 +1,9 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.fern.sdk.core; + +public interface WrappedAlias { + Object get(); +} diff --git a/seed/java-sdk/exhaustive/local-files/src/main/java/resources/types/object/types/DocumentedUnknownType.java b/seed/java-sdk/exhaustive/local-files/src/main/java/resources/types/object/types/DocumentedUnknownType.java new file mode 100644 index 000000000000..47a8a51208a7 --- /dev/null +++ b/seed/java-sdk/exhaustive/local-files/src/main/java/resources/types/object/types/DocumentedUnknownType.java @@ -0,0 +1,46 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package com.fern.sdk.resources.types.object.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.fern.sdk.core.WrappedAlias; +import java.lang.Object; +import java.lang.String; + +public final class DocumentedUnknownType implements WrappedAlias { + private final Object value; + + private DocumentedUnknownType(Object value) { + this.value = value; + } + + @JsonValue + public Object get() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object other) { + return this == other || (other instanceof DocumentedUnknownType && this.value.equals(((DocumentedUnknownType) other).value)); + } + + @java.lang.Override + public int hashCode() { + return value.hashCode(); + } + + @java.lang.Override + public String toString() { + return value.toString(); + } + + @JsonCreator( + mode = JsonCreator.Mode.DELEGATING + ) + public static DocumentedUnknownType of(Object value) { + return new DocumentedUnknownType(value); + } +} diff --git a/seed/java-sdk/exhaustive/local-files/src/main/java/resources/types/object/types/ObjectWithDocumentedUnknownType.java b/seed/java-sdk/exhaustive/local-files/src/main/java/resources/types/object/types/ObjectWithDocumentedUnknownType.java index 100e85f15e9b..d34975e7eadc 100644 --- a/seed/java-sdk/exhaustive/local-files/src/main/java/resources/types/object/types/ObjectWithDocumentedUnknownType.java +++ b/seed/java-sdk/exhaustive/local-files/src/main/java/resources/types/object/types/ObjectWithDocumentedUnknownType.java @@ -17,24 +17,25 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize( builder = ObjectWithDocumentedUnknownType.Builder.class ) public final class ObjectWithDocumentedUnknownType { - private final Object documentedUnknownType; + private final DocumentedUnknownType documentedUnknownType; private final Map additionalProperties; - private ObjectWithDocumentedUnknownType(Object documentedUnknownType, + private ObjectWithDocumentedUnknownType(DocumentedUnknownType documentedUnknownType, Map additionalProperties) { this.documentedUnknownType = documentedUnknownType; this.additionalProperties = additionalProperties; } @JsonProperty("documentedUnknownType") - public Object getDocumentedUnknownType() { + public DocumentedUnknownType getDocumentedUnknownType() { return documentedUnknownType; } @@ -68,7 +69,7 @@ public static DocumentedUnknownTypeStage builder() { } public interface DocumentedUnknownTypeStage { - _FinalStage documentedUnknownType(Object documentedUnknownType); + _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType); Builder from(ObjectWithDocumentedUnknownType other); } @@ -85,7 +86,7 @@ public interface _FinalStage { ignoreUnknown = true ) public static final class Builder implements DocumentedUnknownTypeStage, _FinalStage { - private Object documentedUnknownType; + private DocumentedUnknownType documentedUnknownType; @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -101,8 +102,8 @@ public Builder from(ObjectWithDocumentedUnknownType other) { @java.lang.Override @JsonSetter("documentedUnknownType") - public _FinalStage documentedUnknownType(Object documentedUnknownType) { - this.documentedUnknownType = documentedUnknownType; + public _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType) { + this.documentedUnknownType = Objects.requireNonNull(documentedUnknownType, "documentedUnknownType must not be null"); return this; } diff --git a/seed/java-sdk/exhaustive/no-custom-config/reference.md b/seed/java-sdk/exhaustive/no-custom-config/reference.md index 78bb1dc37e27..72a2baa499f7 100644 --- a/seed/java-sdk/exhaustive/no-custom-config/reference.md +++ b/seed/java-sdk/exhaustive/no-custom-config/reference.md @@ -1305,9 +1305,11 @@ client.endpoints().object().getAndReturnWithUnknownField( client.endpoints().object().getAndReturnWithDocumentedUnknownType( ObjectWithDocumentedUnknownType .builder() - .documentedUnknownType(new + .documentedUnknownType( + DocumentedUnknownType.of(new HashMap() {{put("key", "value"); }}) + ) .build() ); ``` @@ -1351,9 +1353,9 @@ client.endpoints().object().getAndReturnWithDocumentedUnknownType( ```java client.endpoints().object().getAndReturnMapOfDocumentedUnknownType( new HashMap() {{ - put("string", new + put("string", DocumentedUnknownType.of(new HashMap() {{put("key", "value"); - }}); + }})); }} ); ``` diff --git a/seed/java-sdk/exhaustive/no-custom-config/snippet.json b/seed/java-sdk/exhaustive/no-custom-config/snippet.json index dda9334c8aae..57f312c65e28 100644 --- a/seed/java-sdk/exhaustive/no-custom-config/snippet.json +++ b/seed/java-sdk/exhaustive/no-custom-config/snippet.json @@ -321,8 +321,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n" } }, { @@ -334,8 +334,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n" } }, { diff --git a/seed/java-sdk/exhaustive/no-custom-config/src/main/java/com/seed/exhaustive/core/WrappedAlias.java b/seed/java-sdk/exhaustive/no-custom-config/src/main/java/com/seed/exhaustive/core/WrappedAlias.java new file mode 100644 index 000000000000..8cca824d16c6 --- /dev/null +++ b/seed/java-sdk/exhaustive/no-custom-config/src/main/java/com/seed/exhaustive/core/WrappedAlias.java @@ -0,0 +1,8 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.core; + +public interface WrappedAlias { + Object get(); +} diff --git a/seed/java-sdk/exhaustive/no-custom-config/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java b/seed/java-sdk/exhaustive/no-custom-config/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java new file mode 100644 index 000000000000..2c678d4b52d9 --- /dev/null +++ b/seed/java-sdk/exhaustive/no-custom-config/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java @@ -0,0 +1,42 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.resources.types.object.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.seed.exhaustive.core.WrappedAlias; + +public final class DocumentedUnknownType implements WrappedAlias { + private final Object value; + + private DocumentedUnknownType(Object value) { + this.value = value; + } + + @JsonValue + public Object get() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object other) { + return this == other + || (other instanceof DocumentedUnknownType && this.value.equals(((DocumentedUnknownType) other).value)); + } + + @java.lang.Override + public int hashCode() { + return value.hashCode(); + } + + @java.lang.Override + public String toString() { + return value.toString(); + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DocumentedUnknownType of(Object value) { + return new DocumentedUnknownType(value); + } +} diff --git a/seed/java-sdk/exhaustive/no-custom-config/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java b/seed/java-sdk/exhaustive/no-custom-config/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java index 8f43716682d1..4588cd4d078e 100644 --- a/seed/java-sdk/exhaustive/no-custom-config/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java +++ b/seed/java-sdk/exhaustive/no-custom-config/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java @@ -14,21 +14,23 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = ObjectWithDocumentedUnknownType.Builder.class) public final class ObjectWithDocumentedUnknownType { - private final Object documentedUnknownType; + private final DocumentedUnknownType documentedUnknownType; private final Map additionalProperties; - private ObjectWithDocumentedUnknownType(Object documentedUnknownType, Map additionalProperties) { + private ObjectWithDocumentedUnknownType( + DocumentedUnknownType documentedUnknownType, Map additionalProperties) { this.documentedUnknownType = documentedUnknownType; this.additionalProperties = additionalProperties; } @JsonProperty("documentedUnknownType") - public Object getDocumentedUnknownType() { + public DocumentedUnknownType getDocumentedUnknownType() { return documentedUnknownType; } @@ -62,7 +64,7 @@ public static DocumentedUnknownTypeStage builder() { } public interface DocumentedUnknownTypeStage { - _FinalStage documentedUnknownType(Object documentedUnknownType); + _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType); Builder from(ObjectWithDocumentedUnknownType other); } @@ -77,7 +79,7 @@ public interface _FinalStage { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder implements DocumentedUnknownTypeStage, _FinalStage { - private Object documentedUnknownType; + private DocumentedUnknownType documentedUnknownType; @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -92,8 +94,9 @@ public Builder from(ObjectWithDocumentedUnknownType other) { @java.lang.Override @JsonSetter("documentedUnknownType") - public _FinalStage documentedUnknownType(Object documentedUnknownType) { - this.documentedUnknownType = documentedUnknownType; + public _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType) { + this.documentedUnknownType = + Objects.requireNonNull(documentedUnknownType, "documentedUnknownType must not be null"); return this; } diff --git a/seed/java-sdk/exhaustive/no-custom-config/src/main/java/com/snippets/Example24.java b/seed/java-sdk/exhaustive/no-custom-config/src/main/java/com/snippets/Example24.java index 6a53c41d7107..f667d8f4e99a 100644 --- a/seed/java-sdk/exhaustive/no-custom-config/src/main/java/com/snippets/Example24.java +++ b/seed/java-sdk/exhaustive/no-custom-config/src/main/java/com/snippets/Example24.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType; import java.util.HashMap; @@ -14,11 +15,11 @@ public static void main(String[] args) { client.endpoints() .object() .getAndReturnWithDocumentedUnknownType(ObjectWithDocumentedUnknownType.builder() - .documentedUnknownType(new HashMap() { + .documentedUnknownType(DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }) + })) .build()); } } diff --git a/seed/java-sdk/exhaustive/no-custom-config/src/main/java/com/snippets/Example25.java b/seed/java-sdk/exhaustive/no-custom-config/src/main/java/com/snippets/Example25.java index 647c450adce3..4432fc0557f0 100644 --- a/seed/java-sdk/exhaustive/no-custom-config/src/main/java/com/snippets/Example25.java +++ b/seed/java-sdk/exhaustive/no-custom-config/src/main/java/com/snippets/Example25.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import java.util.HashMap; public class Example25 { @@ -12,11 +13,11 @@ public static void main(String[] args) { client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(new HashMap() { { - put("string", new HashMap() { + put("string", DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }); + })); } }); } diff --git a/seed/java-sdk/exhaustive/no-custom-config/src/test/java/com/seed/exhaustive/EndpointsObjectWireTest.java b/seed/java-sdk/exhaustive/no-custom-config/src/test/java/com/seed/exhaustive/EndpointsObjectWireTest.java index ea85f9de8350..57cef11cc8f2 100644 --- a/seed/java-sdk/exhaustive/no-custom-config/src/test/java/com/seed/exhaustive/EndpointsObjectWireTest.java +++ b/seed/java-sdk/exhaustive/no-custom-config/src/test/java/com/seed/exhaustive/EndpointsObjectWireTest.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.seed.exhaustive.core.ObjectMappers; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import com.seed.exhaustive.resources.types.object.types.NestedObjectWithOptionalField; import com.seed.exhaustive.resources.types.object.types.NestedObjectWithRequiredField; import com.seed.exhaustive.resources.types.object.types.ObjectWithDatetimeLikeString; @@ -899,11 +900,11 @@ public void testGetAndReturnWithDocumentedUnknownType() throws Exception { ObjectWithDocumentedUnknownType response = client.endpoints() .object() .getAndReturnWithDocumentedUnknownType(ObjectWithDocumentedUnknownType.builder() - .documentedUnknownType(new HashMap() { + .documentedUnknownType(DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }) + })) .build()); RecordedRequest request = server.takeRequest(); Assertions.assertNotNull(request); @@ -982,11 +983,11 @@ public void testGetAndReturnMapOfDocumentedUnknownType() throws Exception { .object() .getAndReturnMapOfDocumentedUnknownType(new HashMap() { { - put("string", new HashMap() { + put("string", DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }); + })); } }); RecordedRequest request = server.takeRequest(); diff --git a/seed/java-sdk/exhaustive/publish-to/reference.md b/seed/java-sdk/exhaustive/publish-to/reference.md index 78bb1dc37e27..72a2baa499f7 100644 --- a/seed/java-sdk/exhaustive/publish-to/reference.md +++ b/seed/java-sdk/exhaustive/publish-to/reference.md @@ -1305,9 +1305,11 @@ client.endpoints().object().getAndReturnWithUnknownField( client.endpoints().object().getAndReturnWithDocumentedUnknownType( ObjectWithDocumentedUnknownType .builder() - .documentedUnknownType(new + .documentedUnknownType( + DocumentedUnknownType.of(new HashMap() {{put("key", "value"); }}) + ) .build() ); ``` @@ -1351,9 +1353,9 @@ client.endpoints().object().getAndReturnWithDocumentedUnknownType( ```java client.endpoints().object().getAndReturnMapOfDocumentedUnknownType( new HashMap() {{ - put("string", new + put("string", DocumentedUnknownType.of(new HashMap() {{put("key", "value"); - }}); + }})); }} ); ``` diff --git a/seed/java-sdk/exhaustive/publish-to/snippet.json b/seed/java-sdk/exhaustive/publish-to/snippet.json index dda9334c8aae..57f312c65e28 100644 --- a/seed/java-sdk/exhaustive/publish-to/snippet.json +++ b/seed/java-sdk/exhaustive/publish-to/snippet.json @@ -321,8 +321,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n" } }, { @@ -334,8 +334,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n" } }, { diff --git a/seed/java-sdk/exhaustive/publish-to/src/main/java/com/seed/exhaustive/core/WrappedAlias.java b/seed/java-sdk/exhaustive/publish-to/src/main/java/com/seed/exhaustive/core/WrappedAlias.java new file mode 100644 index 000000000000..8cca824d16c6 --- /dev/null +++ b/seed/java-sdk/exhaustive/publish-to/src/main/java/com/seed/exhaustive/core/WrappedAlias.java @@ -0,0 +1,8 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.core; + +public interface WrappedAlias { + Object get(); +} diff --git a/seed/java-sdk/exhaustive/publish-to/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java b/seed/java-sdk/exhaustive/publish-to/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java new file mode 100644 index 000000000000..2c678d4b52d9 --- /dev/null +++ b/seed/java-sdk/exhaustive/publish-to/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java @@ -0,0 +1,42 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.resources.types.object.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.seed.exhaustive.core.WrappedAlias; + +public final class DocumentedUnknownType implements WrappedAlias { + private final Object value; + + private DocumentedUnknownType(Object value) { + this.value = value; + } + + @JsonValue + public Object get() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object other) { + return this == other + || (other instanceof DocumentedUnknownType && this.value.equals(((DocumentedUnknownType) other).value)); + } + + @java.lang.Override + public int hashCode() { + return value.hashCode(); + } + + @java.lang.Override + public String toString() { + return value.toString(); + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DocumentedUnknownType of(Object value) { + return new DocumentedUnknownType(value); + } +} diff --git a/seed/java-sdk/exhaustive/publish-to/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java b/seed/java-sdk/exhaustive/publish-to/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java index 8f43716682d1..4588cd4d078e 100644 --- a/seed/java-sdk/exhaustive/publish-to/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java +++ b/seed/java-sdk/exhaustive/publish-to/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java @@ -14,21 +14,23 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = ObjectWithDocumentedUnknownType.Builder.class) public final class ObjectWithDocumentedUnknownType { - private final Object documentedUnknownType; + private final DocumentedUnknownType documentedUnknownType; private final Map additionalProperties; - private ObjectWithDocumentedUnknownType(Object documentedUnknownType, Map additionalProperties) { + private ObjectWithDocumentedUnknownType( + DocumentedUnknownType documentedUnknownType, Map additionalProperties) { this.documentedUnknownType = documentedUnknownType; this.additionalProperties = additionalProperties; } @JsonProperty("documentedUnknownType") - public Object getDocumentedUnknownType() { + public DocumentedUnknownType getDocumentedUnknownType() { return documentedUnknownType; } @@ -62,7 +64,7 @@ public static DocumentedUnknownTypeStage builder() { } public interface DocumentedUnknownTypeStage { - _FinalStage documentedUnknownType(Object documentedUnknownType); + _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType); Builder from(ObjectWithDocumentedUnknownType other); } @@ -77,7 +79,7 @@ public interface _FinalStage { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder implements DocumentedUnknownTypeStage, _FinalStage { - private Object documentedUnknownType; + private DocumentedUnknownType documentedUnknownType; @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -92,8 +94,9 @@ public Builder from(ObjectWithDocumentedUnknownType other) { @java.lang.Override @JsonSetter("documentedUnknownType") - public _FinalStage documentedUnknownType(Object documentedUnknownType) { - this.documentedUnknownType = documentedUnknownType; + public _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType) { + this.documentedUnknownType = + Objects.requireNonNull(documentedUnknownType, "documentedUnknownType must not be null"); return this; } diff --git a/seed/java-sdk/exhaustive/publish-to/src/main/java/com/snippets/Example24.java b/seed/java-sdk/exhaustive/publish-to/src/main/java/com/snippets/Example24.java index 6a53c41d7107..f667d8f4e99a 100644 --- a/seed/java-sdk/exhaustive/publish-to/src/main/java/com/snippets/Example24.java +++ b/seed/java-sdk/exhaustive/publish-to/src/main/java/com/snippets/Example24.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType; import java.util.HashMap; @@ -14,11 +15,11 @@ public static void main(String[] args) { client.endpoints() .object() .getAndReturnWithDocumentedUnknownType(ObjectWithDocumentedUnknownType.builder() - .documentedUnknownType(new HashMap() { + .documentedUnknownType(DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }) + })) .build()); } } diff --git a/seed/java-sdk/exhaustive/publish-to/src/main/java/com/snippets/Example25.java b/seed/java-sdk/exhaustive/publish-to/src/main/java/com/snippets/Example25.java index 647c450adce3..4432fc0557f0 100644 --- a/seed/java-sdk/exhaustive/publish-to/src/main/java/com/snippets/Example25.java +++ b/seed/java-sdk/exhaustive/publish-to/src/main/java/com/snippets/Example25.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import java.util.HashMap; public class Example25 { @@ -12,11 +13,11 @@ public static void main(String[] args) { client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(new HashMap() { { - put("string", new HashMap() { + put("string", DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }); + })); } }); } diff --git a/seed/java-sdk/exhaustive/signed_publish/reference.md b/seed/java-sdk/exhaustive/signed_publish/reference.md index 78bb1dc37e27..72a2baa499f7 100644 --- a/seed/java-sdk/exhaustive/signed_publish/reference.md +++ b/seed/java-sdk/exhaustive/signed_publish/reference.md @@ -1305,9 +1305,11 @@ client.endpoints().object().getAndReturnWithUnknownField( client.endpoints().object().getAndReturnWithDocumentedUnknownType( ObjectWithDocumentedUnknownType .builder() - .documentedUnknownType(new + .documentedUnknownType( + DocumentedUnknownType.of(new HashMap() {{put("key", "value"); }}) + ) .build() ); ``` @@ -1351,9 +1353,9 @@ client.endpoints().object().getAndReturnWithDocumentedUnknownType( ```java client.endpoints().object().getAndReturnMapOfDocumentedUnknownType( new HashMap() {{ - put("string", new + put("string", DocumentedUnknownType.of(new HashMap() {{put("key", "value"); - }}); + }})); }} ); ``` diff --git a/seed/java-sdk/exhaustive/signed_publish/snippet.json b/seed/java-sdk/exhaustive/signed_publish/snippet.json index dda9334c8aae..57f312c65e28 100644 --- a/seed/java-sdk/exhaustive/signed_publish/snippet.json +++ b/seed/java-sdk/exhaustive/signed_publish/snippet.json @@ -321,8 +321,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(new \n HashMap() {{put(\"key\", \"value\");\n }})\n .build()\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnWithDocumentedUnknownType(\n ObjectWithDocumentedUnknownType\n .builder()\n .documentedUnknownType(\n DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }})\n )\n .build()\n );\n }\n}\n" } }, { @@ -334,8 +334,8 @@ }, "snippet": { "type": "java", - "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n", - "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", new \n HashMap() {{put(\"key\", \"value\");\n }});\n }}\n );\n }\n}\n" + "sync_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n", + "async_client": "package com.example.usage;\n\nimport com.seed.exhaustive.SeedExhaustiveClient;\nimport com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType;\nimport java.util.HashMap;\n\npublic class Example {\n public static void main(String[] args) {\n SeedExhaustiveClient client = SeedExhaustiveClient\n .builder()\n .token(\"\")\n .build();\n\n client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(\n new HashMap() {{\n put(\"string\", DocumentedUnknownType.of(new \n HashMap() {{put(\"key\", \"value\");\n }}));\n }}\n );\n }\n}\n" } }, { diff --git a/seed/java-sdk/exhaustive/signed_publish/src/main/java/com/seed/exhaustive/core/WrappedAlias.java b/seed/java-sdk/exhaustive/signed_publish/src/main/java/com/seed/exhaustive/core/WrappedAlias.java new file mode 100644 index 000000000000..8cca824d16c6 --- /dev/null +++ b/seed/java-sdk/exhaustive/signed_publish/src/main/java/com/seed/exhaustive/core/WrappedAlias.java @@ -0,0 +1,8 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.core; + +public interface WrappedAlias { + Object get(); +} diff --git a/seed/java-sdk/exhaustive/signed_publish/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java b/seed/java-sdk/exhaustive/signed_publish/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java new file mode 100644 index 000000000000..2c678d4b52d9 --- /dev/null +++ b/seed/java-sdk/exhaustive/signed_publish/src/main/java/com/seed/exhaustive/resources/types/object/types/DocumentedUnknownType.java @@ -0,0 +1,42 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.exhaustive.resources.types.object.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.seed.exhaustive.core.WrappedAlias; + +public final class DocumentedUnknownType implements WrappedAlias { + private final Object value; + + private DocumentedUnknownType(Object value) { + this.value = value; + } + + @JsonValue + public Object get() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object other) { + return this == other + || (other instanceof DocumentedUnknownType && this.value.equals(((DocumentedUnknownType) other).value)); + } + + @java.lang.Override + public int hashCode() { + return value.hashCode(); + } + + @java.lang.Override + public String toString() { + return value.toString(); + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DocumentedUnknownType of(Object value) { + return new DocumentedUnknownType(value); + } +} diff --git a/seed/java-sdk/exhaustive/signed_publish/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java b/seed/java-sdk/exhaustive/signed_publish/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java index 8f43716682d1..4588cd4d078e 100644 --- a/seed/java-sdk/exhaustive/signed_publish/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java +++ b/seed/java-sdk/exhaustive/signed_publish/src/main/java/com/seed/exhaustive/resources/types/object/types/ObjectWithDocumentedUnknownType.java @@ -14,21 +14,23 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = ObjectWithDocumentedUnknownType.Builder.class) public final class ObjectWithDocumentedUnknownType { - private final Object documentedUnknownType; + private final DocumentedUnknownType documentedUnknownType; private final Map additionalProperties; - private ObjectWithDocumentedUnknownType(Object documentedUnknownType, Map additionalProperties) { + private ObjectWithDocumentedUnknownType( + DocumentedUnknownType documentedUnknownType, Map additionalProperties) { this.documentedUnknownType = documentedUnknownType; this.additionalProperties = additionalProperties; } @JsonProperty("documentedUnknownType") - public Object getDocumentedUnknownType() { + public DocumentedUnknownType getDocumentedUnknownType() { return documentedUnknownType; } @@ -62,7 +64,7 @@ public static DocumentedUnknownTypeStage builder() { } public interface DocumentedUnknownTypeStage { - _FinalStage documentedUnknownType(Object documentedUnknownType); + _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType); Builder from(ObjectWithDocumentedUnknownType other); } @@ -77,7 +79,7 @@ public interface _FinalStage { @JsonIgnoreProperties(ignoreUnknown = true) public static final class Builder implements DocumentedUnknownTypeStage, _FinalStage { - private Object documentedUnknownType; + private DocumentedUnknownType documentedUnknownType; @JsonAnySetter private Map additionalProperties = new HashMap<>(); @@ -92,8 +94,9 @@ public Builder from(ObjectWithDocumentedUnknownType other) { @java.lang.Override @JsonSetter("documentedUnknownType") - public _FinalStage documentedUnknownType(Object documentedUnknownType) { - this.documentedUnknownType = documentedUnknownType; + public _FinalStage documentedUnknownType(@NotNull DocumentedUnknownType documentedUnknownType) { + this.documentedUnknownType = + Objects.requireNonNull(documentedUnknownType, "documentedUnknownType must not be null"); return this; } diff --git a/seed/java-sdk/exhaustive/signed_publish/src/main/java/com/snippets/Example24.java b/seed/java-sdk/exhaustive/signed_publish/src/main/java/com/snippets/Example24.java index 6a53c41d7107..f667d8f4e99a 100644 --- a/seed/java-sdk/exhaustive/signed_publish/src/main/java/com/snippets/Example24.java +++ b/seed/java-sdk/exhaustive/signed_publish/src/main/java/com/snippets/Example24.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import com.seed.exhaustive.resources.types.object.types.ObjectWithDocumentedUnknownType; import java.util.HashMap; @@ -14,11 +15,11 @@ public static void main(String[] args) { client.endpoints() .object() .getAndReturnWithDocumentedUnknownType(ObjectWithDocumentedUnknownType.builder() - .documentedUnknownType(new HashMap() { + .documentedUnknownType(DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }) + })) .build()); } } diff --git a/seed/java-sdk/exhaustive/signed_publish/src/main/java/com/snippets/Example25.java b/seed/java-sdk/exhaustive/signed_publish/src/main/java/com/snippets/Example25.java index 647c450adce3..4432fc0557f0 100644 --- a/seed/java-sdk/exhaustive/signed_publish/src/main/java/com/snippets/Example25.java +++ b/seed/java-sdk/exhaustive/signed_publish/src/main/java/com/snippets/Example25.java @@ -1,6 +1,7 @@ package com.snippets; import com.seed.exhaustive.SeedExhaustiveClient; +import com.seed.exhaustive.resources.types.object.types.DocumentedUnknownType; import java.util.HashMap; public class Example25 { @@ -12,11 +13,11 @@ public static void main(String[] args) { client.endpoints().object().getAndReturnMapOfDocumentedUnknownType(new HashMap() { { - put("string", new HashMap() { + put("string", DocumentedUnknownType.of(new HashMap() { { put("key", "value"); } - }); + })); } }); } diff --git a/seed/java-sdk/unknown/src/main/java/com/seed/unknownAsAny/core/WrappedAlias.java b/seed/java-sdk/unknown/src/main/java/com/seed/unknownAsAny/core/WrappedAlias.java new file mode 100644 index 000000000000..e21bd4f7067a --- /dev/null +++ b/seed/java-sdk/unknown/src/main/java/com/seed/unknownAsAny/core/WrappedAlias.java @@ -0,0 +1,8 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.unknownAsAny.core; + +public interface WrappedAlias { + Object get(); +} diff --git a/seed/java-sdk/unknown/src/main/java/com/seed/unknownAsAny/resources/unknown/types/MyAlias.java b/seed/java-sdk/unknown/src/main/java/com/seed/unknownAsAny/resources/unknown/types/MyAlias.java new file mode 100644 index 000000000000..81d77b1a6bc6 --- /dev/null +++ b/seed/java-sdk/unknown/src/main/java/com/seed/unknownAsAny/resources/unknown/types/MyAlias.java @@ -0,0 +1,41 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.unknownAsAny.resources.unknown.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.seed.unknownAsAny.core.WrappedAlias; + +public final class MyAlias implements WrappedAlias { + private final Object value; + + private MyAlias(Object value) { + this.value = value; + } + + @JsonValue + public Object get() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object other) { + return this == other || (other instanceof MyAlias && this.value.equals(((MyAlias) other).value)); + } + + @java.lang.Override + public int hashCode() { + return value.hashCode(); + } + + @java.lang.Override + public String toString() { + return value.toString(); + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static MyAlias of(Object value) { + return new MyAlias(value); + } +} diff --git a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsContainerWireTest.php b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsContainerWireTest.php index 9ab08decacc4..11d4b1fa4f81 100644 --- a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsContainerWireTest.php +++ b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsContainerWireTest.php @@ -211,10 +211,11 @@ public function testGetAndReturnOptional(): void { */ protected function setUp(): void { parent::setUp(); + $wiremockUrl = getenv('WIREMOCK_URL') ?: 'http://localhost:8080'; $this->client = new SeedClient( token: 'test-token', options: [ - 'baseUrl' => getenv('WIREMOCK_URL') ?: 'http://localhost:8080', + 'baseUrl' => $wiremockUrl, ], ); } diff --git a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsContentTypeWireTest.php b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsContentTypeWireTest.php index c39ce3201475..649bcdadbc53 100644 --- a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsContentTypeWireTest.php +++ b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsContentTypeWireTest.php @@ -102,10 +102,11 @@ public function testPostJsonPatchContentWithCharsetType(): void { */ protected function setUp(): void { parent::setUp(); + $wiremockUrl = getenv('WIREMOCK_URL') ?: 'http://localhost:8080'; $this->client = new SeedClient( token: 'test-token', options: [ - 'baseUrl' => getenv('WIREMOCK_URL') ?: 'http://localhost:8080', + 'baseUrl' => $wiremockUrl, ], ); } diff --git a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsEnumWireTest.php b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsEnumWireTest.php index 686097e3ff13..036de06706ac 100644 --- a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsEnumWireTest.php +++ b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsEnumWireTest.php @@ -38,10 +38,11 @@ public function testGetAndReturnEnum(): void { */ protected function setUp(): void { parent::setUp(); + $wiremockUrl = getenv('WIREMOCK_URL') ?: 'http://localhost:8080'; $this->client = new SeedClient( token: 'test-token', options: [ - 'baseUrl' => getenv('WIREMOCK_URL') ?: 'http://localhost:8080', + 'baseUrl' => $wiremockUrl, ], ); } diff --git a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsHttpMethodsWireTest.php b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsHttpMethodsWireTest.php index a49f36a33eb4..73c9e748be40 100644 --- a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsHttpMethodsWireTest.php +++ b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsHttpMethodsWireTest.php @@ -151,10 +151,11 @@ public function testTestDelete(): void { */ protected function setUp(): void { parent::setUp(); + $wiremockUrl = getenv('WIREMOCK_URL') ?: 'http://localhost:8080'; $this->client = new SeedClient( token: 'test-token', options: [ - 'baseUrl' => getenv('WIREMOCK_URL') ?: 'http://localhost:8080', + 'baseUrl' => $wiremockUrl, ], ); } diff --git a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsObjectWireTest.php b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsObjectWireTest.php index b8bef76f1836..81a81586361f 100644 --- a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsObjectWireTest.php +++ b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsObjectWireTest.php @@ -379,10 +379,11 @@ public function testGetAndReturnWithDatetimeLikeString(): void { */ protected function setUp(): void { parent::setUp(); + $wiremockUrl = getenv('WIREMOCK_URL') ?: 'http://localhost:8080'; $this->client = new SeedClient( token: 'test-token', options: [ - 'baseUrl' => getenv('WIREMOCK_URL') ?: 'http://localhost:8080', + 'baseUrl' => $wiremockUrl, ], ); } diff --git a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsPaginationWireTest.php b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsPaginationWireTest.php index 21ceb93e0622..5f24cab208ad 100644 --- a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsPaginationWireTest.php +++ b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsPaginationWireTest.php @@ -44,10 +44,11 @@ public function testListItems(): void { */ protected function setUp(): void { parent::setUp(); + $wiremockUrl = getenv('WIREMOCK_URL') ?: 'http://localhost:8080'; $this->client = new SeedClient( token: 'test-token', options: [ - 'baseUrl' => getenv('WIREMOCK_URL') ?: 'http://localhost:8080', + 'baseUrl' => $wiremockUrl, ], ); } diff --git a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsParamsWireTest.php b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsParamsWireTest.php index b4e074a4d8fa..1eae7dbcaa3b 100644 --- a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsParamsWireTest.php +++ b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsParamsWireTest.php @@ -222,10 +222,11 @@ public function testUploadWithPath(): void { */ protected function setUp(): void { parent::setUp(); + $wiremockUrl = getenv('WIREMOCK_URL') ?: 'http://localhost:8080'; $this->client = new SeedClient( token: 'test-token', options: [ - 'baseUrl' => getenv('WIREMOCK_URL') ?: 'http://localhost:8080', + 'baseUrl' => $wiremockUrl, ], ); } diff --git a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsPrimitiveWireTest.php b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsPrimitiveWireTest.php index 9acde1004ca3..6198b60c247f 100644 --- a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsPrimitiveWireTest.php +++ b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsPrimitiveWireTest.php @@ -206,10 +206,11 @@ public function testGetAndReturnBase64(): void { */ protected function setUp(): void { parent::setUp(); + $wiremockUrl = getenv('WIREMOCK_URL') ?: 'http://localhost:8080'; $this->client = new SeedClient( token: 'test-token', options: [ - 'baseUrl' => getenv('WIREMOCK_URL') ?: 'http://localhost:8080', + 'baseUrl' => $wiremockUrl, ], ); } diff --git a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsPutWireTest.php b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsPutWireTest.php index 04c2f893c9c2..8929a00506ac 100644 --- a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsPutWireTest.php +++ b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsPutWireTest.php @@ -37,10 +37,11 @@ public function testAdd(): void { */ protected function setUp(): void { parent::setUp(); + $wiremockUrl = getenv('WIREMOCK_URL') ?: 'http://localhost:8080'; $this->client = new SeedClient( token: 'test-token', options: [ - 'baseUrl' => getenv('WIREMOCK_URL') ?: 'http://localhost:8080', + 'baseUrl' => $wiremockUrl, ], ); } diff --git a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsUnionWireTest.php b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsUnionWireTest.php index 69bebb586f9a..bf7b21d129a0 100644 --- a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsUnionWireTest.php +++ b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsUnionWireTest.php @@ -42,10 +42,11 @@ public function testGetAndReturnUnion(): void { */ protected function setUp(): void { parent::setUp(); + $wiremockUrl = getenv('WIREMOCK_URL') ?: 'http://localhost:8080'; $this->client = new SeedClient( token: 'test-token', options: [ - 'baseUrl' => getenv('WIREMOCK_URL') ?: 'http://localhost:8080', + 'baseUrl' => $wiremockUrl, ], ); } diff --git a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsUrlsWireTest.php b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsUrlsWireTest.php index 88f2d4764574..285bc35265c6 100644 --- a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsUrlsWireTest.php +++ b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/EndpointsUrlsWireTest.php @@ -96,10 +96,11 @@ public function testWithUnderscores(): void { */ protected function setUp(): void { parent::setUp(); + $wiremockUrl = getenv('WIREMOCK_URL') ?: 'http://localhost:8080'; $this->client = new SeedClient( token: 'test-token', options: [ - 'baseUrl' => getenv('WIREMOCK_URL') ?: 'http://localhost:8080', + 'baseUrl' => $wiremockUrl, ], ); } diff --git a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/InlinedRequestsWireTest.php b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/InlinedRequestsWireTest.php index b96a213da4cf..8c4fef1c4d9f 100644 --- a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/InlinedRequestsWireTest.php +++ b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/InlinedRequestsWireTest.php @@ -65,10 +65,11 @@ public function testPostWithObjectBodyandResponse(): void { */ protected function setUp(): void { parent::setUp(); + $wiremockUrl = getenv('WIREMOCK_URL') ?: 'http://localhost:8080'; $this->client = new SeedClient( token: 'test-token', options: [ - 'baseUrl' => getenv('WIREMOCK_URL') ?: 'http://localhost:8080', + 'baseUrl' => $wiremockUrl, ], ); } diff --git a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/NoAuthWireTest.php b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/NoAuthWireTest.php index 64c58231a76e..29721bb10861 100644 --- a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/NoAuthWireTest.php +++ b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/NoAuthWireTest.php @@ -39,10 +39,11 @@ public function testPostWithNoAuth(): void { */ protected function setUp(): void { parent::setUp(); + $wiremockUrl = getenv('WIREMOCK_URL') ?: 'http://localhost:8080'; $this->client = new SeedClient( token: 'test-token', options: [ - 'baseUrl' => getenv('WIREMOCK_URL') ?: 'http://localhost:8080', + 'baseUrl' => $wiremockUrl, ], ); } diff --git a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/NoReqBodyWireTest.php b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/NoReqBodyWireTest.php index cd6038fd641d..2827d18015a2 100644 --- a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/NoReqBodyWireTest.php +++ b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/NoReqBodyWireTest.php @@ -56,10 +56,11 @@ public function testPostWithNoRequestBody(): void { */ protected function setUp(): void { parent::setUp(); + $wiremockUrl = getenv('WIREMOCK_URL') ?: 'http://localhost:8080'; $this->client = new SeedClient( token: 'test-token', options: [ - 'baseUrl' => getenv('WIREMOCK_URL') ?: 'http://localhost:8080', + 'baseUrl' => $wiremockUrl, ], ); } diff --git a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/ReqWithHeadersWireTest.php b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/ReqWithHeadersWireTest.php index 19f34391b438..25784de5b1ab 100644 --- a/seed/php-sdk/exhaustive/wire-tests/tests/Wire/ReqWithHeadersWireTest.php +++ b/seed/php-sdk/exhaustive/wire-tests/tests/Wire/ReqWithHeadersWireTest.php @@ -42,10 +42,11 @@ public function testGetWithCustomHeader(): void { */ protected function setUp(): void { parent::setUp(); + $wiremockUrl = getenv('WIREMOCK_URL') ?: 'http://localhost:8080'; $this->client = new SeedClient( token: 'test-token', options: [ - 'baseUrl' => getenv('WIREMOCK_URL') ?: 'http://localhost:8080', + 'baseUrl' => $wiremockUrl, ], ); } diff --git a/seed/ts-sdk/websocket-bearer-auth/websockets/src/api/resources/realtime/exports.ts b/seed/ts-sdk/websocket-bearer-auth/websockets/src/api/resources/realtime/exports.ts index f7ae9ffe551b..63bcb56f9e27 100644 --- a/seed/ts-sdk/websocket-bearer-auth/websockets/src/api/resources/realtime/exports.ts +++ b/seed/ts-sdk/websocket-bearer-auth/websockets/src/api/resources/realtime/exports.ts @@ -2,3 +2,4 @@ export { RealtimeClient } from "./client/Client.js"; export * from "./client/index.js"; +export { RealtimeSocket } from "./client/Socket.js"; diff --git a/seed/ts-sdk/websocket-bearer-auth/websockets/src/api/resources/realtimeNoAuth/exports.ts b/seed/ts-sdk/websocket-bearer-auth/websockets/src/api/resources/realtimeNoAuth/exports.ts index 28f881beaed2..60f8ec9de42a 100644 --- a/seed/ts-sdk/websocket-bearer-auth/websockets/src/api/resources/realtimeNoAuth/exports.ts +++ b/seed/ts-sdk/websocket-bearer-auth/websockets/src/api/resources/realtimeNoAuth/exports.ts @@ -2,3 +2,4 @@ export { RealtimeNoAuthClient } from "./client/Client.js"; export * from "./client/index.js"; +export { RealtimeNoAuthSocket } from "./client/Socket.js"; diff --git a/seed/ts-sdk/websocket-inferred-auth/websockets/src/api/resources/realtime/exports.ts b/seed/ts-sdk/websocket-inferred-auth/websockets/src/api/resources/realtime/exports.ts index f7ae9ffe551b..63bcb56f9e27 100644 --- a/seed/ts-sdk/websocket-inferred-auth/websockets/src/api/resources/realtime/exports.ts +++ b/seed/ts-sdk/websocket-inferred-auth/websockets/src/api/resources/realtime/exports.ts @@ -2,3 +2,4 @@ export { RealtimeClient } from "./client/Client.js"; export * from "./client/index.js"; +export { RealtimeSocket } from "./client/Socket.js"; diff --git a/seed/ts-sdk/websocket/no-serde/src/api/resources/empty/resources/emptyRealtime/exports.ts b/seed/ts-sdk/websocket/no-serde/src/api/resources/empty/resources/emptyRealtime/exports.ts index 1ad15d1803f1..8e01ecb8a782 100644 --- a/seed/ts-sdk/websocket/no-serde/src/api/resources/empty/resources/emptyRealtime/exports.ts +++ b/seed/ts-sdk/websocket/no-serde/src/api/resources/empty/resources/emptyRealtime/exports.ts @@ -2,3 +2,4 @@ export { EmptyRealtimeClient } from "./client/Client.js"; export * from "./client/index.js"; +export { EmptyRealtimeSocket } from "./client/Socket.js"; diff --git a/seed/ts-sdk/websocket/no-serde/src/api/resources/realtime/exports.ts b/seed/ts-sdk/websocket/no-serde/src/api/resources/realtime/exports.ts index f7ae9ffe551b..63bcb56f9e27 100644 --- a/seed/ts-sdk/websocket/no-serde/src/api/resources/realtime/exports.ts +++ b/seed/ts-sdk/websocket/no-serde/src/api/resources/realtime/exports.ts @@ -2,3 +2,4 @@ export { RealtimeClient } from "./client/Client.js"; export * from "./client/index.js"; +export { RealtimeSocket } from "./client/Socket.js"; diff --git a/seed/ts-sdk/websocket/serde/src/api/resources/empty/resources/emptyRealtime/exports.ts b/seed/ts-sdk/websocket/serde/src/api/resources/empty/resources/emptyRealtime/exports.ts index 1ad15d1803f1..8e01ecb8a782 100644 --- a/seed/ts-sdk/websocket/serde/src/api/resources/empty/resources/emptyRealtime/exports.ts +++ b/seed/ts-sdk/websocket/serde/src/api/resources/empty/resources/emptyRealtime/exports.ts @@ -2,3 +2,4 @@ export { EmptyRealtimeClient } from "./client/Client.js"; export * from "./client/index.js"; +export { EmptyRealtimeSocket } from "./client/Socket.js"; diff --git a/seed/ts-sdk/websocket/serde/src/api/resources/realtime/exports.ts b/seed/ts-sdk/websocket/serde/src/api/resources/realtime/exports.ts index f7ae9ffe551b..63bcb56f9e27 100644 --- a/seed/ts-sdk/websocket/serde/src/api/resources/realtime/exports.ts +++ b/seed/ts-sdk/websocket/serde/src/api/resources/realtime/exports.ts @@ -2,3 +2,4 @@ export { RealtimeClient } from "./client/Client.js"; export * from "./client/index.js"; +export { RealtimeSocket } from "./client/Socket.js";