|
| 1 | +--- |
| 2 | +title: Forward compatibility |
| 3 | +description: > |
| 4 | + Learn how Speakeasy-generated SDKs handle forward compatibility |
| 5 | + with new fields, enum values, and unexpected data. |
| 6 | +--- |
| 7 | + |
| 8 | +import { Callout, CodeWithTabs, Table } from "@/mdx/components"; |
| 9 | + |
| 10 | +# Forward compatibility |
| 11 | + |
| 12 | +This guide explains how Speakeasy-generated SDKs maintain forward compatibility when APIs evolve. Forward compatibility ensures older SDK versions continue to work correctly when the API adds new fields, enum values, or other data. |
| 13 | + |
| 14 | +## Forward compatibility at a glance |
| 15 | + |
| 16 | +<Callout type="info"> |
| 17 | + Forward compatibility ensures older SDK versions continue to work when APIs |
| 18 | + evolve. The table below shows which changes are safe and which require special |
| 19 | + handling. |
| 20 | +</Callout> |
| 21 | + |
| 22 | +<Table |
| 23 | + data={[ |
| 24 | + { |
| 25 | + change: "New Field Added", |
| 26 | + status: "✅", |
| 27 | + impact: |
| 28 | + "**Safe change.** Older SDKs automatically ignore unknown fields in API responses, allowing seamless evolution.", |
| 29 | + }, |
| 30 | + { |
| 31 | + change: "New Enum Value in Open Enum", |
| 32 | + status: "✅", |
| 33 | + impact: |
| 34 | + "**Safe change.** With `x-speakeasy-unknown-values: allow`, SDKs handle new enum values gracefully through language-specific mechanisms.", |
| 35 | + }, |
| 36 | + { |
| 37 | + change: "New Enum Value in Closed Enum", |
| 38 | + status: "❌", |
| 39 | + impact: |
| 40 | + "**Breaking change.** Older SDKs will reject responses with unknown enum values. Convert to open enums using `x-speakeasy-unknown-values: allow`.", |
| 41 | + }, |
| 42 | + { |
| 43 | + change: "Type Change", |
| 44 | + status: "❌", |
| 45 | + impact: |
| 46 | + "**Breaking change.** Changing a field's type (e.g., string to integer) will cause deserialization errors in older SDKs.", |
| 47 | + }, |
| 48 | + { |
| 49 | + change: "Required request field → Optional", |
| 50 | + status: "✅", |
| 51 | + impact: |
| 52 | + "**Safe change.** Older SDKs will continue to send the field, while newer SDKs can omit it when not needed.", |
| 53 | + }, |
| 54 | + { |
| 55 | + change: "Optional request field → Required", |
| 56 | + status: "⚠️", |
| 57 | + impact: |
| 58 | + "**Depends on client implementation.** If clients were already sending the optional field, they'll continue working. If they weren't, requests will fail with validation errors.", |
| 59 | + }, |
| 60 | + { |
| 61 | + change: "Required response field → Optional", |
| 62 | + status: "❌", |
| 63 | + impact: |
| 64 | + "**Breaking change.** Older SDKs expect this field to always be present and may throw errors when it's missing.", |
| 65 | + }, |
| 66 | + { |
| 67 | + change: "Required response field → Optional with Default", |
| 68 | + status: "✅", |
| 69 | + impact: |
| 70 | + "**Safe with proper default value.** When the field is omitted, the API returns a default value, preventing errors in older SDKs.", |
| 71 | + }, |
| 72 | + ]} |
| 73 | + columns={[ |
| 74 | + { key: "change", header: "API Change" }, |
| 75 | + { key: "status", header: "Status" }, |
| 76 | + { key: "impact", header: "Impact & Handling" }, |
| 77 | + ]} |
| 78 | +/> |
| 79 | + |
| 80 | +## Common forward compatibility scenarios |
| 81 | + |
| 82 | +This section covers the most frequent scenarios encountered when evolving APIs and how Speakeasy handles them to maintain forward compatibility. |
| 83 | + |
| 84 | +### Handling new fields |
| 85 | + |
| 86 | +Adding new fields to API responses is safe because older SDK versions ignore these fields. When an API response includes fields not defined in the SDK's model, these fields are simply not deserialized. |
| 87 | + |
| 88 | +<CodeWithTabs> |
| 89 | + |
| 90 | +```yaml !!tabs Original Schema |
| 91 | +type: object |
| 92 | +properties: |
| 93 | + name: |
| 94 | + type: string |
| 95 | + created_at: |
| 96 | + type: string |
| 97 | + format: date-time |
| 98 | +``` |
| 99 | +
|
| 100 | +```yaml !!tabs Updated Schema |
| 101 | +type: object |
| 102 | +properties: |
| 103 | + name: |
| 104 | + type: string |
| 105 | + created_at: |
| 106 | + type: string |
| 107 | + format: date-time |
| 108 | + updated_at: # New field |
| 109 | + type: string |
| 110 | + format: date-time |
| 111 | +``` |
| 112 | +
|
| 113 | +</CodeWithTabs> |
| 114 | +
|
| 115 | +Older SDK versions will continue to work without errors, ignoring the `updated_at` field. This allows APIs to evolve by adding new data without breaking existing integrations. |
| 116 | + |
| 117 | +### Handling new enum values |
| 118 | + |
| 119 | +APIs often need to add new enum values over time. Speakeasy provides the `x-speakeasy-unknown-values` extension to handle this gracefully. |
| 120 | + |
| 121 | +```yaml |
| 122 | +status: |
| 123 | + type: string |
| 124 | + x-speakeasy-unknown-values: allow |
| 125 | + enum: |
| 126 | + - active |
| 127 | + - inactive |
| 128 | + - pending |
| 129 | +``` |
| 130 | + |
| 131 | +When the API adds a new enum value (e.g., `suspended`), older SDK versions handle it according to the language: |
| 132 | + |
| 133 | +- **TypeScript** |
| 134 | +- **Python** |
| 135 | +- **Go** |
| 136 | +- **Java** |
| 137 | + |
| 138 | +This prevents runtime errors when new enum values are encountered, allowing APIs to add new states without breaking existing clients. |
| 139 | + |
| 140 | +### Handling unexpected data |
| 141 | + |
| 142 | +Speakeasy-generated SDKs include built-in mechanisms to handle unexpected data: |
| 143 | + |
| 144 | +1. **Validation errors**: SDKs provide detailed validation errors when unexpected data is received, making debugging easier |
| 145 | + |
| 146 | +2. **OneOf schemas**: When using `oneOf` schemas, SDKs can handle evolving data structures by attempting to match against known variants |
| 147 | + |
| 148 | +3. **Optional fields**: Fields marked as optional in the OpenAPI spec won't cause validation errors if missing |
| 149 | + |
| 150 | +### Handling unexpected response codes |
| 151 | + |
| 152 | +APIs evolve over time and may introduce new response codes. Speakeasy-generated SDKs are designed to handle unexpected response codes gracefully: |
| 153 | + |
| 154 | +```yaml |
| 155 | +responses: |
| 156 | + "2xx": |
| 157 | + description: Success response |
| 158 | + content: |
| 159 | + application/json: |
| 160 | + schema: |
| 161 | + $ref: "#/components/schemas/SuccessResponse" |
| 162 | + "4xx": |
| 163 | + description: Error response |
| 164 | + content: |
| 165 | + application/json: |
| 166 | + schema: |
| 167 | + $ref: "#/components/schemas/ErrorResponse" |
| 168 | +``` |
| 169 | + |
| 170 | +#### Benefits of status code ranges |
| 171 | + |
| 172 | +1. **Flexible status codes**: Using `2xx` and `4xx` patterns allows APIs to add new specific status codes (like `201` or `429`) without breaking existing SDKs |
| 173 | + |
| 174 | +2. **Consistent error handling**: All error responses follow the same structure, making it easier to handle new error types |
| 175 | + |
| 176 | +3. **Graceful degradation**: Even when encountering unexpected status codes, SDKs can still extract useful information from the response |
| 177 | + |
| 178 | +When an API returns a status code that wasn't explicitly defined in the original specification, Speakeasy SDKs: |
| 179 | + |
| 180 | +- Match it to the appropriate range (`2xx`, `4xx`, `5xx`) |
| 181 | +- Parse the response using the defined schema for that range |
| 182 | +- Provide access to both the status code and response body |
| 183 | + |
| 184 | +## Advanced forward compatibility techniques |
| 185 | + |
| 186 | +These advanced techniques help maintain forward compatibility in more complex scenarios. |
| 187 | + |
| 188 | +### Deprecating fields |
| 189 | + |
| 190 | +When evolving APIs, deprecating fields is a common necessity. Speakeasy provides extensions to handle field deprecation gracefully while maintaining forward compatibility: |
| 191 | + |
| 192 | +```yaml |
| 193 | +properties: |
| 194 | + name: |
| 195 | + type: string |
| 196 | + sku: |
| 197 | + type: string |
| 198 | + deprecated: true |
| 199 | + x-speakeasy-deprecation-message: We no longer support the SKU property. |
| 200 | +``` |
| 201 | + |
| 202 | +#### Benefits of proper deprecation |
| 203 | + |
| 204 | +1. Fields remain accessible to older SDK versions |
| 205 | +2. New SDK versions mark these fields with proper deprecation annotations |
| 206 | +3. Generated documentation includes deprecation notices |
| 207 | +4. Developers receive clear guidance on migration |
| 208 | + |
| 209 | +#### Field removal process |
| 210 | + |
| 211 | +When planning to remove a field entirely: |
| 212 | + |
| 213 | +1. Mark the field as optional first |
| 214 | +2. Add deprecation notices with the `deprecated` keyword and `x-speakeasy-deprecation-message` |
| 215 | +3. Allow sufficient time for users to update implementations |
| 216 | +4. Remove the field only after a suitable deprecation period |
| 217 | + |
| 218 | +### Forward-compatible unions |
| 219 | + |
| 220 | +To create forward-compatible unions that can handle new data types added in the future, use the oneOf pattern with a string fallback: |
| 221 | + |
| 222 | +```yaml |
| 223 | +oneOf: |
| 224 | + - { type: "dog" } |
| 225 | + - { type: "cat" } |
| 226 | + - { type: string } |
| 227 | +``` |
| 228 | + |
| 229 | +#### Benefits of string fallback |
| 230 | + |
| 231 | +1. Provides strongly typed handling for known variants (`dog` and `cat` types) |
| 232 | +2. Gracefully captures any future variants as string values |
| 233 | +3. Prevents runtime errors when new variants are introduced |
| 234 | +4. Allows SDK users to handle unknown variants safely |
| 235 | + |
| 236 | +<Callout type="info" title="Language-specific union handling"> |
| 237 | + Each language handles these unions differently: - **TypeScript**: Uses native |
| 238 | + union types with string fallback - **Python**: Leverages `typing.Union` with |
| 239 | + string fallback - **Go**: Generates helper methods for both known and unknown |
| 240 | + types - **Java**: Provides type discrimination with generic string handling |
| 241 | +</Callout> |
| 242 | + |
| 243 | +## Guard-rails for breaking changes |
| 244 | + |
| 245 | +Speakeasy provides several tools to detect and prevent breaking changes: |
| 246 | + |
| 247 | +### Version your API |
| 248 | + |
| 249 | +Create a versioning strategy for your API to manage breaking changes: |
| 250 | + |
| 251 | +- Use path-based versioning (e.g., `/v1/resource`, `/v2/resource`) |
| 252 | +- Include version in request headers (`Api-Version: 2023-01-01`) |
| 253 | +- Maintain multiple API versions simultaneously during migration periods |
| 254 | + |
| 255 | +### Add defaults for optional fields |
| 256 | + |
| 257 | +When making required fields optional: |
| 258 | + |
| 259 | +- Always include default values to maintain backward compatibility |
| 260 | +- Document the default behavior clearly |
| 261 | +- Use the `default` property in your OpenAPI specification: |
| 262 | + ```yaml |
| 263 | + properties: |
| 264 | + status: |
| 265 | + type: string |
| 266 | + default: "active" |
| 267 | + ``` |
| 268 | + |
| 269 | +### Open your enums |
| 270 | + |
| 271 | +Convert closed enums to open enums using the Speakeasy extension: |
| 272 | + |
| 273 | +```yaml |
| 274 | +status: |
| 275 | + type: string |
| 276 | + x-speakeasy-unknown-values: allow |
| 277 | + enum: |
| 278 | + - active |
| 279 | + - inactive |
| 280 | + - pending |
| 281 | +``` |
| 282 | + |
| 283 | +### Use the OpenAPI diff tool |
| 284 | + |
| 285 | +The [OpenAPI diff tool](/docs/speakeasy-reference/cli/openapi/diff) identifies potential breaking changes between API specification versions: |
| 286 | + |
| 287 | +```bash |
| 288 | +speakeasy openapi diff --base v1.yaml --revision v2.yaml |
| 289 | +``` |
| 290 | + |
| 291 | +This highlights changes that might break backward compatibility, such as: |
| 292 | + |
| 293 | +- Removing required fields |
| 294 | +- Changing field types |
| 295 | +- Modifying oneOf schemas |
| 296 | + |
| 297 | +### SDK version management |
| 298 | + |
| 299 | +Speakeasy automatically manages SDK versioning based on the nature of changes: |
| 300 | + |
| 301 | +- Patch version for non-breaking changes |
| 302 | +- Minor version for backward-compatible additions |
| 303 | +- Major version for breaking changes |
| 304 | + |
| 305 | +### Breaking change notifications |
| 306 | + |
| 307 | +When generating SDKs, Speakeasy detects breaking changes and provides clear notifications about what changed and how to handle the transition. |
| 308 | + |
| 309 | +<Callout type="info" title="Related resources"> |
| 310 | + For more information about handling breaking changes, see the [breaking |
| 311 | + changes guide](./breaking-changes). |
| 312 | +</Callout> |
0 commit comments