class

Analyzer::Specification::SpecificationEngine

Inherits Analyzer < FileHelper < Reference < Object

Base for the specification-format analyzers (OpenAPI, Postman, HAR, Terraform, k8s manifests, proxy exports, …).

They do not walk the file tree the way the language engines do. The detector recognises a document and pushes its path into CodeLocator under a key, and the analyzer later drains that key. Forty-odd analyzers each open-coded the same drain:

locator = CodeLocator.instance
files = locator.all(Noir::LocatorKeys::OAS3_JSON)
return @result unless files.is_a?(Array(String))
files.each do |path|
  next unless File.exists?(path)
  begin
    ...
  rescue e
    @logger.debug "..."
    @logger.debug_sub e
  end
end

with just enough variation between copies that some dropped the existence check, some dropped the per-file rescue (letting one malformed document abort every remaining file for that analyzer), and the log wording differed everywhere. each_spec_file is that drain, once.

Constants

ABSOLUTE_SERVER_URL = /\A[A-Za-z][A-Za-z0-9+.\-]*:\/\//

scheme:// prefix of an absolute URL. //host/path is deliberately not matched here — it is protocol-relative and handled separately.

CLIENT_COLLECTION_KEYS = Set {Noir::LocatorKeys::POSTMAN_JSON.name, Noir::LocatorKeys::BRUNO_BRU.name, Noir::LocatorKeys::INSOMNIA_JSON.name, Noir::LocatorKeys::INSOMNIA_YAML.name, Noir::LocatorKeys::HOPPSCOTCH_JSON.name, Noir::LocatorKeys::THUNDER_CLIENT_JSON.name, Noir::LocatorKeys::HTTP_FILE.name, Noir::LocatorKeys::HURL_FILE.name, Noir::LocatorKeys::HAR_PATH.name, Noir::LocatorKeys::BURP_SITEMAP.name, Noir::LocatorKeys::ZAP_SITES_TREE.name, Noir::LocatorKeys::CAIDO_JSON.name, Noir::LocatorKeys::MITMPROXY_PATH.name}

Recorded requests and client collections kept under a test tree usually exercise the real API, so they are never gated.

GENERATED_CLIENT_FILE = /(?:\A|\/)(?:client\.go|configuration\.(?:go|rs)|api_client\.(?:py|rb)|rest\.py|ApiClient\.(?:java|kt|cs)|runtime\.ts|base\.ts)\z/

A client runtime file in an openapi-generator FILES manifest. Server stubs list api/openapi.yaml too, but their templates do not emit these (a custom one can: base.ts, rest.py and client.go are common names).

GENERATED_SERVER_FILE = /(?:\A|\/)(?i:controllers?|handlers?|routers?|routes|servers?)\/|ApiController\.(?:java|kt)\z|Api(?:Delegate|Service(?:Impl)?)\.(?:java|kt|scala)\z|(?:\A|\/)(?:main\.go|routers\.go|__main__\.py|OpenApiGeneratorApplication\.java|OpenAPI2SpringBoot\.java)\z/

What only a server stub's manifest lists: a controller/handler/router/ server directory, Spring's *ApiController/*ApiDelegate/*ApiService and the entry points of the go, python and spring server templates. It outweighs the client runtime above. Deliberately no bare Controller.*, Application.*, server.* or app.*: SDKs generate models by those names (kubernetes V1ReplicationController, hcloud models/server.rs).

GENERATOR_NAME = /"generatorName"\s*:\s*"([^"]+)"/

generatorName in the generator-cli config next to the manifest, and the generators among them that build a server.

MUSTACHE_VAR = /\{\{\s*([A-Za-z0-9_.-]+)\s*\}\}/

A {{ var }} placeholder, the template syntax of .http, Hurl and Thunder Client files (Hoppscotch's <<var>> is rewritten to it).

OAS_KEYS = Set {Noir::LocatorKeys::SWAGGER_JSON.name, Noir::LocatorKeys::SWAGGER_YAML.name, Noir::LocatorKeys::OAS3_JSON.name, Noir::LocatorKeys::OAS3_YAML.name}
REMOTE_REF = /\A(?:[A-Za-z][A-Za-z0-9+.\-]*:)?\/\//

scheme://host/… and protocol-relative //host/… ref targets.

SCHEMA_KEYS = Set {Noir::LocatorKeys::SWAGGER_JSON.name, Noir::LocatorKeys::SWAGGER_YAML.name, Noir::LocatorKeys::OAS3_JSON.name, Noir::LocatorKeys::OAS3_YAML.name, Noir::LocatorKeys::ASYNCAPI_JSON.name, Noir::LocatorKeys::ASYNCAPI_YAML.name, Noir::LocatorKeys::OPENRPC_JSON.name, Noir::LocatorKeys::RAML_SPEC.name, Noir::LocatorKeys::GRAPHQL_SDL.name, Noir::LocatorKeys::GRAPHQL_OPERATION.name, Noir::LocatorKeys::GRPC_PROTO.name, Noir::LocatorKeys::WSDL_SPEC.name, Noir::LocatorKeys::THRIFT_IDL.name, Noir::LocatorKeys::SMITHY_SPEC.name, Noir::LocatorKeys::TYPESPEC_SPEC.name, Noir::LocatorKeys::ODATA_SPEC.name}
SCHEMA_TEST_TREE = /\/(?:tests?|__tests__|e2e|test_data|service references|connected services)\/|\.(?:unit|integration|functional|e2e)?tests?\//i

Schema and definition documents are additionally gated on the generic test/tests segments (and the .Tests .NET project directories), which would be too broad for infra configs: overlays/test and deploy/test hold real test-environment deployments. Visual Studio's Service References/Connected Services folders hold the proxy of a service the project consumes.

SERVER_GENERATOR = /\A(?:spring|aspnetcore|scalatra|jaxrs-.+|nodejs-.+)\z|-(?:server|spring|aspnetcore|flask|fastapi|aiohttp|laravel|lumen|slim4?|symfony|flight|axum|sinatra|rails|vertx-web|play-framework|finch|inflector|camel)\z/
TEST_TREE = /\/(?:src\/(?:test|sbt-test|it|androidTest)|testdata|fixtures|__fixtures__|__snapshots__)\/|\.(?:verified|received)\.[^\/]+\z/i

A spec or config document inside a test tree is test input, a golden output or a generated client proxy: it describes something the project is tested against or consumes, not something it serves. Matched against the scan-base-relative path, so a checkout that merely lives under a directory with one of these names is unaffected.

TEST_TREE holds the unambiguous markers and gates every key but the client collections. spec/specs/examples/samples are deliberately absent: real API descriptions live there.

Class methods

test_tree?(key : Noir::LocatorKey(Array(String)), relative : String) : Bool

The gate itself, on a scan-base-relative path. Public so an analyzer that drains a spec key outside each_spec_file (go_connect_rpc reads the gRPC protos) applies the very same rule instead of a copy of it.

Source