Загрузка данных
#!/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 phase-2 TaskTracker OpenAPI extract to stdout.
The extract includes full OpenAPI details only for the paths and schema objects
identified from the phase-1 catalog as needed for OAS operator-ticket runtime
discovery:
- root/unit create, read, find, and suit conversion paths
- space, suit, workflow, and unit-parameter discovery paths
- the known local link-create path when present
- a narrow set of unit link/relation candidate paths, because phase 1 did not
prove the exact target link-create endpoint
- selected target objects and their transitive internal $ref dependencies
Example:
USAGE
echo " ./${script_name} tt-api-schema.json > tt-ticket.phase2.extract.json" >&2
}
if [[ $# -ne 1 ]]; then
usage
exit 2
fi
spec_file=$1
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 '
def wanted_paths:
[
"/rest/api/unit/v2/{suit}/create",
"/rest/api/unit/v2/{code}",
"/rest/api/unit/v1/find",
"/rest/api/unit/v1/convert/suit",
"/rest/api/space/v2/{code}",
"/rest/api/space/type/v1/{code}",
"/rest/api/space/v3/{code}/suits",
"/rest/api/space/configuration/v1/{space}/suits/find",
"/rest/api/space/v3/manage/parameters/{unitCode}",
"/rest/api/workflow/v1/suit/{suitCode}/space/{spaceCode}/workflow",
"/rest/api/workflow/v1/space/type/{spaceTypeCode}/suit/{suitCode}/list",
"/rest/api/unit/v1/link"
];
def wanted_schema_names:
[
"UnitWithAttributes",
"UnitAttributeCodeValueInfo",
"UserInfoWithDetails",
"SuitWithAttributesInfo",
"SpaceInfo",
"SpaceTypeDetailsDto",
"SpaceTypeConfigurationDto",
"LinkTypes",
"StatusResponseDto",
"UpdateResponse"
];
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 null
else
null
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) | select(. != null) | internal_refs[] ] | unique) as $next
| .pending = ($next - .seen)
)
| .seen;
def text_blob:
[.. | strings] | join("\n") | ascii_downcase;
def unit_link_candidate:
(.key | ascii_downcase) as $path
| (.value | text_blob) as $body
| (
($path | test("(^|/)(unit[-_/])?links?($|/|-)|(^|/)relations?($|/|-)|parent|child"))
and
($body | test("source|destination|from|to|parent|child|unit|ticket|issue|link|relation"))
);
def wanted_schema_refs($doc):
[
wanted_schema_names[] as $name
| (
if (($doc.components.schemas // {}) | has($name)) then
"#/components/schemas/\($name)"
else
empty
end
),
(
if (($doc.definitions // {}) | has($name)) then
"#/definitions/\($name)"
else
empty
end
)
] | unique;
def component_names($refs; $section):
[
$refs[]
| select(startswith("#/components/\($section)/"))
| pointer_segments[2]
] | unique;
def selected_component_section($doc; $refs; $section):
(component_names($refs; $section)) as $names
| (($doc.components[$section] // {})
| with_entries(select(.key as $name | ($names | index($name)) != null)));
def selected_components($doc; $refs):
reduce (($doc.components // {}) | keys_unsorted[]) as $section ({};
(selected_component_section($doc; $refs; $section)) as $selected
| if ($selected | length) > 0 then
. + {($section): $selected}
else
.
end
);
def definition_names($refs):
[
$refs[]
| select(startswith("#/definitions/"))
| pointer_segments[1]
] | unique;
def selected_definitions($doc; $refs):
(definition_names($refs)) as $names
| (($doc.definitions // {})
| with_entries(select(.key as $name | ($names | index($name)) != null)));
def top_level_ref_names($refs; $section):
[
$refs[]
| select(startswith("#/\($section)/"))
| pointer_segments[1]
] | unique;
def selected_top_level_ref_section($doc; $refs; $section):
(top_level_ref_names($refs; $section)) as $names
| (($doc[$section] // {})
| with_entries(select(.key as $name | ($names | index($name)) != null)));
. as $spec
| (($spec.paths // {}) | type) as $paths_type
| if $paths_type != "object" then
error("OpenAPI spec must have object-valued .paths; do not pass the phase-1 catalog as input")
else
(wanted_paths) as $wanted_paths
| (($spec.paths // {})
| with_entries(select((.key as $path | ($wanted_paths | index($path)) != null) or unit_link_candidate))
) as $paths
| ([
$wanted_paths[] as $path
| select((($spec.paths // {}) | has($path)) | not)
| $path
]) as $missing_exact_paths
| (($paths | keys | sort) - ($wanted_paths | sort)) as $included_link_candidate_paths
| (($paths | internal_refs) + wanted_schema_refs($spec)) as $start_refs
| (ref_closure($spec; $start_refs)) as $refs
| (selected_components($spec; $refs)) as $components
| (selected_definitions($spec; $refs)) as $definitions
| (selected_top_level_ref_section($spec; $refs; "parameters")) as $parameters
| (selected_top_level_ref_section($spec; $refs; "responses")) as $responses
| ([
wanted_schema_names[] as $name
| select(
((($spec.components.schemas // {}) | has($name)) or (($spec.definitions // {}) | has($name))) | not
)
| $name
]) as $missing_schema_names
| {
openapi: $spec.openapi,
swagger: $spec.swagger,
info: $spec.info,
servers: $spec.servers,
security: $spec.security,
paths: $paths,
parameters: (
if ($parameters | length) > 0 then
$parameters
else
null
end
),
responses: (
if ($responses | length) > 0 then
$responses
else
null
end
),
components: (
if ($components | length) > 0 then
$components
else
null
end
),
definitions: (
if ($definitions | length) > 0 then
$definitions
else
null
end
),
"x-phase2-filter": {
purpose: "focused full extract for Sprint 45 OAS TaskTracker runtime-discovery spike",
exact_path_count: ($wanted_paths | length),
selected_path_count: ($paths | length),
selected_component_section_count: ($components | length),
selected_definition_count: ($definitions | length),
selected_top_level_parameter_count: ($parameters | length),
selected_top_level_response_count: ($responses | length),
selected_internal_ref_count: ($refs | length),
wanted_exact_paths: $wanted_paths,
missing_exact_paths: $missing_exact_paths,
included_link_candidate_paths: $included_link_candidate_paths,
wanted_schema_names: wanted_schema_names,
missing_schema_names: $missing_schema_names,
note: "Full path/component bodies are included only for the exact wanted paths, selected schema names, their transitive internal refs, and narrow unit link/relation path candidates."
}
}
| with_entries(select(.key == "paths" or .key == "x-phase2-filter" or .value != null))
end
' "$spec_file"