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
62 changes: 48 additions & 14 deletions .github/scripts/generate-composition-overlay.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,55 @@ output_file="${2:-releases/overlays/generated.overlay.json}"

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

base_input_count=$(yq -r '
[.sources[].inputs[] | select(.modelNamespace == null)]
| length
' "$workflow_file")

if [[ "$base_input_count" != "1" ]]; then
echo "expected exactly one base OpenAPI input, found $base_input_count" >&2
exit 1
fi

base_location=$(yq -r '
.sources[].inputs[]
| select(.modelNamespace == null)
| .location
' "$workflow_file")

{
# Component metadata is merged with the base metadata by Speakeasy. Restore
# the canonical info object from the sole non-namespaced base input.
yq -o=json -I=0 '{"target": "$.info", "update": .info}' "$base_location"

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" '
# Extract only discriminator metadata before converting to JSON. Some
# component specs contain valid uint64 bounds that yq cannot marshal
# through its signed JSON integer representation.
yq -o=json -I=0 '
.components.schemas as $schemas
| $schemas
| ..
| select(kind == "map" and .discriminator.mapping != null)
| {
"path": path,
"mapping": .discriminator.mapping,
"schema_names": ($schemas | keys)
}
| select(.mapping != null)
' "$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
.path[2] as $schema_name
| .path[3:] as $path
| .mapping as $mapping
| .schema_names as $schema_names
| {
target: (
"$.components.schemas["
Expand All @@ -40,15 +71,18 @@ mkdir -p "$(dirname "$output_file")"
. as $ref
| if (
($ref | startswith("#/components/schemas/"))
and ($schemas[$ref | sub("^#/components/schemas/"; "")] != null)
and (
($schema_names | index($ref | sub("^#/components/schemas/"; "")))
!= null
)
)
then (
"#/components/schemas/"
+ $namespace
+ "_"
+ ($ref | sub("^#/components/schemas/"; ""))
)
elif $schemas[$ref] != null
elif ($schema_names | index($ref)) != null
then "#/components/schemas/" + $namespace + "_" + $ref
else $ref
end
Expand All @@ -62,9 +96,9 @@ mkdir -p "$(dirname "$output_file")"
# 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[]
yq -o=json -I=0 '.components.schemas.V2QueryParams.oneOf // []' "$location" \
| jq -c --arg namespace "$namespace" '
to_entries[]
| select(
(.value.properties.resource.enum? | type) == "array"
and (.value.properties.resource.enum | length) == 1
Expand Down Expand Up @@ -105,7 +139,7 @@ mkdir -p "$(dirname "$output_file")"
{
overlay: "1.0.0",
info: {
title: "Generated namespace fixes for the Formance Stack OpenAPI spec",
title: "Generated composition fixes for the Formance Stack OpenAPI spec",
version: "0.0.1"
},
actions: .
Expand Down
8 changes: 5 additions & 3 deletions .speakeasy/workflow.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
speakeasyVersion: 1.796.4
speakeasyVersion: 1.797.0
sources:
stacks-source:
sourceNamespace: stacks-source
sourceRevisionDigest: sha256:e21e54e7818b64f5bd5b04dc7d0f16434b7d6cd22ec399eab0b8b4353ca72c21
sourceBlobDigest: sha256:bab4ef7cb2db033bdbfea628f9fa8831eea64b2990bdc91e35552809d3497e9b
sourceRevisionDigest: sha256:9df14dce9ad22e9a6989dda36e511ab3ce3973fcad5f1bbfce8118cc49f2e3e6
sourceBlobDigest: sha256:da34e684e433e7d236caa00e25f3a97d66ad660153d0201f78ffaecfde5b2b00
tags:
- latest
- SDK_VERSION
Expand All @@ -19,6 +19,8 @@ workflow:
- location: ./components/gateway.openapi.yaml
modelNamespace: gateway
- location: ./components/ledger.openapi.yaml
modelNamespace: ledgerV3
- location: ./components/ledger-v2.openapi.yaml
modelNamespace: ledger
- location: ./components/payments.openapi.yaml
modelNamespace: payments
Expand Down
2 changes: 2 additions & 0 deletions .speakeasy/workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ sources:
- location: ./components/gateway.openapi.yaml
modelNamespace: gateway
- location: ./components/ledger.openapi.yaml
modelNamespace: ledgerV3
- location: ./components/ledger-v2.openapi.yaml
modelNamespace: ledger
- location: ./components/payments.openapi.yaml
modelNamespace: payments
Expand Down
27 changes: 18 additions & 9 deletions Justfile
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
# Component versions
LEDGER_VERSION := "v2.4.12"
LEDGER_VERSION := "v3.0.0-beta.3"
LEDGER_V2_VERSION := "v2.4.12"
PAYMENTS_VERSION := "v3.4.4"
WALLETS_VERSION := "v2.2.0"
WEBHOOKS_VERSION := "v2.5.0"
AUTH_VERSION := "v2.5.0"
WALLETS_VERSION := "v2.2.1"
WEBHOOKS_VERSION := "v2.5.4"
AUTH_VERSION := "v2.5.1"
SEARCH_VERSION := "v2.1.0"
ORCHESTRATION_VERSION := "v2.6.0"
RECONCILIATION_VERSION := "v2.4.0"
GATEWAY_VERSION := "v2.3.1"
ORCHESTRATION_VERSION := "v2.6.4"
RECONCILIATION_VERSION := "v2.5.0"
GATEWAY_VERSION := "v2.3.2"

# Download all component OpenAPI specs from GitHub releases
download-specs:
mkdir -p components
wget -q https://github.com/formancehq/ledger/releases/download/{{ LEDGER_VERSION }}/openapi.yaml -O components/ledger.openapi.yaml
wget -q https://github.com/formancehq/ledger/releases/download/{{ LEDGER_VERSION }}/openapi.yml -O components/ledger.openapi.yaml
wget -q https://github.com/formancehq/ledger/releases/download/{{ LEDGER_V2_VERSION }}/openapi.yaml -O components/ledger-v2.openapi.yaml
wget -q https://github.com/formancehq/payments/releases/download/{{ PAYMENTS_VERSION }}/openapi.yaml -O components/payments.openapi.yaml
wget -q https://raw.githubusercontent.com/formancehq/gateway/{{ GATEWAY_VERSION }}/openapi.yaml -O components/gateway.openapi.yaml
wget -q https://github.com/formancehq/auth/releases/download/{{ AUTH_VERSION }}/openapi.yaml -O components/auth.openapi.yaml
Expand All @@ -25,7 +27,8 @@ download-specs:
# Prepend API path prefix to each component spec
prepend-paths: download-specs
yq -i '.paths |= (to_entries | map(select(.key == "/*").key = "/api/auth" + .key) | from_entries)' components/auth.openapi.yaml
yq -i '.paths |= (to_entries | map(select(.key == "/*").key = "/api/ledger" + .key) | from_entries)' components/ledger.openapi.yaml
yq -i '.paths |= (to_entries | map(select(.key == "/*").key = "/api/ledger" + .key) | from_entries) | (.paths[] | .. | select(has("tags")).tags) = ["ledger.v3"]' components/ledger.openapi.yaml
yq -i '.paths |= (to_entries | map(select(.key == "/*").key = "/api/ledger" + .key) | from_entries) | del(.paths."/api/ledger/_info")' components/ledger-v2.openapi.yaml
yq -i '.paths |= (to_entries | map(select(.key == "/*").key = "/api/payments" + .key) | from_entries)' components/payments.openapi.yaml
yq -i '.paths |= (to_entries | map(select(.key == "/*").key = "/api/search" + .key) | from_entries)' components/search.openapi.yaml
yq -i '.paths |= (to_entries | map(select(.key == "/*").key = "/api/webhooks" + .key) | from_entries)' components/webhooks.openapi.yaml
Expand All @@ -51,6 +54,8 @@ build-openapi version="v0.0.0": generate-composition-overlay

# Validate invariants introduced by composing namespaced OpenAPI documents.
validate-openapi:
# Global API metadata must remain exactly sourced from base.yaml.
jq -e --argjson base_info "$(yq -o=json -I=0 '.info' releases/base.yaml)" '(.info | .version = $base_info.version) == $base_info' releases/build/generate.json >/dev/null
# 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.
Expand All @@ -59,6 +64,10 @@ validate-openapi:
test "$(jq -c '.paths["x-speakeasy-errors"].statusCodes' releases/build/generate.json)" = '["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
# Namespace conflict suffixes are merge artifacts, never callable API paths.
jq -e '[.paths | keys[] | select(contains("#"))] | length == 0' releases/build/generate.json >/dev/null
# Both supported Ledger API generations must remain in the composed spec.
jq -e '.paths["/api/ledger/v2"] != null and .paths["/api/ledger/v3/"] != null' 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
Expand Down
2 changes: 2 additions & 0 deletions releases/base.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ servers:
tags:
- name: ledger.v1
- name: ledger.v2
- name: ledger.v3
- name: payments.v1
- name: payments.v3
- name: auth.v1
Expand Down Expand Up @@ -70,6 +71,7 @@ x-tagGroups:
tags:
- ledger.v1
- ledger.v2
- ledger.v3
- name: Payments
tags:
- payments.v1
Expand Down
19 changes: 18 additions & 1 deletion releases/overlays/generated.overlay.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
{
"overlay": "1.0.0",
"info": {
"title": "Generated namespace fixes for the Formance Stack OpenAPI spec",
"title": "Generated composition fixes for the Formance Stack OpenAPI spec",
"version": "0.0.1"
},
"actions": [
{
"target": "$.info",
"update": {
"title": "Formance Stack API",
"description": "Open, modular foundation for unique payments flows\n\n# Introduction\nThis API is documented in **OpenAPI format**.\n\n# Authentication\nFormance Stack offers one forms of authentication:\n - OAuth2\nOAuth2 - an open protocol to allow secure authorization in a simple\nand standard method from web, mobile and desktop applications.\n<SecurityDefinitions />\n",
"contact": {
"name": "Formance",
"url": "https://www.formance.com",
"email": "support@formance.com"
},
"x-logo": {
"url": "https://avatars.githubusercontent.com/u/84325077?s=200&v=4",
"altText": "Formance"
},
"version": "SDK_VERSION"
}
},
{
"target": "$.components.schemas[\"ledger_V2BulkElement\"].discriminator.mapping",
"update": {
Expand Down
5 changes: 0 additions & 5 deletions releases/overlays/shared.overlay.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@ info:
title: Shared overlay for Formance Stack OpenAPI spec
version: 0.0.1
actions:
# Restore info.version (overwritten by last-wins merge from component specs)
- target: $.info
update:
version: SDK_VERSION

# Remove the NoAuthorization securityScheme definition (injected by component merge)
- target: $.components.securitySchemes.NoAuthorization
remove: true
Expand Down
Loading