From 4ddcfef1bf012b40a598eef833cfa0bb5e4d15a6 Mon Sep 17 00:00:00 2001 From: Juraj Uhlar Date: Mon, 31 Aug 2026 17:07:54 +0100 Subject: [PATCH 1/8] feat: model Event as EventDevice | EventEdge SPIKE INTER-2457 option C. Docs and Node keep the union. Other SDK schemas flatten Event with source optional. Do not merge. --- .changeset/event-edge.md | 6 + .../sync-server-side-sdks-schema.yml | 2 + schemas/components/schemas/EdgeResponse.yaml | 36 -- schemas/components/schemas/Event.yaml | 358 +----------------- schemas/components/schemas/EventBase.yaml | 30 ++ schemas/components/schemas/EventDevice.yaml | 325 ++++++++++++++++ schemas/components/schemas/EventEdge.yaml | 64 ++++ schemas/paths/edge.yaml | 2 +- schemas/paths/event.yaml | 4 +- .../paths/examples/webhook/webhook_event.json | 1 + ...flattenNamedSchemaOneOfTransformer.spec.ts | 84 ++++ .../flattenNamedSchemaOneOfTransformer.ts | 53 +++ utils/transformers/transformSchema.spec.ts | 67 +++- utils/transformers/transformSchema.ts | 38 +- vite.config.ts | 18 +- 15 files changed, 678 insertions(+), 410 deletions(-) create mode 100644 .changeset/event-edge.md delete mode 100644 schemas/components/schemas/EdgeResponse.yaml create mode 100644 schemas/components/schemas/EventBase.yaml create mode 100644 schemas/components/schemas/EventDevice.yaml create mode 100644 schemas/components/schemas/EventEdge.yaml create mode 100644 utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts create mode 100644 utils/transformers/flattenNamedSchemaOneOfTransformer.ts diff --git a/.changeset/event-edge.md b/.changeset/event-edge.md new file mode 100644 index 00000000..f49b7d83 --- /dev/null +++ b/.changeset/event-edge.md @@ -0,0 +1,6 @@ +--- +'fingerprint-pro-server-api-openapi': minor +--- + +**events**: Replace `EdgeResponse` with `EventEdge`. Add `EventDevice`. Model `Event` as a `source` discriminated union of `EventDevice` and `EventEdge` in docs and the Node SDK schema. Other SDK schemas flatten `Event` and leave `source` optional. `POST /edge` returns `EventEdge`. + diff --git a/.github/workflows/sync-server-side-sdks-schema.yml b/.github/workflows/sync-server-side-sdks-schema.yml index fbfb7c20..a892c509 100644 --- a/.github/workflows/sync-server-side-sdks-schema.yml +++ b/.github/workflows/sync-server-side-sdks-schema.yml @@ -110,6 +110,7 @@ jobs: language: python language-version: '3.12' generate-command: 'bash ./generate.sh' + schema-source: fingerprint-server-api-v4-flat.yaml schema-path: res/fingerprint-server-api.yaml examples-path: test/mocks app-id: ${{ vars.RUNNER_APP_ID }} @@ -152,6 +153,7 @@ jobs: language: php language-version: '8.3' generate-command: 'bash ./scripts/generate.sh' + schema-source: fingerprint-server-api-v4-flat.yaml schema-path: res/fingerprint-server-api.yaml examples-path: test/mocks app-id: ${{ vars.RUNNER_APP_ID }} diff --git a/schemas/components/schemas/EdgeResponse.yaml b/schemas/components/schemas/EdgeResponse.yaml deleted file mode 100644 index 33292f5f..00000000 --- a/schemas/components/schemas/EdgeResponse.yaml +++ /dev/null @@ -1,36 +0,0 @@ -type: object -description: IP and bot analysis for a request submitted through the Automation Intelligence API. -additionalProperties: false -required: - - event_id - - timestamp - - ip_info -properties: - event_id: - $ref: platform/EventId.yaml - timestamp: - $ref: platform/Timestamp.yaml - source: - $ref: platform/EventSource.yaml - linked_id: - $ref: platform/LinkedId.yaml - tags: - $ref: identification/Tags.yaml - url: - $ref: identification/Url.yaml - bot_info: - $ref: smartsignals/BotInfo.yaml - ip_info: - $ref: smartsignals/IPInfo.yaml - proxy: - $ref: smartsignals/Proxy.yaml - proxy_confidence: - $ref: smartsignals/ProxyConfidence.yaml - proxy_details: - $ref: smartsignals/ProxyDetails.yaml - vpn: - $ref: smartsignals/Vpn.yaml - vpn_confidence: - $ref: smartsignals/VpnConfidence.yaml - vpn_methods: - $ref: smartsignals/VpnMethods.yaml diff --git a/schemas/components/schemas/Event.yaml b/schemas/components/schemas/Event.yaml index 3f9f7af8..028a34d8 100644 --- a/schemas/components/schemas/Event.yaml +++ b/schemas/components/schemas/Event.yaml @@ -1,345 +1,15 @@ type: object -description: Contains results from Fingerprint Identification and all active Smart Signals. Some Smart Signals are only supported for certain device types, these fields will be omitted for events not generated from the supported devices. Consult the [Smart Signals reference](https://docs.fingerprint.com/docs/smart-signals-reference) for more details. -additionalProperties: false -required: - - event_id - - timestamp -properties: - event_id: - $ref: platform/EventId.yaml - x-platforms: - - android - - ios - - browser - timestamp: - $ref: platform/Timestamp.yaml - x-platforms: - - android - - ios - - browser - source: - $ref: platform/EventSource.yaml - x-platforms: - - android - - ios - - browser - incremental_identification_status: - $ref: platform/IncrementalIdentificationStatus.yaml - x-platforms: - - browser - linked_id: - $ref: platform/LinkedId.yaml - x-platforms: - - android - - ios - - browser - environment_id: - $ref: platform/EnvironmentId.yaml - x-platforms: - - android - - ios - - browser - suspect: - $ref: platform/Suspect.yaml - x-platforms: - - android - - ios - - browser - sdk: - $ref: platform/SDK.yaml - x-platforms: - - android - - ios - - browser - replayed: - $ref: platform/Replayed.yaml - x-platforms: - - android - - ios - - browser - triggered_by: - $ref: platform/webhook/TriggeredBy.yaml - identification: - $ref: identification/Identification.yaml - x-platforms: - - android - - ios - - browser - supplementary_id_high_recall: - $ref: identification/SupplementaryIDHighRecall.yaml - x-platforms: - - android - - ios - - browser - tags: - $ref: identification/Tags.yaml - x-platforms: - - android - - ios - - browser - url: - $ref: identification/Url.yaml - x-platforms: - - browser - bundle_id: - $ref: identification/BundleId.yaml - x-platforms: - - ios - package_name: - $ref: identification/PackageName.yaml - x-platforms: - - android - ip_address: - $ref: identification/IpAddress.yaml - x-platforms: - - android - - ios - - browser - user_agent: - $ref: identification/UserAgent.yaml - x-platforms: - - android - - ios - - browser - device: - $ref: identification/Device.yaml - x-platforms: - - android - - ios - - browser - os: - $ref: identification/Os.yaml - x-platforms: - - android - - ios - - browser - os_version: - $ref: identification/OsVersion.yaml - x-platforms: - - android - - ios - - browser - client_referrer: - $ref: identification/ClientReferrer.yaml - x-platforms: - - browser - browser_details: - $ref: identification/BrowserDetails.yaml - x-platforms: - - browser - proximity: - $ref: identification/Proximity.yaml - x-platforms: - - android - - ios - - browser - active_call: - $ref: smartsignals/ActiveCall.yaml - x-platforms: - - android - - ios - bot: - $ref: smartsignals/BotResult.yaml - x-platforms: - - browser - bot_type: - $ref: smartsignals/BotType.yaml - x-platforms: - - browser - bot_info: - $ref: smartsignals/BotInfo.yaml - x-platforms: - - browser - cloned_app: - $ref: smartsignals/ClonedApp.yaml - x-platforms: - - android - developer_tools: - $ref: smartsignals/DeveloperTools.yaml - x-platforms: - - browser - - android - - ios - emulator: - $ref: smartsignals/Emulator.yaml - x-platforms: - - android - factory_reset_timestamp: - $ref: smartsignals/FactoryReset.yaml - x-platforms: - - android - - ios - frida: - $ref: smartsignals/Frida.yaml - x-platforms: - - android - - ios - ip_blocklist: - $ref: smartsignals/IPBlockList.yaml - x-platforms: - - android - - ios - - browser - ip_info: - $ref: smartsignals/IPInfo.yaml - x-platforms: - - android - - ios - - browser - proxy: - $ref: smartsignals/Proxy.yaml - x-platforms: - - android - - ios - - browser - proxy_confidence: - $ref: smartsignals/ProxyConfidence.yaml - x-platforms: - - android - - ios - - browser - proxy_details: - $ref: smartsignals/ProxyDetails.yaml - x-platforms: - - android - - ios - - browser - proxy_ml_score: - $ref: smartsignals/ProxyMLScore.yaml - x-platforms: - - browser - incognito: - $ref: smartsignals/Incognito.yaml - x-platforms: - - browser - jailbroken: - $ref: smartsignals/Jailbroken.yaml - x-platforms: - - ios - location_spoofing: - $ref: smartsignals/LocationSpoofing.yaml - x-platforms: - - android - - ios - mitm_attack: - $ref: smartsignals/MitMAttack.yaml - x-platforms: - - android - - ios - privacy_settings: - $ref: smartsignals/PrivacySettings.yaml - x-platforms: - - browser - root_apps: - $ref: smartsignals/RootApps.yaml - x-platforms: - - android - rule_action: - $ref: platform/EventRuleAction.yaml - simulator: - $ref: smartsignals/Simulator.yaml - x-platforms: - - ios - suspect_score: - $ref: smartsignals/SuspectScore.yaml - x-platforms: - - android - - ios - - browser - tampering: - $ref: smartsignals/Tampering.yaml - x-platforms: - - android - - ios - - browser - tampering_confidence: - $ref: smartsignals/TamperingConfidence.yaml - x-platforms: - - android - - ios - - browser - tampering_ml_score: - $ref: smartsignals/TamperingMlScore.yaml - x-platforms: - - android - - ios - - browser - tampering_details: - $ref: smartsignals/TamperingDetails.yaml - x-platforms: - - android - - ios - - browser - velocity: - $ref: smartsignals/Velocity.yaml - x-platforms: - - android - - ios - - browser - virtual_machine: - $ref: smartsignals/VirtualMachine.yaml - x-platforms: - - browser - virtual_machine_ml_score: - $ref: smartsignals/VirtualMachineMLScore.yaml - x-platforms: - - browser - vpn: - $ref: smartsignals/Vpn.yaml - x-platforms: - - android - - ios - - browser - vpn_confidence: - $ref: smartsignals/VpnConfidence.yaml - x-platforms: - - android - - ios - - browser - vpn_ml_score: - $ref: smartsignals/VpnMLScore.yaml - x-platforms: - - browser - vpn_origin_timezone: - $ref: smartsignals/VpnOriginTimezone.yaml - x-platforms: - - android - - ios - - browser - vpn_origin_country: - $ref: smartsignals/VpnOriginCountry.yaml - x-platforms: - - android - - ios - vpn_methods: - $ref: smartsignals/VpnMethods.yaml - x-platforms: - - android - - ios - - browser - high_activity_device: - $ref: smartsignals/HighActivity.yaml - x-platforms: - - android - - ios - - browser - rare_device: - $ref: smartsignals/RareDevice.yaml - x-platforms: - - browser - rare_device_percentile_bucket: - $ref: smartsignals/RareDevicePercentileBucket.yaml - x-platforms: - - browser - raw_device_attributes: - $ref: smartsignals/RawDeviceAttributes.yaml - x-platforms: - - browser - - ios - - android - labels: - $ref: smartsignals/Labels.yaml - x-platforms: - - browser - - ios - - android +description: | + An identification event (`source: device`) or an Automation Intelligence event (`source: edge`). + + Use `source` to tell them apart. Device events include Identification and device-derived Smart Signals. Edge events do not. + + Consult the [Smart Signals reference](https://docs.fingerprint.com/docs/smart-signals-reference) for more details. +oneOf: + - $ref: EventDevice.yaml + - $ref: EventEdge.yaml +discriminator: + propertyName: source + mapping: + device: EventDevice.yaml + edge: EventEdge.yaml diff --git a/schemas/components/schemas/EventBase.yaml b/schemas/components/schemas/EventBase.yaml new file mode 100644 index 00000000..c2cb8839 --- /dev/null +++ b/schemas/components/schemas/EventBase.yaml @@ -0,0 +1,30 @@ +type: object +description: Identifiers present on every Event. `linked_id` and `tags` are optional customer-provided values. +required: + - event_id + - timestamp +properties: + event_id: + $ref: platform/EventId.yaml + x-platforms: + - android + - ios + - browser + timestamp: + $ref: platform/Timestamp.yaml + x-platforms: + - android + - ios + - browser + linked_id: + $ref: platform/LinkedId.yaml + x-platforms: + - android + - ios + - browser + tags: + $ref: identification/Tags.yaml + x-platforms: + - android + - ios + - browser diff --git a/schemas/components/schemas/EventDevice.yaml b/schemas/components/schemas/EventDevice.yaml new file mode 100644 index 00000000..e631917b --- /dev/null +++ b/schemas/components/schemas/EventDevice.yaml @@ -0,0 +1,325 @@ +type: object +description: | + Contains results from Fingerprint Identification and Smart Signals derived from client-side device telemetry. Consult the [Smart Signals reference](https://docs.fingerprint.com/docs/smart-signals-reference) for more details. +additionalProperties: false +allOf: + - $ref: EventBase.yaml + - type: object + required: + - source + properties: + url: + $ref: identification/Url.yaml + x-platforms: + - browser + bot_info: + $ref: smartsignals/BotInfo.yaml + x-platforms: + - browser + ip_info: + $ref: smartsignals/IPInfo.yaml + x-platforms: + - android + - ios + - browser + proxy: + $ref: smartsignals/Proxy.yaml + x-platforms: + - android + - ios + - browser + proxy_confidence: + $ref: smartsignals/ProxyConfidence.yaml + x-platforms: + - android + - ios + - browser + proxy_details: + $ref: smartsignals/ProxyDetails.yaml + x-platforms: + - android + - ios + - browser + vpn: + $ref: smartsignals/Vpn.yaml + x-platforms: + - android + - ios + - browser + vpn_confidence: + $ref: smartsignals/VpnConfidence.yaml + x-platforms: + - android + - ios + - browser + vpn_methods: + $ref: smartsignals/VpnMethods.yaml + x-platforms: + - android + - ios + - browser + source: + $ref: platform/EventSource.yaml + const: device + x-platforms: + - android + - ios + - browser + incremental_identification_status: + $ref: platform/IncrementalIdentificationStatus.yaml + x-platforms: + - browser + environment_id: + $ref: platform/EnvironmentId.yaml + x-platforms: + - android + - ios + - browser + suspect: + $ref: platform/Suspect.yaml + x-platforms: + - android + - ios + - browser + sdk: + $ref: platform/SDK.yaml + x-platforms: + - android + - ios + - browser + replayed: + $ref: platform/Replayed.yaml + x-platforms: + - android + - ios + - browser + triggered_by: + $ref: platform/webhook/TriggeredBy.yaml + identification: + $ref: identification/Identification.yaml + x-platforms: + - android + - ios + - browser + supplementary_id_high_recall: + $ref: identification/SupplementaryIDHighRecall.yaml + x-platforms: + - android + - ios + - browser + bundle_id: + $ref: identification/BundleId.yaml + x-platforms: + - ios + package_name: + $ref: identification/PackageName.yaml + x-platforms: + - android + ip_address: + $ref: identification/IpAddress.yaml + x-platforms: + - android + - ios + - browser + user_agent: + $ref: identification/UserAgent.yaml + x-platforms: + - android + - ios + - browser + device: + $ref: identification/Device.yaml + x-platforms: + - android + - ios + - browser + os: + $ref: identification/Os.yaml + x-platforms: + - android + - ios + - browser + os_version: + $ref: identification/OsVersion.yaml + x-platforms: + - android + - ios + - browser + client_referrer: + $ref: identification/ClientReferrer.yaml + x-platforms: + - browser + browser_details: + $ref: identification/BrowserDetails.yaml + x-platforms: + - browser + proximity: + $ref: identification/Proximity.yaml + x-platforms: + - android + - ios + - browser + active_call: + $ref: smartsignals/ActiveCall.yaml + x-platforms: + - android + - ios + bot: + $ref: smartsignals/BotResult.yaml + x-platforms: + - browser + bot_type: + $ref: smartsignals/BotType.yaml + x-platforms: + - browser + cloned_app: + $ref: smartsignals/ClonedApp.yaml + x-platforms: + - android + developer_tools: + $ref: smartsignals/DeveloperTools.yaml + x-platforms: + - browser + - android + - ios + emulator: + $ref: smartsignals/Emulator.yaml + x-platforms: + - android + factory_reset_timestamp: + $ref: smartsignals/FactoryReset.yaml + x-platforms: + - android + - ios + frida: + $ref: smartsignals/Frida.yaml + x-platforms: + - android + - ios + ip_blocklist: + $ref: smartsignals/IPBlockList.yaml + x-platforms: + - android + - ios + - browser + proxy_ml_score: + $ref: smartsignals/ProxyMLScore.yaml + x-platforms: + - browser + incognito: + $ref: smartsignals/Incognito.yaml + x-platforms: + - browser + jailbroken: + $ref: smartsignals/Jailbroken.yaml + x-platforms: + - ios + location_spoofing: + $ref: smartsignals/LocationSpoofing.yaml + x-platforms: + - android + - ios + mitm_attack: + $ref: smartsignals/MitMAttack.yaml + x-platforms: + - android + - ios + privacy_settings: + $ref: smartsignals/PrivacySettings.yaml + x-platforms: + - browser + root_apps: + $ref: smartsignals/RootApps.yaml + x-platforms: + - android + rule_action: + $ref: platform/EventRuleAction.yaml + simulator: + $ref: smartsignals/Simulator.yaml + x-platforms: + - ios + suspect_score: + $ref: smartsignals/SuspectScore.yaml + x-platforms: + - android + - ios + - browser + tampering: + $ref: smartsignals/Tampering.yaml + x-platforms: + - android + - ios + - browser + tampering_confidence: + $ref: smartsignals/TamperingConfidence.yaml + x-platforms: + - android + - ios + - browser + tampering_ml_score: + $ref: smartsignals/TamperingMlScore.yaml + x-platforms: + - android + - ios + - browser + tampering_details: + $ref: smartsignals/TamperingDetails.yaml + x-platforms: + - android + - ios + - browser + velocity: + $ref: smartsignals/Velocity.yaml + x-platforms: + - android + - ios + - browser + virtual_machine: + $ref: smartsignals/VirtualMachine.yaml + x-platforms: + - browser + virtual_machine_ml_score: + $ref: smartsignals/VirtualMachineMLScore.yaml + x-platforms: + - browser + vpn_ml_score: + $ref: smartsignals/VpnMLScore.yaml + x-platforms: + - browser + vpn_origin_timezone: + $ref: smartsignals/VpnOriginTimezone.yaml + x-platforms: + - android + - ios + - browser + vpn_origin_country: + $ref: smartsignals/VpnOriginCountry.yaml + x-platforms: + - android + - ios + high_activity_device: + $ref: smartsignals/HighActivity.yaml + x-platforms: + - android + - ios + - browser + rare_device: + $ref: smartsignals/RareDevice.yaml + x-platforms: + - browser + rare_device_percentile_bucket: + $ref: smartsignals/RareDevicePercentileBucket.yaml + x-platforms: + - browser + raw_device_attributes: + $ref: smartsignals/RawDeviceAttributes.yaml + x-platforms: + - browser + - ios + - android + labels: + $ref: smartsignals/Labels.yaml + x-platforms: + - browser + - ios + - android diff --git a/schemas/components/schemas/EventEdge.yaml b/schemas/components/schemas/EventEdge.yaml new file mode 100644 index 00000000..275b6847 --- /dev/null +++ b/schemas/components/schemas/EventEdge.yaml @@ -0,0 +1,64 @@ +type: object +description: | + IP and bot analysis for an event generated by the Automation Intelligence API (`/edge` endpoint). No client-side collection agent is involved, so Identification (`visitor_id`) and device-telemetry-derived Smart Signals are not available. +additionalProperties: false +allOf: + - $ref: EventBase.yaml + - type: object + required: + - source + - ip_info + properties: + url: + $ref: identification/Url.yaml + x-platforms: + - browser + bot_info: + $ref: smartsignals/BotInfo.yaml + x-platforms: + - browser + ip_info: + $ref: smartsignals/IPInfo.yaml + x-platforms: + - android + - ios + - browser + proxy: + $ref: smartsignals/Proxy.yaml + x-platforms: + - android + - ios + - browser + proxy_confidence: + $ref: smartsignals/ProxyConfidence.yaml + x-platforms: + - android + - ios + - browser + proxy_details: + $ref: smartsignals/ProxyDetails.yaml + x-platforms: + - android + - ios + - browser + vpn: + $ref: smartsignals/Vpn.yaml + x-platforms: + - android + - ios + - browser + vpn_confidence: + $ref: smartsignals/VpnConfidence.yaml + x-platforms: + - android + - ios + - browser + vpn_methods: + $ref: smartsignals/VpnMethods.yaml + x-platforms: + - android + - ios + - browser + source: + $ref: platform/EventSource.yaml + const: edge diff --git a/schemas/paths/edge.yaml b/schemas/paths/edge.yaml index 3ac4227f..09945227 100644 --- a/schemas/paths/edge.yaml +++ b/schemas/paths/edge.yaml @@ -37,7 +37,7 @@ post: content: application/json: schema: - $ref: ../components/schemas/EdgeResponse.yaml + $ref: ../components/schemas/EventEdge.yaml examples: 200-ok-bot-detected: summary: Example response when a bot was detected. diff --git a/schemas/paths/event.yaml b/schemas/paths/event.yaml index 74bf0f3a..7a99fd5b 100644 --- a/schemas/paths/event.yaml +++ b/schemas/paths/event.yaml @@ -4,9 +4,11 @@ get: operationId: getEvent summary: Get an event by event ID description: | - Get a detailed analysis of an individual identification event, including Smart Signals. + Get a detailed analysis of an individual event, including Smart Signals. Use `event_id` as the URL path parameter. This API method is scoped to a request, i.e. all returned information is by `event_id`. + + Returns `EventDevice` when `source` is `device`, and `EventEdge` when `source` is `edge`. parameters: - name: event_id in: path diff --git a/schemas/paths/examples/webhook/webhook_event.json b/schemas/paths/examples/webhook/webhook_event.json index 35567917..407a005d 100644 --- a/schemas/paths/examples/webhook/webhook_event.json +++ b/schemas/paths/examples/webhook/webhook_event.json @@ -1,4 +1,5 @@ { + "source": "device", "linked_id": "somelinkedId", "tags": {}, "timestamp": 1708102555327, diff --git a/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts b/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts new file mode 100644 index 00000000..d651633d --- /dev/null +++ b/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts @@ -0,0 +1,84 @@ +import { flattenNamedSchemaOneOfTransformer } from './flattenNamedSchemaOneOfTransformer.ts'; +import type { OpenApiDocument } from '../openapi.ts'; + +const apply = (schema: OpenApiDocument, schemaName = 'Event', optionalProperties?: string[]) => { + flattenNamedSchemaOneOfTransformer(schemaName, { optionalProperties })(schema); + return schema; +}; + +describe('flattenNamedSchemaOneOfTransformer', () => { + it('merges a named oneOf into one object and can leave source optional', () => { + const schema: OpenApiDocument = { + components: { + schemas: { + EventSource: { + type: 'string', + enum: ['device', 'edge'], + }, + EventDevice: { + type: 'object', + required: ['event_id', 'source'], + properties: { + event_id: { type: 'string' }, + source: { $ref: '#/components/schemas/EventSource', const: 'device' }, + identification: { type: 'object' }, + }, + }, + EventEdge: { + type: 'object', + required: ['event_id', 'source', 'ip_info'], + properties: { + event_id: { type: 'string' }, + source: { $ref: '#/components/schemas/EventSource', const: 'edge' }, + ip_info: { type: 'object' }, + }, + }, + Event: { + oneOf: [{ $ref: '#/components/schemas/EventDevice' }, { $ref: '#/components/schemas/EventEdge' }], + discriminator: { + propertyName: 'source', + mapping: { + device: '#/components/schemas/EventDevice', + edge: '#/components/schemas/EventEdge', + }, + }, + }, + }, + }, + }; + + apply(schema, 'Event', ['source']); + + const event = schema.components.schemas.Event; + expect(event.oneOf).toBeUndefined(); + expect(event.discriminator).toBeUndefined(); + expect(event.properties.identification).toBeDefined(); + expect(event.properties.ip_info).toBeDefined(); + expect(event.properties.source).toEqual({ $ref: '#/components/schemas/EventSource' }); + expect(event.properties.source.enum).toBeUndefined(); + expect(event.required).toEqual(['event_id']); + }); + + it('does not modify other oneOf schemas', () => { + const schema: OpenApiDocument = { + components: { + schemas: { + Event: { + type: 'object', + properties: { event_id: { type: 'string' } }, + }, + EventRuleAction: { + oneOf: [{ $ref: '#/components/schemas/Allow' }, { $ref: '#/components/schemas/Block' }], + }, + Allow: { type: 'object', properties: { type: { const: 'allow' } } }, + Block: { type: 'object', properties: { type: { const: 'block' } } }, + }, + }, + }; + const original = structuredClone(schema); + + apply(schema); + + expect(schema).toEqual(original); + }); +}); diff --git a/utils/transformers/flattenNamedSchemaOneOfTransformer.ts b/utils/transformers/flattenNamedSchemaOneOfTransformer.ts new file mode 100644 index 00000000..214a58bb --- /dev/null +++ b/utils/transformers/flattenNamedSchemaOneOfTransformer.ts @@ -0,0 +1,53 @@ +import { replaceOneOf } from '../replaceOneOf.ts'; +import type { Transformer } from '../openapi.ts'; + +function isObject(value: unknown): value is Record { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} + +export type FlattenNamedSchemaOneOfOptions = { + /** + * Leave these properties optional on the flattened object even if every + * variant required them. Used so Event.source stays optional in SDK schemas. + */ + optionalProperties?: string[]; +}; + +/** + * Flatten `oneOf` on a named schema only. Used so EventDevice | EventEdge + * becomes a single Event in SDK output without flattening EventRuleAction + * and other unions. + */ +export function flattenNamedSchemaOneOfTransformer( + schemaName: string, + options: FlattenNamedSchemaOneOfOptions = {} +): Transformer { + return (apiDefinition) => { + const schema = apiDefinition.components?.schemas?.[schemaName]; + if (!schema?.oneOf) { + return; + } + replaceOneOf(schema, apiDefinition.components.schemas, 'oneOf'); + + // replaceOneOf may attach `enum` next to a `$ref` (e.g. Event.source). + // Generators then emit a nested SourceEnum and change getSource()'s type. + if (isObject(schema.properties)) { + for (const property of Object.values(schema.properties)) { + if (isObject(property) && typeof property.$ref === 'string') { + delete property.enum; + delete property.const; + } + } + } + + const optionalProperties = options.optionalProperties; + if (!optionalProperties?.length || !Array.isArray(schema.required)) { + return; + } + + schema.required = schema.required.filter((name: string) => !optionalProperties.includes(name)); + if (schema.required.length === 0) { + delete schema.required; + } + }; +} diff --git a/utils/transformers/transformSchema.spec.ts b/utils/transformers/transformSchema.spec.ts index 3d699edb..aac846b1 100644 --- a/utils/transformers/transformSchema.spec.ts +++ b/utils/transformers/transformSchema.spec.ts @@ -4,6 +4,7 @@ import { transformSchema, v4Transformers, v4SchemaForSdksTransformers, + v4SchemaForSdksFlatTransformers, v4SchemaForSdksNormalizedTransformers, } from './transformSchema.ts'; import type { OpenApiDocument } from '../openapi.ts'; @@ -109,17 +110,46 @@ describe('Test transformSchema pipelines for v4', () => { expect(parsed.paths['/edge']).toBeDefined(); }); - it('v4 sdk schemas remove /edge when present', () => { - const yamlWithEdge = toYaml({ - openapi: '3.1.1', - paths: { '/edge': { post: {} }, '/events': { get: {} } }, - components: { schemas: {} }, - }); + it('v4 docs and Node SDK schemas keep Event as EventDevice | EventEdge', () => { + for (const transformers of [v4Transformers, v4SchemaForSdksTransformers]) { + const parsed = parseYaml(transformSchema(v4Schema, transformers)); + const event = parsed.components.schemas.Event; + + expect(event.oneOf).toEqual([ + { $ref: '#/components/schemas/EventDevice' }, + { $ref: '#/components/schemas/EventEdge' }, + ]); + expect(event.discriminator).toEqual({ + propertyName: 'source', + mapping: { + device: '#/components/schemas/EventDevice', + edge: '#/components/schemas/EventEdge', + }, + }); + expect(parsed.components.schemas.EventDevice.properties.source).toBeDefined(); + expect(parsed.components.schemas.EventDevice.properties.ip_info).toBeDefined(); + expect(parsed.components.schemas.EventEdge.properties.ip_info).toBeDefined(); + expect(parsed.components.schemas.EventDevice.properties.identification).toBeDefined(); + expect(parsed.components.schemas.EventEdge.properties.identification).toBeUndefined(); + expect(parsed.paths['/edge']).toBeDefined(); + } + }); - for (const transformers of [v4SchemaForSdksTransformers, v4SchemaForSdksNormalizedTransformers]) { - const result = transformSchema(yamlWithEdge, transformers); - const parsed = parseYaml(result); - expect(parsed.paths['/edge']).toBeUndefined(); + it('v4 flat and normalized SDK schemas flatten Event and keep source optional', () => { + for (const transformers of [v4SchemaForSdksFlatTransformers, v4SchemaForSdksNormalizedTransformers]) { + const parsed = parseYaml(transformSchema(v4Schema, transformers)); + const event = parsed.components.schemas.Event; + + expect(event.oneOf).toBeUndefined(); + expect(event.discriminator).toBeUndefined(); + expect(event.properties.identification).toBeDefined(); + expect(event.properties.ip_info).toBeDefined(); + expect(event.properties.source).toBeDefined(); + expect(event.required).toEqual(expect.arrayContaining(['event_id', 'timestamp'])); + expect(event.required).not.toContain('source'); + expect(parsed.components.schemas.EventEdge.properties.ip_info).toBeDefined(); + expect(parsed.components.schemas.EventDevice).toBeUndefined(); + expect(parsed.paths['/edge']).toBeDefined(); } }); @@ -136,4 +166,21 @@ describe('Test transformSchema pipelines for v4', () => { expect(hasYamlKey(pathsYaml, 'oneOf')).toBe(false); }); + + it('v4 sdk schemas keep /edge', () => { + const yamlWithEdge = toYaml({ + openapi: '3.1.1', + paths: { '/edge': { post: {} }, '/events': { get: {} } }, + components: { schemas: {} }, + }); + + for (const transformers of [ + v4SchemaForSdksTransformers, + v4SchemaForSdksFlatTransformers, + v4SchemaForSdksNormalizedTransformers, + ]) { + const parsed = parseYaml(transformSchema(yamlWithEdge, transformers)); + expect(parsed.paths['/edge']).toBeDefined(); + } + }); }); diff --git a/utils/transformers/transformSchema.ts b/utils/transformers/transformSchema.ts index 33db8d4e..b1edfa7d 100644 --- a/utils/transformers/transformSchema.ts +++ b/utils/transformers/transformSchema.ts @@ -4,9 +4,9 @@ import { addXReadmeTransformer } from './addXReadmeTransformer.ts'; import { appendExternalSchemaRefTransformer } from './appendExternalSchemaRefTransformer.ts'; import { extractFirstParameterExampleTransformer } from './extractFirstParameterExampleTransformer.ts'; import { extractPathOperationInlineEnumsTransformer } from './extractPathOperationInlineEnumsTransformer.ts'; +import { flattenNamedSchemaOneOfTransformer } from './flattenNamedSchemaOneOfTransformer.ts'; import { parseYaml } from './parseYaml.ts'; import { removeBigExamplesTransformer } from './removeBigExamplesTransformer.ts'; -import { removeEdgeTransformer } from './removeEdgeTransformer.ts'; import { removeWebhookTransformer } from './removeWebhookTransformer.ts'; import { resolveAllOfTransformer } from './resolveAllOfTransformer.ts'; import { resolveExternalValueTransformer } from './resolveExternalValueTransformer.ts'; @@ -45,7 +45,6 @@ export const v4Transformers: Transformer[] = [ export const v4SchemaForSdksCommonTransformers: Transformer[] = [ ...v4Transformers, extractFirstParameterExampleTransformer, - removeEdgeTransformer, extractPathOperationInlineEnumsTransformer, replaceTagsTransformer, removeFieldTransformer('webhooks'), @@ -54,8 +53,17 @@ export const v4SchemaForSdksCommonTransformers: Transformer[] = [ removeBigExamplesTransformer, ]; -export const v4SchemaForSdksTransformers: Transformer[] = [ - ...v4SchemaForSdksCommonTransformers, +const flattenEventForSdks = flattenNamedSchemaOneOfTransformer('Event', { + optionalProperties: ['source'], +}); + +const flattenEventForOtherSdks: Transformer[] = [ + flattenEventForSdks, + // Flatten copies additionalProperties: false from EventDevice/EventEdge; strip it again for SDK compat. + removeFieldTransformer('additionalProperties', false), +]; + +const v4SdkBotInfoTransformers: Transformer[] = [ // Inline enums previously extracted from BotInfo to avoid breaking changes in the SDKs using this schema inlineReferencedPropertiesTransformer('BotInfo'), // Remove the added enum attribute for BotInfo.category. This must follow the inline transformer on the previous line. @@ -64,16 +72,26 @@ export const v4SchemaForSdksTransformers: Transformer[] = [ removeUnusedSchemasTransformer, ]; +// Node: Event stays EventDevice | EventEdge. start/end stay a date|int oneOf. +export const v4SchemaForSdksTransformers: Transformer[] = [ + ...v4SchemaForSdksCommonTransformers, + ...v4SdkBotInfoTransformers, +]; + +// Python, PHP: Event flattened to one object. start/end stay a date|int oneOf. +export const v4SchemaForSdksFlatTransformers: Transformer[] = [ + ...v4SchemaForSdksCommonTransformers, + ...flattenEventForOtherSdks, + ...v4SdkBotInfoTransformers, +]; + +// Go, Java, .NET: Event flattened to one object. start/end split into start + start_date_time. export const v4SchemaForSdksNormalizedTransformers: Transformer[] = [ ...v4SchemaForSdksCommonTransformers, // Expand oneOf query parameters, start and end, to avoid breaking changes in the SDKs using this schema replaceStartEndQueryParameters(), - // Inline enums previously extracted from BotInfo to avoid breaking changes in the SDKs using this schema - inlineReferencedPropertiesTransformer('BotInfo'), - // Remove the added enum attribute for BotInfo.category. This must follow the inline transformer on the previous line. - removeFieldByPathTransformer(['components', 'schemas', 'BotInfo', 'properties', 'category', 'enum']), - // This transformer should run last to ensure all unused schemas are found - removeUnusedSchemasTransformer, + ...flattenEventForOtherSdks, + ...v4SdkBotInfoTransformers, ]; export const readmeApiExplorerTransformers: Transformer[] = [ diff --git a/vite.config.ts b/vite.config.ts index 81a4aa79..aa36f816 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -7,6 +7,7 @@ import { removeExtraDocumentationTransformers, schemaForSdksTransformers, transformSchema, + v4SchemaForSdksFlatTransformers, v4SchemaForSdksNormalizedTransformers, v4SchemaForSdksTransformers, v4Transformers, @@ -26,19 +27,20 @@ const schemaOutputs = [ }, { from: 'schemas/fingerprint-server-api-v4.yaml', - // just schema used by most SDKs - // examples are removed - // includes `oneOf` operators same as the source schema - // additionalProperties: false are removed for backward compatibility + // Node SDK. Event is EventDevice | EventEdge. start/end stay a date|int oneOf. to: 'fingerprint-server-api-v4.yaml', transformers: v4SchemaForSdksTransformers, }, { from: 'schemas/fingerprint-server-api-v4.yaml', - // normalized schema used by SDKs in weakly typed languages - // examples are removed - // `oneOf` query parameters are split into two or more parameters - // additionalProperties: false are removed for backward compatibility + // Python, PHP. Event is a single object (source optional). start/end stay a date|int oneOf. + to: 'fingerprint-server-api-v4-flat.yaml', + transformers: v4SchemaForSdksFlatTransformers, + }, + { + from: 'schemas/fingerprint-server-api-v4.yaml', + // Go, Java, .NET. Event is a single object (source optional). + // start/end are split into start + start_date_time (same as those SDKs ship today). to: 'fingerprint-server-api-v4-normalized.yaml', transformers: v4SchemaForSdksNormalizedTransformers, }, From ac4f525ad97e169652788e3987fb39fb519694cf Mon Sep 17 00:00:00 2001 From: Juraj Uhlar Date: Mon, 31 Aug 2026 17:14:34 +0100 Subject: [PATCH 2/8] fix: drop x-platforms from EventEdge Edge events have no sdk.platform. Device still annotates url and bot_info as browser. --- schemas/components/schemas/EventEdge.yaml | 32 ----------------------- 1 file changed, 32 deletions(-) diff --git a/schemas/components/schemas/EventEdge.yaml b/schemas/components/schemas/EventEdge.yaml index 275b6847..2d65bf1a 100644 --- a/schemas/components/schemas/EventEdge.yaml +++ b/schemas/components/schemas/EventEdge.yaml @@ -11,54 +11,22 @@ allOf: properties: url: $ref: identification/Url.yaml - x-platforms: - - browser bot_info: $ref: smartsignals/BotInfo.yaml - x-platforms: - - browser ip_info: $ref: smartsignals/IPInfo.yaml - x-platforms: - - android - - ios - - browser proxy: $ref: smartsignals/Proxy.yaml - x-platforms: - - android - - ios - - browser proxy_confidence: $ref: smartsignals/ProxyConfidence.yaml - x-platforms: - - android - - ios - - browser proxy_details: $ref: smartsignals/ProxyDetails.yaml - x-platforms: - - android - - ios - - browser vpn: $ref: smartsignals/Vpn.yaml - x-platforms: - - android - - ios - - browser vpn_confidence: $ref: smartsignals/VpnConfidence.yaml - x-platforms: - - android - - ios - - browser vpn_methods: $ref: smartsignals/VpnMethods.yaml - x-platforms: - - android - - ios - - browser source: $ref: platform/EventSource.yaml const: edge From 840dcfb3d8afd6133fab6c37003e50e83f42f1a0 Mon Sep 17 00:00:00 2001 From: Juraj Uhlar Date: Mon, 31 Aug 2026 17:17:49 +0100 Subject: [PATCH 3/8] fix: satisfy exactOptionalPropertyTypes in flatten transformer spec --- utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts b/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts index d651633d..3b254302 100644 --- a/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts +++ b/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts @@ -2,7 +2,8 @@ import { flattenNamedSchemaOneOfTransformer } from './flattenNamedSchemaOneOfTra import type { OpenApiDocument } from '../openapi.ts'; const apply = (schema: OpenApiDocument, schemaName = 'Event', optionalProperties?: string[]) => { - flattenNamedSchemaOneOfTransformer(schemaName, { optionalProperties })(schema); + const options = optionalProperties === undefined ? {} : { optionalProperties }; + flattenNamedSchemaOneOfTransformer(schemaName, options)(schema); return schema; }; From ae00ba74b4750f022c5287bbd364241c66446fbd Mon Sep 17 00:00:00 2001 From: Juraj Uhlar Date: Mon, 31 Aug 2026 17:19:49 +0100 Subject: [PATCH 4/8] fix: keep x-platforms when flattening Event oneOf EventEdge omits x-platforms. Flatten was taking the last variant and dropping Device annotations on shared fields. --- utils/replaceOneOf.spec.ts | 26 +++++++++++++++ utils/replaceOneOf.ts | 18 +++++++++-- ...flattenNamedSchemaOneOfTransformer.spec.ts | 32 +++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) diff --git a/utils/replaceOneOf.spec.ts b/utils/replaceOneOf.spec.ts index b419ef03..8262e786 100644 --- a/utils/replaceOneOf.spec.ts +++ b/utils/replaceOneOf.spec.ts @@ -455,4 +455,30 @@ describe('Test replaceOneOf', () => { expect(schema.additionalProperties).toBe(true); }); + + it('keeps x-platforms from an earlier variant when a later variant omits them', () => { + const schema: OpenApiDocument = { + oneOf: [ + { + type: 'object', + properties: { + url: { type: 'string', 'x-platforms': ['browser'] }, + ip_info: { type: 'object', 'x-platforms': ['android', 'ios', 'browser'] }, + }, + }, + { + type: 'object', + properties: { + url: { type: 'string' }, + ip_info: { type: 'object' }, + }, + }, + ], + }; + + replaceOneOf(schema, {}, 'oneOf'); + + expect(schema.properties.url['x-platforms']).toEqual(['browser']); + expect(schema.properties.ip_info['x-platforms']).toEqual(['android', 'ios', 'browser']); + }); }); diff --git a/utils/replaceOneOf.ts b/utils/replaceOneOf.ts index cf83c9de..5b29e701 100644 --- a/utils/replaceOneOf.ts +++ b/utils/replaceOneOf.ts @@ -42,8 +42,22 @@ export function replaceOneOf( requiredCounts[propName] = (requiredCounts[propName] || 0) + 1; } - // Clone the property to avoid mutating the original schema - properties[propName] = structuredClone(currentItem.properties[propName]); + // Clone the property to avoid mutating the original schema. + // Keep x-platforms from an earlier variant when a later one omits it + // (EventEdge has no sdk.platform, EventDevice still annotates shared fields). + const incoming = structuredClone(currentItem.properties[propName]) as JsonObject; + const previous = properties[propName]; + if ( + previous && + typeof previous === 'object' && + previous['x-platforms'] !== undefined && + incoming && + typeof incoming === 'object' && + incoming['x-platforms'] === undefined + ) { + incoming['x-platforms'] = structuredClone(previous['x-platforms']); + } + properties[propName] = incoming; if (!literalValues[propName]) { literalValues[propName] = []; diff --git a/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts b/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts index 3b254302..3dae3efe 100644 --- a/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts +++ b/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts @@ -60,6 +60,38 @@ describe('flattenNamedSchemaOneOfTransformer', () => { expect(event.required).toEqual(['event_id']); }); + it('keeps x-platforms from EventDevice when EventEdge omits them', () => { + const schema: OpenApiDocument = { + components: { + schemas: { + EventDevice: { + type: 'object', + properties: { + url: { type: 'string', 'x-platforms': ['browser'] }, + ip_info: { type: 'object', 'x-platforms': ['android', 'ios', 'browser'] }, + }, + }, + EventEdge: { + type: 'object', + properties: { + url: { type: 'string' }, + ip_info: { type: 'object' }, + }, + }, + Event: { + oneOf: [{ $ref: '#/components/schemas/EventDevice' }, { $ref: '#/components/schemas/EventEdge' }], + }, + }, + }, + }; + + apply(schema); + + const event = schema.components.schemas.Event; + expect(event.properties.url['x-platforms']).toEqual(['browser']); + expect(event.properties.ip_info['x-platforms']).toEqual(['android', 'ios', 'browser']); + }); + it('does not modify other oneOf schemas', () => { const schema: OpenApiDocument = { components: { From 0ad285510fca0b7e7899c2849cbc78e908d41ca4 Mon Sep 17 00:00:00 2001 From: Juraj Uhlar Date: Mon, 31 Aug 2026 19:24:17 +0100 Subject: [PATCH 5/8] fix: describe get-event by source without generated type names --- .changeset/event-edge.md | 2 +- schemas/components/schemas/EdgeRequest.yaml | 13 ++++++- schemas/paths/event.yaml | 2 +- ...flattenNamedSchemaOneOfTransformer.spec.ts | 34 +++++++++++++++++++ utils/transformers/transformSchema.spec.ts | 8 +++++ 5 files changed, 56 insertions(+), 3 deletions(-) diff --git a/.changeset/event-edge.md b/.changeset/event-edge.md index f49b7d83..a0989879 100644 --- a/.changeset/event-edge.md +++ b/.changeset/event-edge.md @@ -2,5 +2,5 @@ 'fingerprint-pro-server-api-openapi': minor --- -**events**: Replace `EdgeResponse` with `EventEdge`. Add `EventDevice`. Model `Event` as a `source` discriminated union of `EventDevice` and `EventEdge` in docs and the Node SDK schema. Other SDK schemas flatten `Event` and leave `source` optional. `POST /edge` returns `EventEdge`. +**events**: Replace `EdgeResponse` with `EventEdge`. Add `EventDevice`. Model `Event` as a `source` discriminated union of `EventDevice` and `EventEdge` in docs and the Node SDK schema. Other SDK schemas flatten `Event` and leave `source` optional. Get-event copy describes `source` without naming generated types. `POST /edge` returns `EventEdge`. diff --git a/schemas/components/schemas/EdgeRequest.yaml b/schemas/components/schemas/EdgeRequest.yaml index c31e4a7d..ff3df65b 100644 --- a/schemas/components/schemas/EdgeRequest.yaml +++ b/schemas/components/schemas/EdgeRequest.yaml @@ -2,6 +2,17 @@ type: object description: |- HTTP request metadata (including the HTTP method, headers and IP address) sent by you (your server) to the Fingerprint API for IP and bot analysis. To improve accuracy, retain as much of the original semantics of the HTTP request as possible. For example, preserve the order of the request headers and their capitalization. At least one of `ipv4_address` or `ipv6_address` must be provided; a request with neither is rejected with a `400` error. If both IPv4 and IPv6 are provided, IP intelligence will be provided for each address. If an IPv4-mapped IPv6 address is provided in the `ipv6_address` request property, the IP intelligence will be provided in the `ipv4_address` property of the response. +example: + method: GET + url: https://example.com/login + ipv4_address: 34.162.244.71 + headers: + - name: Host + value: example.com + - name: User-Agent + value: Mozilla/5.0 + - name: Authorization + value: '' additionalProperties: false required: - headers @@ -13,7 +24,7 @@ properties: description: | Ordered header entries from the request made to your server. Each entry represents one header line. If one header name appears as multiple lines, send each as a separate item in the array. - Headers that contain authentication or session data must still be included, but with with their value set to an empty string. This includes headers like `Authorization` and `Cookie`, but may contain more depending on your specific project, for instance `Proxy-Authenticate` or `X-Api-Key`. Omitting the headers entirely changes the shape of the request and can affect detection. Never forward the real secret values. + Headers that contain authentication or session data must still be included, but with their value set to an empty string. This includes headers like `Authorization` and `Cookie`, but may contain more depending on your specific project, for instance `Proxy-Authenticate` or `X-Api-Key`. Omitting the headers entirely changes the shape of the request and can affect detection. Never forward the real secret values. Whenever possible, we recommend preserving header order and capitalization to provide the best accuracy, however it’s not a strict requirement if your runtime does not maintain http header order or canonicalizes header names. minItems: 1 diff --git a/schemas/paths/event.yaml b/schemas/paths/event.yaml index 7a99fd5b..60117cd4 100644 --- a/schemas/paths/event.yaml +++ b/schemas/paths/event.yaml @@ -8,7 +8,7 @@ get: Use `event_id` as the URL path parameter. This API method is scoped to a request, i.e. all returned information is by `event_id`. - Returns `EventDevice` when `source` is `device`, and `EventEdge` when `source` is `edge`. + Use `source` to tell identification events (`device`) from Automation Intelligence events (`edge`). parameters: - name: event_id in: path diff --git a/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts b/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts index 3dae3efe..e9871c25 100644 --- a/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts +++ b/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts @@ -114,4 +114,38 @@ describe('flattenNamedSchemaOneOfTransformer', () => { expect(schema).toEqual(original); }); + + it('flattens Event without flattening EventRuleAction', () => { + const schema: OpenApiDocument = { + components: { + schemas: { + EventDevice: { + type: 'object', + properties: { event_id: { type: 'string' } }, + }, + EventEdge: { + type: 'object', + properties: { event_id: { type: 'string' } }, + }, + Event: { + oneOf: [{ $ref: '#/components/schemas/EventDevice' }, { $ref: '#/components/schemas/EventEdge' }], + }, + EventRuleAction: { + oneOf: [{ $ref: '#/components/schemas/Allow' }, { $ref: '#/components/schemas/Block' }], + }, + Allow: { type: 'object', properties: { type: { const: 'allow' } } }, + Block: { type: 'object', properties: { type: { const: 'block' } } }, + }, + }, + }; + + apply(schema); + + expect(schema.components.schemas.Event.oneOf).toBeUndefined(); + expect(schema.components.schemas.Event.properties.event_id).toBeDefined(); + expect(schema.components.schemas.EventRuleAction.oneOf).toEqual([ + { $ref: '#/components/schemas/Allow' }, + { $ref: '#/components/schemas/Block' }, + ]); + }); }); diff --git a/utils/transformers/transformSchema.spec.ts b/utils/transformers/transformSchema.spec.ts index aac846b1..3cc9c54b 100644 --- a/utils/transformers/transformSchema.spec.ts +++ b/utils/transformers/transformSchema.spec.ts @@ -132,6 +132,10 @@ describe('Test transformSchema pipelines for v4', () => { expect(parsed.components.schemas.EventDevice.properties.identification).toBeDefined(); expect(parsed.components.schemas.EventEdge.properties.identification).toBeUndefined(); expect(parsed.paths['/edge']).toBeDefined(); + expect(parsed.paths['/events/{event_id}'].get.description).toContain( + 'Use `source` to tell identification events (`device`) from Automation Intelligence events (`edge`).' + ); + expect(parsed.paths['/events/{event_id}'].get.description).not.toContain('EventDevice'); } }); @@ -150,6 +154,10 @@ describe('Test transformSchema pipelines for v4', () => { expect(parsed.components.schemas.EventEdge.properties.ip_info).toBeDefined(); expect(parsed.components.schemas.EventDevice).toBeUndefined(); expect(parsed.paths['/edge']).toBeDefined(); + expect(parsed.paths['/events/{event_id}'].get.description).toContain( + 'Use `source` to tell identification events (`device`) from Automation Intelligence events (`edge`).' + ); + expect(parsed.paths['/events/{event_id}'].get.description).not.toContain('EventDevice'); } }); From 511c9695158d46bc46bc6d2c8bb95ed09519a96b Mon Sep 17 00:00:00 2001 From: Juraj Uhlar Date: Fri, 4 Sep 2026 14:00:17 +0100 Subject: [PATCH 6/8] fix: write Event source as allOf + const like RuleAction Sibling const next to $ref is ignored by generators. Flatten still unwraps to an optional EventSource $ref. --- schemas/components/schemas/EventDevice.yaml | 5 +-- schemas/components/schemas/EventEdge.yaml | 5 +-- ...flattenNamedSchemaOneOfTransformer.spec.ts | 14 ++++++-- .../flattenNamedSchemaOneOfTransformer.ts | 36 ++++++++++++++++--- utils/transformers/transformSchema.spec.ts | 13 +++++-- 5 files changed, 59 insertions(+), 14 deletions(-) diff --git a/schemas/components/schemas/EventDevice.yaml b/schemas/components/schemas/EventDevice.yaml index e631917b..9891c63d 100644 --- a/schemas/components/schemas/EventDevice.yaml +++ b/schemas/components/schemas/EventDevice.yaml @@ -59,8 +59,9 @@ allOf: - ios - browser source: - $ref: platform/EventSource.yaml - const: device + allOf: + - $ref: platform/EventSource.yaml + - const: device x-platforms: - android - ios diff --git a/schemas/components/schemas/EventEdge.yaml b/schemas/components/schemas/EventEdge.yaml index 2d65bf1a..354ce4b9 100644 --- a/schemas/components/schemas/EventEdge.yaml +++ b/schemas/components/schemas/EventEdge.yaml @@ -28,5 +28,6 @@ allOf: vpn_methods: $ref: smartsignals/VpnMethods.yaml source: - $ref: platform/EventSource.yaml - const: edge + allOf: + - $ref: platform/EventSource.yaml + - const: edge diff --git a/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts b/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts index e9871c25..f29bf04e 100644 --- a/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts +++ b/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts @@ -21,7 +21,10 @@ describe('flattenNamedSchemaOneOfTransformer', () => { required: ['event_id', 'source'], properties: { event_id: { type: 'string' }, - source: { $ref: '#/components/schemas/EventSource', const: 'device' }, + source: { + allOf: [{ $ref: '#/components/schemas/EventSource' }, { const: 'device' }], + 'x-platforms': ['android', 'ios', 'browser'], + }, identification: { type: 'object' }, }, }, @@ -30,7 +33,9 @@ describe('flattenNamedSchemaOneOfTransformer', () => { required: ['event_id', 'source', 'ip_info'], properties: { event_id: { type: 'string' }, - source: { $ref: '#/components/schemas/EventSource', const: 'edge' }, + source: { + allOf: [{ $ref: '#/components/schemas/EventSource' }, { const: 'edge' }], + }, ip_info: { type: 'object' }, }, }, @@ -55,7 +60,10 @@ describe('flattenNamedSchemaOneOfTransformer', () => { expect(event.discriminator).toBeUndefined(); expect(event.properties.identification).toBeDefined(); expect(event.properties.ip_info).toBeDefined(); - expect(event.properties.source).toEqual({ $ref: '#/components/schemas/EventSource' }); + expect(event.properties.source).toEqual({ + $ref: '#/components/schemas/EventSource', + 'x-platforms': ['android', 'ios', 'browser'], + }); expect(event.properties.source.enum).toBeUndefined(); expect(event.required).toEqual(['event_id']); }); diff --git a/utils/transformers/flattenNamedSchemaOneOfTransformer.ts b/utils/transformers/flattenNamedSchemaOneOfTransformer.ts index 214a58bb..b9602a04 100644 --- a/utils/transformers/flattenNamedSchemaOneOfTransformer.ts +++ b/utils/transformers/flattenNamedSchemaOneOfTransformer.ts @@ -5,6 +5,32 @@ function isObject(value: unknown): value is Record { return value !== null && typeof value === 'object' && !Array.isArray(value); } +function collapseToEnumRef(property: Record): void { + if (typeof property.$ref === 'string') { + delete property.enum; + delete property.const; + return; + } + + if (!Array.isArray(property.allOf)) { + return; + } + + const refItem = property.allOf.find((item) => isObject(item) && typeof item.$ref === 'string'); + if (!isObject(refItem) || typeof refItem.$ref !== 'string') { + return; + } + + const platforms = property['x-platforms']; + for (const key of Object.keys(property)) { + delete property[key]; + } + property.$ref = refItem.$ref; + if (platforms !== undefined) { + property['x-platforms'] = platforms; + } +} + export type FlattenNamedSchemaOneOfOptions = { /** * Leave these properties optional on the flattened object even if every @@ -29,13 +55,13 @@ export function flattenNamedSchemaOneOfTransformer( } replaceOneOf(schema, apiDefinition.components.schemas, 'oneOf'); - // replaceOneOf may attach `enum` next to a `$ref` (e.g. Event.source). - // Generators then emit a nested SourceEnum and change getSource()'s type. + // Discriminator fields use `allOf: [$ref Enum, const]` (EventRuleAction) or + // `$ref` + sibling `const`. Flattening would otherwise leave a nested + // SourceEnum / leftover const and change getSource()'s type. if (isObject(schema.properties)) { for (const property of Object.values(schema.properties)) { - if (isObject(property) && typeof property.$ref === 'string') { - delete property.enum; - delete property.const; + if (isObject(property)) { + collapseToEnumRef(property); } } } diff --git a/utils/transformers/transformSchema.spec.ts b/utils/transformers/transformSchema.spec.ts index 3cc9c54b..ec74dce9 100644 --- a/utils/transformers/transformSchema.spec.ts +++ b/utils/transformers/transformSchema.spec.ts @@ -126,7 +126,13 @@ describe('Test transformSchema pipelines for v4', () => { edge: '#/components/schemas/EventEdge', }, }); - expect(parsed.components.schemas.EventDevice.properties.source).toBeDefined(); + expect(parsed.components.schemas.EventDevice.properties.source).toEqual({ + allOf: [{ $ref: '#/components/schemas/EventSource' }, { const: 'device' }], + 'x-platforms': ['android', 'ios', 'browser'], + }); + expect(parsed.components.schemas.EventEdge.properties.source).toEqual({ + allOf: [{ $ref: '#/components/schemas/EventSource' }, { const: 'edge' }], + }); expect(parsed.components.schemas.EventDevice.properties.ip_info).toBeDefined(); expect(parsed.components.schemas.EventEdge.properties.ip_info).toBeDefined(); expect(parsed.components.schemas.EventDevice.properties.identification).toBeDefined(); @@ -148,7 +154,10 @@ describe('Test transformSchema pipelines for v4', () => { expect(event.discriminator).toBeUndefined(); expect(event.properties.identification).toBeDefined(); expect(event.properties.ip_info).toBeDefined(); - expect(event.properties.source).toBeDefined(); + expect(event.properties.source).toEqual({ + $ref: '#/components/schemas/EventSource', + 'x-platforms': ['android', 'ios', 'browser'], + }); expect(event.required).toEqual(expect.arrayContaining(['event_id', 'timestamp'])); expect(event.required).not.toContain('source'); expect(parsed.components.schemas.EventEdge.properties.ip_info).toBeDefined(); From c6234b4b53c34b9bcf09d2e1dc78c8ea57824dd6 Mon Sep 17 00:00:00 2001 From: Juraj Uhlar Date: Fri, 4 Sep 2026 16:34:13 +0100 Subject: [PATCH 7/8] fix: rewrite Event source $ref + const in the lift transformer Keep the same source form as EventRuleActionAllow. Bare oneOf parents now run the same normalizeRefSiblings pass on variants. --- schemas/components/schemas/EventDevice.yaml | 5 +-- schemas/components/schemas/EventEdge.yaml | 5 +-- utils/replaceOneOf.ts | 30 +++++++++----- ...flattenNamedSchemaOneOfTransformer.spec.ts | 39 +++++++++++++++++++ .../flattenNamedSchemaOneOfTransformer.ts | 7 +++- ...ftOneOfSharedPropertiesTransformer.spec.ts | 37 ++++++++++++++++++ .../liftOneOfSharedPropertiesTransformer.ts | 34 +++++++++++++++- utils/transformers/transformSchema.spec.ts | 6 ++- 8 files changed, 144 insertions(+), 19 deletions(-) diff --git a/schemas/components/schemas/EventDevice.yaml b/schemas/components/schemas/EventDevice.yaml index 9891c63d..e631917b 100644 --- a/schemas/components/schemas/EventDevice.yaml +++ b/schemas/components/schemas/EventDevice.yaml @@ -59,9 +59,8 @@ allOf: - ios - browser source: - allOf: - - $ref: platform/EventSource.yaml - - const: device + $ref: platform/EventSource.yaml + const: device x-platforms: - android - ios diff --git a/schemas/components/schemas/EventEdge.yaml b/schemas/components/schemas/EventEdge.yaml index 354ce4b9..2d65bf1a 100644 --- a/schemas/components/schemas/EventEdge.yaml +++ b/schemas/components/schemas/EventEdge.yaml @@ -28,6 +28,5 @@ allOf: vpn_methods: $ref: smartsignals/VpnMethods.yaml source: - allOf: - - $ref: platform/EventSource.yaml - - const: edge + $ref: platform/EventSource.yaml + const: edge diff --git a/utils/replaceOneOf.ts b/utils/replaceOneOf.ts index 5b29e701..8a399c2e 100644 --- a/utils/replaceOneOf.ts +++ b/utils/replaceOneOf.ts @@ -1,6 +1,23 @@ import { resolveComponent } from './resolveComponent.ts'; import type { OpenApiDocument, JsonObject } from './openapi.ts'; +function getXPlatforms(prop: JsonObject | undefined): unknown { + if (!prop || typeof prop !== 'object') { + return undefined; + } + if (prop['x-platforms'] !== undefined) { + return prop['x-platforms']; + } + if (Array.isArray(prop.allOf)) { + for (const item of prop.allOf) { + if (item && typeof item === 'object' && item['x-platforms'] !== undefined) { + return item['x-platforms']; + } + } + } + return undefined; +} + /** * Merges multiple schemas from oneOf/anyOf into a single schema. * Properties that are only present in one schema are made optional. @@ -47,15 +64,10 @@ export function replaceOneOf( // (EventEdge has no sdk.platform, EventDevice still annotates shared fields). const incoming = structuredClone(currentItem.properties[propName]) as JsonObject; const previous = properties[propName]; - if ( - previous && - typeof previous === 'object' && - previous['x-platforms'] !== undefined && - incoming && - typeof incoming === 'object' && - incoming['x-platforms'] === undefined - ) { - incoming['x-platforms'] = structuredClone(previous['x-platforms']); + const previousPlatforms = getXPlatforms(previous); + const incomingPlatforms = getXPlatforms(incoming); + if (previousPlatforms !== undefined && incomingPlatforms === undefined && incoming) { + incoming['x-platforms'] = structuredClone(previousPlatforms); } properties[propName] = incoming; diff --git a/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts b/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts index f29bf04e..453014f2 100644 --- a/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts +++ b/utils/transformers/flattenNamedSchemaOneOfTransformer.spec.ts @@ -68,6 +68,45 @@ describe('flattenNamedSchemaOneOfTransformer', () => { expect(event.required).toEqual(['event_id']); }); + it('keeps x-platforms when they sit on the allOf const item', () => { + const schema: OpenApiDocument = { + components: { + schemas: { + EventSource: { type: 'string', enum: ['device', 'edge'] }, + EventDevice: { + type: 'object', + properties: { + source: { + allOf: [ + { $ref: '#/components/schemas/EventSource' }, + { const: 'device', 'x-platforms': ['android', 'ios', 'browser'] }, + ], + }, + }, + }, + EventEdge: { + type: 'object', + properties: { + source: { + allOf: [{ $ref: '#/components/schemas/EventSource' }, { const: 'edge' }], + }, + }, + }, + Event: { + oneOf: [{ $ref: '#/components/schemas/EventDevice' }, { $ref: '#/components/schemas/EventEdge' }], + }, + }, + }, + }; + + apply(schema, 'Event', ['source']); + + expect(schema.components.schemas.Event.properties.source).toEqual({ + $ref: '#/components/schemas/EventSource', + 'x-platforms': ['android', 'ios', 'browser'], + }); + }); + it('keeps x-platforms from EventDevice when EventEdge omits them', () => { const schema: OpenApiDocument = { components: { diff --git a/utils/transformers/flattenNamedSchemaOneOfTransformer.ts b/utils/transformers/flattenNamedSchemaOneOfTransformer.ts index b9602a04..5d1338c6 100644 --- a/utils/transformers/flattenNamedSchemaOneOfTransformer.ts +++ b/utils/transformers/flattenNamedSchemaOneOfTransformer.ts @@ -21,7 +21,12 @@ function collapseToEnumRef(property: Record): void { return; } - const platforms = property['x-platforms']; + let platforms = property['x-platforms']; + for (const item of property.allOf) { + if (isObject(item) && item['x-platforms'] !== undefined) { + platforms = item['x-platforms']; + } + } for (const key of Object.keys(property)) { delete property[key]; } diff --git a/utils/transformers/liftOneOfSharedPropertiesTransformer.spec.ts b/utils/transformers/liftOneOfSharedPropertiesTransformer.spec.ts index c2df51e2..cfb2ade4 100644 --- a/utils/transformers/liftOneOfSharedPropertiesTransformer.spec.ts +++ b/utils/transformers/liftOneOfSharedPropertiesTransformer.spec.ts @@ -60,6 +60,43 @@ describe('liftOneOfSharedPropertiesTransformer', () => { expect(schema).toEqual(original); }); + it('rewrites $ref + const on variants of a bare oneOf parent', () => { + const schema: OpenApiDocument = { + components: { + schemas: { + Kind: { type: 'string', enum: ['device', 'edge'] }, + EventDevice: { + type: 'object', + allOf: [ + { + type: 'object', + properties: { + source: { + $ref: '#/components/schemas/Kind', + const: 'device', + 'x-platforms': ['browser'], + }, + }, + }, + ], + }, + Event: { + type: 'object', + oneOf: [{ $ref: '#/components/schemas/EventDevice' }], + }, + }, + }, + }; + + applyTransformer(schema); + + expect(schema.components.schemas.Event.properties).toBeUndefined(); + expect(schema.components.schemas.EventDevice.properties).toBeUndefined(); + expect(schema.components.schemas.EventDevice.allOf[0].properties.source).toEqual({ + allOf: [{ $ref: '#/components/schemas/Kind' }, { const: 'device', 'x-platforms': ['browser'] }], + }); + }); + it('skips variants that already have allOf', () => { const schema: OpenApiDocument = { components: { diff --git a/utils/transformers/liftOneOfSharedPropertiesTransformer.ts b/utils/transformers/liftOneOfSharedPropertiesTransformer.ts index cbadd0a0..6d06e396 100644 --- a/utils/transformers/liftOneOfSharedPropertiesTransformer.ts +++ b/utils/transformers/liftOneOfSharedPropertiesTransformer.ts @@ -79,6 +79,10 @@ function wrapVariantWithBase(schema: JsonObject, baseSchema: JsonObject): boolea /** * For schemas that define both shared object properties and `oneOf`, extract * the shared object shape and apply it directly to each alternative via `allOf`. + * + * For a bare `oneOf` parent (no sibling properties), still rewrite `$ref` + + * sibling keywords on each variant — same `normalizeRefSiblings` step the lift + * path uses. Event is this shape; EventRuleAction is the sibling-properties shape. */ export function liftOneOfSharedPropertiesTransformer(apiDefinition: OpenApiDocument): void { const schemas = apiDefinition?.components?.schemas; @@ -88,7 +92,12 @@ export function liftOneOfSharedPropertiesTransformer(apiDefinition: OpenApiDocum } for (const schema of Object.values(schemas)) { - if (!isObject(schema) || !Array.isArray(schema.oneOf) || !isObject(schema.properties)) { + if (!isObject(schema) || !Array.isArray(schema.oneOf)) { + continue; + } + + if (!isObject(schema.properties)) { + normalizeOneOfVariants(schema, schemas); continue; } @@ -127,3 +136,26 @@ export function liftOneOfSharedPropertiesTransformer(apiDefinition: OpenApiDocum } } } + +function normalizeOneOfVariants(schema: JsonObject, schemas: JsonObject): void { + if (!Array.isArray(schema.oneOf)) { + return; + } + + for (const oneOfAlternative of schema.oneOf) { + if (!isObject(oneOfAlternative)) { + continue; + } + + if (typeof oneOfAlternative.$ref === 'string') { + const schemaName = getSchemaNameFromRef(oneOfAlternative.$ref); + const alternativeSchema = schemaName ? schemas[schemaName] : null; + if (isObject(alternativeSchema)) { + normalizeRefSiblings(alternativeSchema); + } + continue; + } + + normalizeRefSiblings(oneOfAlternative); + } +} diff --git a/utils/transformers/transformSchema.spec.ts b/utils/transformers/transformSchema.spec.ts index ec74dce9..40411f34 100644 --- a/utils/transformers/transformSchema.spec.ts +++ b/utils/transformers/transformSchema.spec.ts @@ -127,8 +127,10 @@ describe('Test transformSchema pipelines for v4', () => { }, }); expect(parsed.components.schemas.EventDevice.properties.source).toEqual({ - allOf: [{ $ref: '#/components/schemas/EventSource' }, { const: 'device' }], - 'x-platforms': ['android', 'ios', 'browser'], + allOf: [ + { $ref: '#/components/schemas/EventSource' }, + { const: 'device', 'x-platforms': ['android', 'ios', 'browser'] }, + ], }); expect(parsed.components.schemas.EventEdge.properties.source).toEqual({ allOf: [{ $ref: '#/components/schemas/EventSource' }, { const: 'edge' }], From 767520ffd03dbdad2a5ac0c3a3ae4df1141f19e5 Mon Sep 17 00:00:00 2001 From: Juraj Uhlar Date: Sun, 6 Sep 2026 17:53:04 +0100 Subject: [PATCH 8/8] fix: leave x-platforms outside the $ref + const rewrite --- ...ftOneOfSharedPropertiesTransformer.spec.ts | 11 +++++++++- .../liftOneOfSharedPropertiesTransformer.ts | 21 +++++++++++-------- utils/transformers/transformSchema.spec.ts | 6 ++---- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/utils/transformers/liftOneOfSharedPropertiesTransformer.spec.ts b/utils/transformers/liftOneOfSharedPropertiesTransformer.spec.ts index cfb2ade4..79819adc 100644 --- a/utils/transformers/liftOneOfSharedPropertiesTransformer.spec.ts +++ b/utils/transformers/liftOneOfSharedPropertiesTransformer.spec.ts @@ -71,6 +71,10 @@ describe('liftOneOfSharedPropertiesTransformer', () => { { type: 'object', properties: { + url: { + $ref: '#/components/schemas/Url', + 'x-platforms': ['browser'], + }, source: { $ref: '#/components/schemas/Kind', const: 'device', @@ -93,7 +97,12 @@ describe('liftOneOfSharedPropertiesTransformer', () => { expect(schema.components.schemas.Event.properties).toBeUndefined(); expect(schema.components.schemas.EventDevice.properties).toBeUndefined(); expect(schema.components.schemas.EventDevice.allOf[0].properties.source).toEqual({ - allOf: [{ $ref: '#/components/schemas/Kind' }, { const: 'device', 'x-platforms': ['browser'] }], + allOf: [{ $ref: '#/components/schemas/Kind' }, { const: 'device' }], + 'x-platforms': ['browser'], + }); + expect(schema.components.schemas.EventDevice.allOf[0].properties.url).toEqual({ + $ref: '#/components/schemas/Url', + 'x-platforms': ['browser'], }); }); diff --git a/utils/transformers/liftOneOfSharedPropertiesTransformer.ts b/utils/transformers/liftOneOfSharedPropertiesTransformer.ts index 6d06e396..f9daa19e 100644 --- a/utils/transformers/liftOneOfSharedPropertiesTransformer.ts +++ b/utils/transformers/liftOneOfSharedPropertiesTransformer.ts @@ -34,8 +34,8 @@ function takeKeys(schema: JsonObject, keys: string[]): JsonObject { } /** - * Convert `{ $ref: ..., ...constraints }` to `allOf` form to keep downstream - * allOf/oneOf resolvers behavior consistent. + * Convert `{ $ref, const|enum, ... }` to `allOf: [$ref, const|enum]` plus leftover + * siblings (e.g. `x-platforms`). JSON Schema ignores keywords next to `$ref`. */ function normalizeRefSiblings(node: unknown): void { if (Array.isArray(node)) { @@ -49,15 +49,18 @@ function normalizeRefSiblings(node: unknown): void { Object.values(node).forEach((value) => normalizeRefSiblings(value)); - if (Object.hasOwn(node, '$ref') && Object.keys(node).length > 1) { - const { $ref, ...siblingConstraints } = node; + if (!Object.hasOwn(node, '$ref') || (!Object.hasOwn(node, 'const') && !Object.hasOwn(node, 'enum'))) { + return; + } - Object.keys(node).forEach((key) => { - delete node[key]; - }); + const { $ref, const: constValue, enum: enumValue, ...rest } = node; - node.allOf = [{ $ref }, siblingConstraints]; - } + Object.keys(node).forEach((key) => { + delete node[key]; + }); + + node.allOf = [{ $ref }, constValue !== undefined ? { const: constValue } : { enum: enumValue }]; + Object.assign(node, rest); } function wrapVariantWithBase(schema: JsonObject, baseSchema: JsonObject): boolean { diff --git a/utils/transformers/transformSchema.spec.ts b/utils/transformers/transformSchema.spec.ts index 40411f34..ec74dce9 100644 --- a/utils/transformers/transformSchema.spec.ts +++ b/utils/transformers/transformSchema.spec.ts @@ -127,10 +127,8 @@ describe('Test transformSchema pipelines for v4', () => { }, }); expect(parsed.components.schemas.EventDevice.properties.source).toEqual({ - allOf: [ - { $ref: '#/components/schemas/EventSource' }, - { const: 'device', 'x-platforms': ['android', 'ios', 'browser'] }, - ], + allOf: [{ $ref: '#/components/schemas/EventSource' }, { const: 'device' }], + 'x-platforms': ['android', 'ios', 'browser'], }); expect(parsed.components.schemas.EventEdge.properties.source).toEqual({ allOf: [{ $ref: '#/components/schemas/EventSource' }, { const: 'edge' }],