From 789a5b8cc93ec48e148383708b2922369b7c4c0b Mon Sep 17 00:00:00 2001 From: Andrei Matei Date: Fri, 19 Jun 2026 10:08:25 +0100 Subject: [PATCH] feat(ipa): Enrich IPA-125 Single Type in Request and Response with structured metadata CLOUDP-399919 --- ipa/general/0125.mdx | 384 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 349 insertions(+), 35 deletions(-) diff --git a/ipa/general/0125.mdx b/ipa/general/0125.mdx index aaa921d..a0d73da 100644 --- a/ipa/general/0125.mdx +++ b/ipa/general/0125.mdx @@ -13,39 +13,353 @@ state management. ## Guidance -- Splitting Fields for Multiple Value Types - - API producers **should not** use oneOf with base types like int or string if - the field can have multiple distinct value types - - API producers **should** split such fields into separate, clearly named - fields with appropriate types -- API producers **may** use fields that contain multiple objects when request - and response objects allow explicitly setting the type of the object - - In OpenAPI each `oneOf` property **must** be accompanied by a - `discriminator` property defining when the exact type will be used - - In OpenAPI each `discriminator` property **must** be accompanied by a - `oneOf`, `anyOf` or `allOf` property - ([OAS 3.1.0 4.8.25.1](https://spec.openapis.org/oas/v3.1.0#fixed-fields-20)) - - If multiple `oneOf` models define a property with the same name, that - property **must** have the same data type in each model - -## Example - -Avoid this design, using the same field for multiple types: - -```http -POST /groups/search/index -{ - "indexArray": false, // Can be a boolean, an array or a single object -} -``` - -Preferred design, splitting the field into distinct types: - -```http -POST /groups/search/index -{ - "isArray": false, //Explicitly a boolean - "indexArrayObjects": [{}, {}], //Expilicitly an array - "singleIndexObject": {"key1": "value1", "key2": "value2"} //Explicitly a single object -} +### Splitting Fields for Multiple Value Types + + + + + +API producers **should not** use `oneOf` with base types like `integer` or +`string` when the field can hold multiple distinct value types. + + + + + +```yaml +components: + schemas: + Setting: + type: object + properties: + enabled: + type: boolean + threshold: + type: integer +``` + + + +Each value type lives in its own typed field, so a consumer reads `enabled` as a +boolean and `threshold` as an integer without inspecting the payload to find out +which type arrived. + + + + + + + +```yaml +components: + schemas: + Setting: + type: object + properties: + value: + oneOf: + - type: boolean + - type: integer + - type: string +``` + + + +A single field that may arrive as a boolean, an integer, or a string forces +every consumer to branch on the runtime type, and generated clients cannot give +the field one stable static type. + + + + + + + + + + + +API producers **should** split such fields into separate, clearly named fields +with appropriate types. + + + + + +```yaml +components: + schemas: + Index: + type: object + properties: + isArray: + type: boolean + arrayObjects: + type: array + items: + type: object + singleObject: + type: object +``` + + + +The three value shapes become three named fields with explicit types, so the +name of each field documents what it holds and the type system enforces it. + + + + + + + +```yaml +components: + schemas: + Index: + type: object + properties: + index: + oneOf: + - type: boolean + - type: array + items: + type: object + - type: object +``` + + + +One overloaded field carries a boolean, an array, or an object depending on the +case, so the field name describes none of them and a consumer cannot tell from +the schema which shape to send. + + + + + + + + For each schema under `$.components.schemas`, list every property whose + definition uses `oneOf`. + + + For each such `oneOf`, determine whether the members are distinct base types + (`boolean`, `integer`, `number`, `string`) or different structural shapes + (object versus array versus scalar). + + + When the members are distinct value types rather than variants of one typed + object, treat the single field as overloaded. + + + Report each overloaded field, noting that the fix is one named, single-typed + field per value shape. + + + + + + + + + +### Fields Containing Multiple Object Types + + + + + +API producers **may** use fields that contain multiple objects when request and +response objects allow explicitly setting the type of the object. + + + + + +In OpenAPI each `oneOf` property **must** be accompanied by a `discriminator` +property that defines when each exact type is used. + + + + + +```yaml +components: + schemas: + Notification: + oneOf: + - $ref: "#/components/schemas/EmailNotification" + - $ref: "#/components/schemas/SmsNotification" + discriminator: + propertyName: channel + mapping: + email: "#/components/schemas/EmailNotification" + sms: "#/components/schemas/SmsNotification" ``` + + + +The `discriminator` names the field (`channel`) that selects a variant and maps +each value to one schema, so a consumer resolves the concrete type from the +payload without guessing. + + + + + + + +```yaml +components: + schemas: + Notification: + oneOf: + - $ref: "#/components/schemas/EmailNotification" + - $ref: "#/components/schemas/SmsNotification" +``` + + + +Without a `discriminator`, nothing in the schema indicates which variant a given +payload represents, so the concrete type must be inferred by trial validation +against each branch. + + + + + + + + + + + +In OpenAPI each `discriminator` property **must** be accompanied by a `oneOf`, +`anyOf`, or `allOf` property +([OAS 3.1.0 4.8.25.1](https://spec.openapis.org/oas/v3.1.0#fixed-fields-20)). + + + + + +```yaml +components: + schemas: + Payment: + oneOf: + - $ref: "#/components/schemas/CardPayment" + - $ref: "#/components/schemas/BankPayment" + discriminator: + propertyName: method +``` + + + +The `discriminator` sits beside a `oneOf` composition, so it has a set of +candidate schemas to select among. + + + + + + + +```yaml +components: + schemas: + Payment: + type: object + properties: + method: + type: string + discriminator: + propertyName: method +``` + + + +A `discriminator` with no `oneOf`, `anyOf`, or `allOf` sibling has no candidate +schemas to choose between, so the selection it describes points at nothing. + + + + + + + + + + + +If multiple `oneOf` models define a property with the same name, that property +**must** have the same data type in each model. + + + + + +```yaml +components: + schemas: + Event: + oneOf: + - $ref: "#/components/schemas/CreatedEvent" + - $ref: "#/components/schemas/DeletedEvent" + CreatedEvent: + type: object + properties: + id: + type: string + DeletedEvent: + type: object + properties: + id: + type: string +``` + + + +Both variants type the shared `id` field as a string, so a consumer reads `id` +the same way regardless of which variant arrives. + + + + + + + +```yaml +components: + schemas: + Event: + oneOf: + - $ref: "#/components/schemas/CreatedEvent" + - $ref: "#/components/schemas/DeletedEvent" + CreatedEvent: + type: object + properties: + id: + type: string + DeletedEvent: + type: object + properties: + id: + type: integer +``` + + + +The `id` field is a string in one variant and an integer in the other, so the +type of a field with one name depends on which variant arrived, defeating the +single-type goal. + + + + + + + + + +