Bruno importer research and design¶
Implemented in internal/importing/bruno; researched against the official Bruno repository on 2026-09-30. Parse handles one standalone .bru HTTP request; Load handles a file or a directory containing bruno.json. Neither function writes output or executes source code.
Primary evidence and semantic decisions¶
- The official v2 grammar and semantic actions distinguish dictionary and text blocks. Closing braces at column zero terminate blocks. HTTP JSON and JavaScript content is not parsed by counting braces. Dictionary keys can be quoted and disabled with
~; duplicate pairs are possible. Triple-quoted dictionary values use four-space slicing. Text values use the two-space outdent utility. The importer implements these distinctions, normalizes CRLF, accepts a UTF-8 BOM, and preserves raw text whitespace after grammar indentation removal. - The upstream request fixture covers quoted colons, escaped quotes, disabled rows, independent query/path parameters, multiple body representations, and executable blocks. Tests here use independently authored examples exercising those cases. Active method/body/auth selectors determine conversion; saved inactive auth bodies do not select themselves.
- Collection parsing has an
authblock selecting a mode, distinct from request method-blockauth. Runtime collection merging applies collection, folder, and request variables in increasing precedence, and only enabled rows override parents. The importer copies each scope before descending, so siblings cannot alter each other. Disabled header overrides preserve the enabled inherited header. Header names are matched case-insensitively, in keeping with inherited-header handling. - Auth inheritance had conflicting implementation evidence: the CLI merge helper historically ignored folder
none, whereas the official regression tests explicitly require foldernoneto stop inheritance. The importer follows those tests: explicitinheritselects the nearest available parent, andnoneterminates the chain. - HTTP request preparation uses mode-selected bodies and API-key placement. Variable interpolation has different behavior inside JSON strings. Static values are recursively expanded per request; JSON string substitutions are escaped while unquoted JSON values remain raw. Unbound identifier references become Posting
${NAME}.process.env.NAMEalso becomes${NAME}without reading the importing process's environment. Literal dollar signs are escaped for Posting's substitution rules.
Support and limits¶
Supported: Posting's GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS methods; metadata names and docs; URL/query/path fields; disabled rows; inherited headers; JSON/text/XML/SPARQL and URL-encoded forms; Basic/Digest/Bearer and header/query API keys; collection/folder/static request variables; redirect and numeric timeout settings. URL query entries already represented in params:query are not duplicated, repeated URL keys survive, and fragments are preserved. Filenames preserve source folders when loading directories.
Bruno's variable precedence is runtime, request, folder, environment, collection, then global. Collection variables from collection.bru therefore become the base environment, posting.env, and stay ${NAME} references in requests. Folder and request variables outrank environments, so they are materialized into each request to avoid scope leakage. A collection variable that refers to a folder or request variable is materialized with it. Collection variables whose names Posting cannot reference are materialized too.
Load reads environments/*.bru at the collection root into named environments, one per file stem, in sorted order. Files go through the same bounded, symlink-rejecting reads as requests. Non-.bru entries and subdirectories are skipped with a warning. An environment file has a vars dictionary, where ~ disables a row, and a vars:secret list block. Secret values live in Bruno's app storage, not on disk, so the importer names them in a warning and suggests <name>.local.env. A folder called environments below the root holds requests as usual.
Runtime/post-response vars, tests, assertions, scripts, type/description annotations, examples, application blocks, unsupported settings, multipart/uploads, legacy raw body blocks, GraphQL bodies, and unsupported auth emit diagnostics. Scripts never execute; upload references are never opened. Non-HTTP requests and HTTP methods Posting cannot serialize are skipped with diagnostics. Standalone Parse cannot access parent settings and diagnoses unresolved auth inheritance.
Unbound JSON-body references require JSON-escaped runtime values, because Posting has no context-aware JSON substitution; this emits a warning. Unsupported variable expressions and cycles are retained with warnings. Acyclic expansion exceeding depth/work/output bounds fails. Bruno collection ignore patterns are diagnosed rather than interpreted; hidden directories and node_modules are excluded.
Resource limits are 16 MiB per input file, 128 MiB per collection, 10,000 files, 20,000 visited directory entries, 64 folder levels, 10,000 blocks/rows, and 500 detailed warnings. Collection reads use os.Root, reject special files and symlink metadata, and skip symlink entries. Output goes exclusively through the caller's safe collection writer.
Verification¶
Semantic tests cover dictionary quoting, disabled values, multiline form values, JSON escaping, raw-body preservation, repeated query keys, fragments, auth boundaries, sibling isolation, literal dollars, cycles, expansion bombs, unsupported-feature warnings, malformed inputs, and symlink rejection. Import-to-Posting YAML roundtrips exercise the actual serializer. FuzzParse checks that arbitrary bytes cannot crash the parser and every successful result serializes into a valid Posting request. The initial 20-second, two-worker fuzz run completed 853,930 executions without failure; the separate probing phase follows integration.