#!/usr/bin/env bash
set -euo pipefail
usage() {
local script_name
script_name=$(basename "$0")
printf 'Usage: ./%s OPENAPI_SPEC.json\n\n' "$script_name" >&2
cat >&2 <<'USAGE'
Print a focused OpenAPI JSON extract to stdout:
- paths whose path key starts with /rest/api/workflow/v2/
- only top-level schema objects referenced from those paths, following internal
$ref links transitively through the spec
Example:
USAGE
echo " ./${script_name} tt-api-schema.json > workflow-v2.extract.json" >&2
}
if [[ $# -ne 1 ]]; then
usage
exit 2
fi
spec_file=$1
prefix="/rest/api/workflow/v2/"
if ! command -v jq >/dev/null 2>&1; then
echo "error: jq is required" >&2
exit 127
fi
if [[ ! -f "$spec_file" ]]; then
echo "error: input file not found: $spec_file" >&2
exit 1
fi
jq --arg prefix "$prefix" '
def pointer_segments:
sub("^#/"; "")
| split("/")
| map(gsub("~1"; "/") | gsub("~0"; "~"));
def internal_refs:
[.. | objects | .["$ref"]? | strings | select(startswith("#/"))] | unique;
def deref($doc; $ref):
if ($ref | startswith("#/")) then
try ($doc | getpath($ref | pointer_segments)) catch empty
else
empty
end;
def ref_closure($doc; $start):
{seen: [], pending: ($start | unique)}
| until((.pending | length) == 0;
.pending as $todo
| .seen = ((.seen + $todo) | unique)
| ([ $todo[] as $ref | deref($doc; $ref) | internal_refs[] ] | unique) as $next
| .pending = ($next - .seen)
)
| .seen;
def component_schema_name:
select(startswith("#/components/schemas/"))
| pointer_segments as $p
| $p[2];
def definition_name:
select(startswith("#/definitions/"))
| pointer_segments as $p
| $p[1];
. as $spec
| (($spec.paths // {}) | with_entries(select(.key | startswith($prefix)))) as $paths
| (ref_closure($spec; ($paths | internal_refs))) as $refs
| ([$refs[] | component_schema_name] | unique) as $component_schema_names
| ([$refs[] | definition_name] | unique) as $definition_names
| (($spec.components.schemas // {})
| with_entries(select(.key as $name | ($component_schema_names | index($name)) != null))
) as $filtered_component_schemas
| (($spec.definitions // {})
| with_entries(select(.key as $name | ($definition_names | index($name)) != null))
) as $filtered_definitions
| {
openapi: $spec.openapi,
swagger: $spec.swagger,
info: $spec.info,
paths: $paths,
components: (
if ($filtered_component_schemas | length) > 0 then
{schemas: $filtered_component_schemas}
else
null
end
),
definitions: (
if ($filtered_definitions | length) > 0 then
$filtered_definitions
else
null
end
),
"x-filter": {
path_prefix: $prefix,
path_count: ($paths | length),
component_schema_count: ($filtered_component_schemas | length),
definition_count: ($filtered_definitions | length)
}
}
| with_entries(select(.key == "paths" or .key == "x-filter" or .value != null))
' "$spec_file"