diff --git a/Justfile b/Justfile index 03e6d3700b..b829ddd390 100644 --- a/Justfile +++ b/Justfile @@ -35,7 +35,7 @@ prepend-paths: download-specs yq -i '.paths |= (to_entries | map(select(.key == "/*").key = "/api/connectivity" + .key) | from_entries) | (.paths[] | .. | select(has("tags")).tags) = ["connectivity.v1"] | .paths."/api/connectivity/_info".get.operationId = "connectivityInfo"' components/connectivity.openapi.yaml 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) | (.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/ledger" + .key) | from_entries)' 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/webhooks" + .key) | from_entries)' components/webhooks.openapi.yaml yq -i '.paths |= (to_entries | map(select(.key == "/*").key = "/api/wallets" + .key) | from_entries)' components/wallets.openapi.yaml @@ -70,8 +70,11 @@ 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 + # Speakeasy keeps different operations on the shared Ledger info path under + # namespace fragments; generated SDK requests omit those fragments. + jq -e '[.paths | keys[] | select(contains("#") and . != "/api/ledger/_info#ledger" and . != "/api/ledger/_info#ledgerV3")] | length == 0' releases/build/generate.json >/dev/null + # Preserve both info contracts: V1's data envelope and V3's build metadata. + jq -e '.paths["/api/ledger/_info#ledger"].get as $v1 | .paths["/api/ledger/_info#ledgerV3"].get as $v3 | $v1.operationId == "getInfo" and $v1.tags == ["ledger.v1"] and $v1.responses["200"].content["application/json"].schema["$ref"] == "#/components/schemas/ledger_ConfigInfoResponse" and $v3.operationId == "v3Info" and $v3.tags == ["ledger.v3"] and $v3.responses["200"].content["application/json"].schema["$ref"] == "#/components/schemas/ledgerV3_ServerInfo"' 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. diff --git a/README.md b/README.md index 0bf90f3f20..c344d5d47a 100644 --- a/README.md +++ b/README.md @@ -54,3 +54,15 @@ The Formance Stack is built on open-source, battle tested technologies including - **PostgreSQL** - Main storage backend - **Kafka/NATS** - Cross-services async communication - **Traefik** - Main HTTP gateway + +## OpenAPI composition + +`just build-openapi` composes the component contracts and validates the result. +Ledger V1 and V3 both expose `GET /api/ledger/_info` with different response +models. Both operations remain in the composed document as +`/api/ledger/_info#ledger` and `/api/ledger/_info#ledgerV3`. These are Speakeasy +namespace fragments: generated SDK requests use `/api/ledger/_info`. + +This preserves `Ledger.V1.GetInfo` and `Ledger.V3.Info`. V2's separate +`/api/ledger/_/info` operation remains unchanged. Consumers using other OpenAPI +generators must verify support for namespace fragments.