Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions .github/scripts/generate-composition-overlay.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/usr/bin/env bash

set -euo pipefail

workflow_file="${1:-.speakeasy/workflow.yaml}"
output_file="${2:-releases/overlays/generated.overlay.json}"

mkdir -p "$(dirname "$output_file")"

{
while IFS=$'\t' read -r location namespace; do
# Speakeasy applies modelNamespace to schema names and structural refs,
# but explicit discriminator mappings need the same transformation.
yq -o=json "$location" | jq -c --arg namespace "$namespace" '
def json_path_segment:
if type == "number"
then "[" + tostring + "]"
else "[" + (@json) + "]"
end;

.components.schemas as $schemas
| $schemas
| to_entries[]
| .key as $schema_name
| .value as $schema
| ($schema | path(.. | objects | select(.discriminator.mapping? != null))) as $path
| ($schema | getpath($path) | .discriminator.mapping) as $mapping
| {
target: (
"$.components.schemas["
+ (($namespace + "_" + $schema_name) | @json)
+ "]"
+ ($path | map(json_path_segment) | join(""))
+ ".discriminator.mapping"
),
update: (
$mapping
| with_entries(
.value |= (
. as $ref
| if (
($ref | startswith("#/components/schemas/"))
and ($schemas[$ref | sub("^#/components/schemas/"; "")] != null)
)
then (
"#/components/schemas/"
+ $namespace
+ "_"
+ ($ref | sub("^#/components/schemas/"; ""))
)
elif $schemas[$ref] != null
then "#/components/schemas/" + $namespace + "_" + $ref
else $ref
end
)
)
)
}
'

# Ledger uses a tagged inline union while discriminator inference is
# disabled. Convert every singleton resource enum, including future
# branches, into the constant expected by generated union helpers.
if [[ "$namespace" == "ledger" ]]; then
yq -o=json "$location" | jq -c --arg namespace "$namespace" '
.components.schemas.V2QueryParams.oneOf
| to_entries[]
| select(
(.value.properties.resource.enum? | type) == "array"
and (.value.properties.resource.enum | length) == 1
)
| .key as $index
| .value.properties.resource.enum[0] as $resource
| {
target: (
"$.components.schemas[\""
+ $namespace
+ "_V2QueryParams\"].oneOf["
+ ($index | tostring)
+ "].properties.resource.enum"
),
remove: true
},
{
target: (
"$.components.schemas[\""
+ $namespace
+ "_V2QueryParams\"].oneOf["
+ ($index | tostring)
+ "].properties.resource"
),
update: {const: $resource}
}
'
fi
done < <(
yq -r '
.sources[].inputs[]
| select(.modelNamespace != null)
| [.location, .modelNamespace]
| @tsv
' "$workflow_file"
)
} | jq -s '
{
overlay: "1.0.0",
info: {
title: "Generated namespace fixes for the Formance Stack OpenAPI spec",
version: "0.0.1"
},
actions: .
}
' > "$output_file"
7 changes: 4 additions & 3 deletions .speakeasy/workflow.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
speakeasyVersion: 1.791.1
speakeasyVersion: 1.791.4
sources:
stacks-source:
sourceNamespace: stacks-source
sourceRevisionDigest: sha256:6c3c89b43c0d8a0f2fecf34ff86937fc61d691088dc23ac99c12b455daefb396
sourceBlobDigest: sha256:13724b4f3a2771b9912c10beaaecd2e73b3ca8203d7f7b40cadaca7014f45994
sourceRevisionDigest: sha256:1d9e07120ee16749020c103aba5ef1de0601f8ee52ff065d000a1e651521898e
sourceBlobDigest: sha256:29450b93df4a9523bbe1b02552a402098ed562fe9fae90ccacfa55994fee806f
tags:
- latest
- SDK_VERSION
Expand Down Expand Up @@ -35,6 +35,7 @@ workflow:
- location: ./releases/base.yaml
overlays:
- location: ./releases/overlays/shared.overlay.yaml
- location: ./releases/overlays/generated.overlay.json
output: ./releases/build/generate.json
registry:
location: registry.speakeasyapi.dev/formance/formance/stacks-source
Expand Down
1 change: 1 addition & 0 deletions .speakeasy/workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ sources:
- location: ./releases/base.yaml
overlays:
- location: ./releases/overlays/shared.overlay.yaml
- location: ./releases/overlays/generated.overlay.json
output: ./releases/build/generate.json
registry:
location: registry.speakeasyapi.dev/formance/formance/stacks-source
Expand Down
24 changes: 22 additions & 2 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,31 @@ prepend-paths: download-specs
strip-servers: prepend-paths
for f in components/*.openapi.yaml; do yq -i 'del(.servers)' "$f"; done

# Generate namespace-aware composition fixes from the configured inputs.
generate-composition-overlay: strip-servers
mkdir -p releases/build
bash .github/scripts/generate-composition-overlay.sh

# Build the merged OpenAPI spec using Speakeasy
build-openapi version="v0.0.0": strip-servers
build-openapi version="v0.0.0": generate-composition-overlay
mkdir -p releases/build
speakeasy run -s all
cd releases && sed -i'' -e 's/SDK_VERSION/{{ version }}/g' build/generate.json
just validate-openapi

# Validate invariants introduced by composing namespaced OpenAPI documents.
validate-openapi:
# Every local discriminator mapping must resolve to an existing schema.
jq -e '.components.schemas as $schemas | [ $schemas | .. | objects | select(.discriminator.mapping? != null) | .discriminator.mapping[] | select(type == "string" and startswith("#/components/schemas/")) | sub("^#/components/schemas/"; "") | select($schemas[.] == null) ] | length == 0' releases/build/generate.json >/dev/null
# Ledger query-template helpers rely on these resource constants.
jq -e '[.components.schemas.ledger_V2QueryParams.oneOf[].properties.resource | select(.const == null or .enum != null)] | length == 0' releases/build/generate.json >/dev/null
# Error responses must not be generated as successful response variants.
test "$(jq -c '.paths["x-speakeasy-errors"].statusCodes' releases/build/generate.json)" = '["4XX","5XX","default"]'
# Every operation tag must be declared globally.
jq -e '[.tags[].name] as $tags | [.paths[] | to_entries[] | select(.key | IN("get", "post", "put", "patch", "delete", "options", "head", "trace")) | .value.tags[]? | select(. as $tag | $tags | index($tag) == null)] | length == 0' releases/build/generate.json >/dev/null
# Composition cleanups must remain applied when component specs change.
jq -e '.components.schemas.auth_Scope == null and .components.schemas.auth_ScopeOptions == null' releases/build/generate.json >/dev/null
jq -e '[.. | objects | select(.enum? != null) | .enum | select(length != (unique | length))] | length == 0' releases/build/generate.json >/dev/null

# Generate event schemas
generate-events:
Expand All @@ -52,7 +72,7 @@ generate-events:
build version="v0.0.0": (build-openapi version) generate-events

# Publish OpenAPI spec to Speakeasy Registry
publish-speakeasy version: prepend-paths
publish-speakeasy version: generate-composition-overlay
speakeasy run -s all --registry-tags {{ version }},LATEST_RELEASE

# Pre-commit: build spec and generate events
Expand Down
133 changes: 133 additions & 0 deletions releases/overlays/generated.overlay.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
{
"overlay": "1.0.0",
"info": {
"title": "Generated namespace fixes for the Formance Stack OpenAPI spec",
"version": "0.0.1"
},
"actions": [
{
"target": "$.components.schemas[\"ledger_V2BulkElement\"].discriminator.mapping",
"update": {
"CREATE_TRANSACTION": "#/components/schemas/ledger_V2BulkElementCreateTransaction",
"ADD_METADATA": "#/components/schemas/ledger_V2BulkElementAddMetadata",
"REVERT_TRANSACTION": "#/components/schemas/ledger_V2BulkElementRevertTransaction",
"DELETE_METADATA": "#/components/schemas/ledger_V2BulkElementDeleteMetadata"
}
},
{
"target": "$.components.schemas[\"ledger_V2BulkElementResult\"].discriminator.mapping",
"update": {
"CREATE_TRANSACTION": "#/components/schemas/ledger_V2BulkElementResultCreateTransaction",
"ADD_METADATA": "#/components/schemas/ledger_V2BulkElementResultAddMetadata",
"REVERT_TRANSACTION": "#/components/schemas/ledger_V2BulkElementResultRevertTransaction",
"DELETE_METADATA": "#/components/schemas/ledger_V2BulkElementResultDeleteMetadata",
"ERROR": "#/components/schemas/ledger_V2BulkElementResultError"
}
},
{
"target": "$.components.schemas[\"ledger_V2QueryParams\"].oneOf[0].properties.resource.enum",
"remove": true
},
{
"target": "$.components.schemas[\"ledger_V2QueryParams\"].oneOf[0].properties.resource",
"update": {
"const": "accounts"
}
},
{
"target": "$.components.schemas[\"ledger_V2QueryParams\"].oneOf[1].properties.resource.enum",
"remove": true
},
{
"target": "$.components.schemas[\"ledger_V2QueryParams\"].oneOf[1].properties.resource",
"update": {
"const": "transactions"
}
},
{
"target": "$.components.schemas[\"ledger_V2QueryParams\"].oneOf[2].properties.resource.enum",
"remove": true
},
{
"target": "$.components.schemas[\"ledger_V2QueryParams\"].oneOf[2].properties.resource",
"update": {
"const": "logs"
}
},
{
"target": "$.components.schemas[\"ledger_V2QueryParams\"].oneOf[3].properties.resource.enum",
"remove": true
},
{
"target": "$.components.schemas[\"ledger_V2QueryParams\"].oneOf[3].properties.resource",
"update": {
"const": "volumes"
}
},
{
"target": "$.components.schemas[\"payments_ConnectorConfig\"].discriminator.mapping",
"update": {
"Stripe": "#/components/schemas/payments_StripeConfig",
"Dummypay": "#/components/schemas/payments_DummyPayConfig",
"Wise": "#/components/schemas/payments_WiseConfig",
"Modulr": "#/components/schemas/payments_ModulrConfig",
"Currencycloud": "#/components/schemas/payments_CurrencyCloudConfig",
"Bankingcircle": "#/components/schemas/payments_BankingCircleConfig",
"Mangopay": "#/components/schemas/payments_MangoPayConfig",
"Moneycorp": "#/components/schemas/payments_MoneycorpConfig",
"Atlar": "#/components/schemas/payments_AtlarConfig",
"Adyen": "#/components/schemas/payments_AdyenConfig",
"Generic": "#/components/schemas/payments_GenericConfig"
}
},
{
"target": "$.components.schemas[\"payments_V3ConnectorConfig\"].discriminator.mapping",
"update": {
"Adyen": "#/components/schemas/payments_V3AdyenConfig",
"Atlar": "#/components/schemas/payments_V3AtlarConfig",
"Bankingbridge": "#/components/schemas/payments_V3BankingbridgeConfig",
"Bankingcircle": "#/components/schemas/payments_V3BankingcircleConfig",
"Bitstamp": "#/components/schemas/payments_V3BitstampConfig",
"Coinbaseprime": "#/components/schemas/payments_V3CoinbaseprimeConfig",
"Column": "#/components/schemas/payments_V3ColumnConfig",
"Currencycloud": "#/components/schemas/payments_V3CurrencycloudConfig",
"Dummypay": "#/components/schemas/payments_V3DummypayConfig",
"Fireblocks": "#/components/schemas/payments_V3FireblocksConfig",
"Generic": "#/components/schemas/payments_V3GenericConfig",
"Increase": "#/components/schemas/payments_V3IncreaseConfig",
"Krakenpro": "#/components/schemas/payments_V3KrakenproConfig",
"Mangopay": "#/components/schemas/payments_V3MangopayConfig",
"Modulr": "#/components/schemas/payments_V3ModulrConfig",
"Moneycorp": "#/components/schemas/payments_V3MoneycorpConfig",
"Plaid": "#/components/schemas/payments_V3PlaidConfig",
"Powens": "#/components/schemas/payments_V3PowensConfig",
"Qonto": "#/components/schemas/payments_V3QontoConfig",
"Routable": "#/components/schemas/payments_V3RoutableConfig",
"Stripe": "#/components/schemas/payments_V3StripeConfig",
"Tink": "#/components/schemas/payments_V3TinkConfig",
"Wise": "#/components/schemas/payments_V3WiseConfig"
}
},
{
"target": "$.components.schemas[\"wallets_Subject\"].discriminator.mapping",
"update": {
"ACCOUNT": "#/components/schemas/wallets_LedgerAccountSubject",
"WALLET": "#/components/schemas/wallets_WalletSubject"
}
},
{
"target": "$.components.schemas[\"orchestration_Subject\"].discriminator.mapping",
"update": {
"ACCOUNT": "#/components/schemas/orchestration_LedgerAccountSubject",
"WALLET": "#/components/schemas/orchestration_WalletSubject"
}
},
{
"target": "$.components.schemas[\"orchestration_V2Subject\"].discriminator.mapping",
"update": {
"ACCOUNT": "#/components/schemas/orchestration_V2LedgerAccountSubject",
"WALLET": "#/components/schemas/orchestration_V2WalletSubject"
}
}
]
}
29 changes: 28 additions & 1 deletion releases/overlays/shared.overlay.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,36 @@ actions:
update:
x-speakeasy-model-namespace: ledger

# Speakeasy error handling
# Keep infrastructure endpoints in the declared Ledger v2 tag group
- target: $.paths['/api/ledger/_/info'].get.tags[0]
remove: true
- target: $.paths['/api/ledger/_/info'].get.tags
update:
- ledger.v2
- target: $.paths['/api/ledger/_/metrics'].get.tags[0]
remove: true
- target: $.paths['/api/ledger/_/metrics'].get.tags
update:
- ledger.v2

# Remove an orphaned Auth schema that is not referenced by any operation
- target: $.components.schemas.auth_Scope
remove: true
- target: $.components.schemas.auth_ScopeOptions
remove: true

# Remove the duplicated FAILED value from the legacy payment status enum
- target: $.components.schemas.payments_LegacyPaymentStatus.enum[14]
remove: true

# Treat all HTTP error responses as errors. Component specs contribute their
# own defaults during merge, so remove the merged value before replacing it.
- target: $.paths.x-speakeasy-errors
remove: true
- target: $.paths
update:
x-speakeasy-errors:
statusCodes:
- 4XX
- 5XX
- default
Loading