Загрузка данных


#!/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 compact OpenAPI path catalog to stdout:
- paths whose path key starts with /rest/api/workflow/v2/
- paths whose path key contains suit
- paths whose subtree mentions suit, for example in description, summary,
  operation id, parameters, request bodies, responses, or refs
- short path/method descriptions, operation ids, tags, and internal ref names
- minimum object catalog for referenced objects, with lightweight fields such
  as section, name, type, format, title, description, required/property names,
  enum values, and nested refs

It intentionally does not include full parameters, response bodies, return-code
payloads, or full schema/component bodies.

Example:
USAGE
  echo "  ./${script_name} tt-api-schema.json > workflow-v2-suit.path-catalog.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 compact_text($n):
  if . == null then
    null
  else
    tostring
    | gsub("[[:space:]]+"; " ")
    | if length > $n then .[0:$n] + "..." else . end
  end;

def mentions_suit:
  any(.. | strings; test("suit"; "i"));

def selected_path($prefix):
  (.key | startswith($prefix) or test("suit"; "i"))
  or (.value | mentions_suit);

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 ref_meta:
  pointer_segments as $p
  | {
      ref: .,
      section: (if $p[0] == "components" then $p[1] else $p[0] end),
      name: $p[-1]
    };

def object_summary($doc; $ref):
  (deref($doc; $ref)) as $obj
  | if $obj == null then
      empty
    else
      ($ref | ref_meta)
      + {
          type: $obj.type,
          format: $obj.format,
          title: $obj.title,
          description: ($obj.description | compact_text(360)),
          required: $obj.required,
          enum: (
            if (($obj.enum // null) | type) == "array" then
              ($obj.enum | .[0:30])
            else
              null
            end
          ),
          property_count: (
            (($obj.properties // {}) | length) as $n
            | if $n > 0 then $n else null end
          ),
          property_names: (
            (($obj.properties // {}) | keys | sort) as $names
            | if ($names | length) > 0 then $names[0:60] else null end
          ),
          nested_refs: ([$obj | internal_refs[]] | unique)
        }
      | with_entries(select(.value != null and .value != [] and .value != {}))
    end;

def http_methods:
  ["get", "put", "post", "delete", "patch", "options", "head", "trace"];

def operation_entries:
  to_entries | map(select(.key as $method | http_methods | index($method)));

def compact_operation($entry):
  {
    method: $entry.key,
    operationId: $entry.value.operationId,
    summary: ($entry.value.summary | compact_text(240)),
    description: ($entry.value.description | compact_text(520)),
    tags: $entry.value.tags,
    refs: ($entry.value | internal_refs)
  }
  | with_entries(select(.value != null and .value != []));

. as $spec
| (($spec.paths // {}) | with_entries(select(selected_path($prefix)))) as $selected_paths
| ($selected_paths
    | to_entries
    | map(
        {
            path: .key,
            summary: (.value.summary | compact_text(240)),
            description: (.value.description | compact_text(520)),
            refs: (.value | internal_refs),
            operations: (.value | operation_entries | map(compact_operation(.)))
          }
        | with_entries(select(.value != null and .value != []))
      )
  ) as $path_catalog
| (ref_closure($spec; ($selected_paths | internal_refs))) as $refs
| ([$refs[] as $ref | object_summary($spec; $ref)] | sort_by(.section, .name, .ref)) as $objects
| {
    openapi: $spec.openapi,
    swagger: $spec.swagger,
    info: $spec.info,
    paths: $path_catalog,
    objects: $objects,
    "x-filter": {
      purpose: "workflow-v2 and suit-related path catalog for choosing a later focused full extract",
      path_prefix: $prefix,
      extra_path_match: "path key contains suit, or path subtree mentions suit",
      path_count: ($path_catalog | length),
      operation_count: ([$path_catalog[].operations[]?] | length),
      object_count: ($objects | length),
      note: "No full parameters, responses, request bodies, or full schema/component bodies are included. Objects contain minimum metadata only. Use this catalog to choose exact paths/objects for a later full extract."
    }
  }
| with_entries(select(.key == "paths" or .key == "objects" or .key == "x-filter" or .value != null))
' "$spec_file"