OpenAPI 2.0 (Swagger)¶
Parses a Swagger 2.0 specification into the Vayu draft model. Swagger 2.0, like OpenAPI 3.0, is a specification document, not a request log - it describes endpoints, parameters, and schemas but carries no concrete values. The parser therefore emits synthetic request stubs: a {{baseUrl}} built from schemes/host/basePath, query/header params with empty values, and a body sampled from the in: "body" parameter schema. Users fill in real values after import.
- Source:
app/src/services/importers/openapi-v2.ts - Exports:
| Symbol | formatName |
formatKey |
|---|---|---|
OpenApiV2Parser (class, implements ImportParser) |
OpenAPI 2.0 (Swagger) |
openapi-v2 |
swaggerSchemeToAuth (helper) |
- | - |
OpenApiV2Parser exposes formatName / formatKey as readonly fields and implements detect + parse from ./types.
Detection¶
The top-level swagger field must equal the exact string "2.0". OpenAPI 3.x (the openapi field, no swagger) is handled by a separate parser - see OpenAPI v3. The factory (factory.ts) parses the raw text once (JSON, then YAML fallback) and runs each parser's detect in registration order.
Tree structure¶
The spec maps to a single root collection, with operations grouped into child collections by their first tag.
- Root collection ← the whole spec (named from
info.title). It directly holds: requests: every untagged operation (rootRequests).children: one child collection per distinct first-tag, in first-encounter order.- Tag child collections ← created lazily, inline in
parse, the first time a tag is seen, keyed in aMap<string, CollectionDraft>(tagCollections). Description comes from the matching entry in the top-leveltags[]array (if any).
Iteration order: parse loops over spec.paths entries, and for each path item over the fixed HTTP_METHODS list (get, post, put, patch, delete, head, options). For each present operation it calls buildSwaggerOp(...), then routes by tag:
const tag = op.tags?.[0]; // ONLY the first tag is used
if (tag) tagCollections.get(tag).requests.push(req);
else rootRequests.push(req);
Multi-tag operations: only op.tags[0] is consulted. An operation with tags: ["a", "b"] lands solely in the a child collection; b is ignored (no duplication, no extra folder). An operation with no tags (or tags: []) becomes a root request.
Unlike v3 (which has dedicated makeTagCollection / buildBody helpers), v2 builds the root and tag collections inline in parse and delegates only the per-operation draft to buildSwaggerOp. A closed-over resolveRef handles $ref resolution.
Field mapping¶
Collection (root)¶
Built inline in parse.
| Swagger | Vayu CollectionDraft |
Notes |
|---|---|---|
info.title |
name |
fallback "Imported API" |
info.description |
description |
fallback "" |
schemes + host + basePath |
variables.baseUrl |
only added when host is present: { baseUrl: { value: baseUrl, enabled: true } }; otherwise variables is {}. See Base URL. |
security / securityDefinitions |
auth |
via the picked primary scheme + swaggerSchemeToAuth (see Auth). Collections never inherit. |
| (none) | preRequestScript / postRequestScript |
always "" |
| tag groups | children |
[...tagCollections.values()] |
| untagged operations | requests |
Collection (per tag)¶
Built inline when a tag is first encountered.
| Swagger | Vayu CollectionDraft |
Notes |
|---|---|---|
tag (the string) |
name |
|
tags[].description where tags[].name === tag |
description |
fallback "" |
| (none) | variables |
always {} (baseUrl lives only on the root) |
| (none) | auth |
always { mode: "none" } - tag collections do not carry security |
| (none) | children |
always [] (tags are flat; no nesting) |
| (none) | preRequestScript / postRequestScript |
always "" |
Request (per operation)¶
Built by buildSwaggerOp(method, path, op, spec, resolveRef, pathParams).
| Swagger | Vayu RequestDraft |
Notes |
|---|---|---|
op.summary → op.operationId → "{METHOD} {path}" |
name |
precedence in that order; final fallback uses upper-cased method + raw path, e.g. "GET /users/{id}" |
op.description |
description |
fallback "" |
| HTTP method | method |
method.toUpperCase() (e.g. get → GET), cast to HttpMethod |
path |
url |
`{{baseUrl}}${normalizeVars(path)}` - always prefixed with {{baseUrl}}, even if no host was defined (see URL) |
parameter in: "query" |
params |
{ key: name, value: "", enabled: true, description? } - description included only when present |
parameter in: "header" |
headers |
{ key: name, value: "", enabled: true } - no description carried; authorization and content-type headers are dropped (case-insensitive) since Vayu manages those |
parameter in: "body" |
body |
sampled via sampleSchema; JSON vs text decided by consumes (see Parameters & body) |
parameter in: "formData" |
body |
collected into form-data fields (see below) |
parameter in: "path" |
- | not emitted as params/headers; path params are represented in the URL via normalizeVars |
| (none) | auth |
always { mode: "inherit" } - auth is configured once at the collection level |
| (none) | preRequestScript / postRequestScript |
always "" |
Parameter resolution & merge. buildSwaggerOp concatenates path-item-level parameters (passed in as pathParams) with operation-level op.parameters, resolving any $ref entries via resolveRef. Each parameter is keyed by `${in}:${name}` in a Map (byKey), so an operation-level parameter overrides a path-level one with the same in+name (later writes win). Entries missing in or name after resolution are skipped.
Base URL construction¶
The base URL is assembled from three top-level spec fields and stored as the baseUrl collection variable on the root only:
const scheme = (spec.schemes?.[0] as string) ?? "https";
const basePath = spec.basePath && spec.basePath !== "/" ? spec.basePath : "";
const baseUrl = spec.host ? `${scheme}://${spec.host}${basePath}` : "";
- Scheme - the first entry of
schemes[]; ifschemesis absent/empty, defaults to"https". Additional schemes (http,ws,wss, …) are ignored - onlyschemes[0]is used. basePath- appended verbatim unless it is missing or exactly"/"(in which case it contributes nothing, avoiding a trailing//).host- required for a base URL. Ifhostis absent,baseUrlis"", nobaseUrlvariable is added (rootvariablesstays{}), and request URLs still carry the literal{{baseUrl}}prefix (resolving to empty at runtime).
Examples (host: "api.example.com"):
schemes |
basePath |
baseUrl |
|---|---|---|
["https"] |
"/v2" |
https://api.example.com/v2 |
["http", "https"] |
"/v2" |
http://api.example.com/v2 (first scheme only) |
| (absent) | "/" |
https://api.example.com (default scheme, / dropped) |
["https"] |
(absent) | https://api.example.com |
When set, the value is stored as variables.baseUrl = { value: baseUrl, enabled: true }.
URL & path parameters¶
- The request
urlis always`{{baseUrl}}${normalizeVars(path)}`.{{baseUrl}}is the Vayu collection variable described above (defined on the root collection). If the spec has nohost,baseUrlis absent from the root variables and{{baseUrl}}resolves to empty at runtime. - Swagger path templates
{param}are converted to Vayu{{param}}bynormalizeVars(var-normalize.ts). It rewrites single-brace{x}(identifier chars[\w$-]) to{{x}}, while leaving any existing{{...}}pairs intact. So/users/{userId}/posts/{postId}becomes/users/{{userId}}/posts/{{postId}}. Path parameters (in: "path") are not also emitted asparamsentries - they live only in the URL.
Parameters & body¶
Swagger 2.0 has no requestBody object (unlike v3). Request bodies are expressed as ordinary parameters with a special in value. buildSwaggerOp iterates the resolved, deduped parameters and dispatches on param.in:
param.in |
Effect | Detail |
|---|---|---|
query |
push to params |
{ key, value: "", enabled: true, description? }; description only when present |
header |
push to headers |
{ key, value: "", enabled: true }; skipped when name.toLowerCase() is authorization or content-type |
body |
set body |
sample = param.schema ? sampleSchema(param.schema, resolveRef) : {}; serialized with JSON.stringify(sample, null, 2). Mode is JSON or text per consumes (below). |
formData |
collect into formFields |
{ key: name, value: "", enabled: true } per field |
| (anything else) | ignored | no default case action |
After the loop, form data wins: if any formData fields were collected, body is unconditionally replaced with { mode: "form-data", fields: formFields } - overriding any body set by an in: "body" parameter. (A spec mixing both would be unusual, but the code resolves it in favor of form-data.)
consumes → body mode¶
The JSON-vs-text decision for an in: "body" parameter is driven by consumes:
const consumes = op.consumes ?? spec.consumes ?? [];
const isJsonConsume =
consumes.length === 0 ||
consumes.some(
(c) => c === "application/json" || c.startsWith("application/json;") || c.endsWith("+json")
);
consumesis taken from the operation first, falling back to the spec-levelconsumes, else[].isJsonConsumeistruewhenconsumesis empty (default assumption: JSON) or any entry is exactlyapplication/json, starts withapplication/json;(e.g. with a charset), or ends with+json(e.g.application/hal+json).- Body mode:
isJsonConsume === true→{ mode: "json", content: JSON.stringify(sample, null, 2) }- otherwise →
{ mode: "text", content: JSON.stringify(sample, null, 2) }
Note the text branch still serializes the sampled schema to JSON text (it does not blank the body - this differs from v3's text/plain handling, which emits an empty string).
$ref & schema sampling¶
resolveRef resolves any JSON-pointer ref against the whole spec - Swagger model refs are #/definitions/..., but the resolver is generic. It strips the leading #/, splits on /, un-escapes ~1→/ and ~0→~, and walks the spec object segment by segment.
Body schemas are turned into stub values by sampleSchema(schema, resolveRef) (schema-sampler.ts), which walks the schema recursively. It is bounded and recursive - materially more capable than older "one level deep" notes:
- Depth cap.
MAX_DEPTH = 6. Oncedepth > 6, the walker returns{}. Non-object / null nodes also return{}. $refresolution + cycle guard. A node with a string$ref(e.g.#/definitions/User) is resolved viaresolveRefand walked (depth +1). ASetof already-visited$refstrings is threaded down each branch; re-encountering a$refalready on the current path returns{}(breaks reference cycles). Resolution failures (throwornullresult) also yield{}.examplepreference. If the schema node has anexamplefield, that value is returned verbatim (checked after$ref, before composition andtype). This lets authors pin exact sample values.allOf/oneOf/anyOf- first branch. If any of these is a non-empty array, the walker recurses intobranch[0]only (precedenceallOf→oneOf→anyOf). It does not mergeallOfmembers.- Type defaults:
schema.type |
Sample value |
|---|---|
string |
enum[0] if a non-empty enum is present, else "" |
integer / number |
0 |
boolean |
false |
array |
[ sample(items) ] if items is present, else [] (one element) |
object (or no/unknown type) |
walks each entry of properties, producing { key: sample }; {} if no properties |
sampleSchema is shared verbatim with the v3 parser - same depth cap, cycle guard, and branch handling.
collectionFormat for array query params¶
Not implemented. Swagger 2.0's collectionFormat (csv / ssv / tsv / pipes / multi) on array parameters is not consulted anywhere in the parser. A query parameter - array or scalar - produces exactly one KeyValueEntry with an empty value:
params.push({ key: param.name, value: "", enabled: true, ...(description ? { description } : {}) });
There is no per-value expansion, no separator joining, and no multi handling. The parameter's type, items, and collectionFormat are ignored entirely (the parser produces empty-value stubs regardless of declared type). multi does not emit one entry per value - it emits the same single empty-value entry as any other query param.
Auth / security¶
Auth is applied only at the root collection; every request is { mode: "inherit" }, so the user configures credentials once.
Primary-scheme selection in parse:
const reqName = spec.security?.[0] ? Object.keys(spec.security[0])[0] : undefined;
const defs = spec.securityDefinitions ?? {};
const primaryScheme = (reqName && defs[reqName]) || Object.values(defs)[0];
- If
spec.security[0]exists, take its first key and look it up insecurityDefinitions. - Otherwise (or if that lookup is falsy) fall back to the first entry of
securityDefinitions.
swaggerSchemeToAuth(scheme) maps the picked scheme to a concrete collection auth (always with empty secrets - the spec has no real credentials):
securityDefinitions entry |
Vayu RequestAuth |
|---|---|
type: "basic" |
{ mode: "basic", username: "", password: "" } |
type: "apiKey" |
{ mode: "apikey", key: scheme.name ?? "", value: "", in: scheme.in === "query" ? "query" : "header" } |
type: "oauth2" |
{ mode: "oauth2", config: OAuth2Config } via mapSwaggerOAuth2 - maps the Swagger flow (application → client-credentials, accessCode → auth-code+PKCE, password, implicit→auth-code+PKCE), fills tokenUrl/authorizationUrl/scope, seeds clientId/clientSecret as {{variables}} |
missing scheme / missing type / any other type |
{ mode: "none" } |
nonExecutableAuth: always 0 - oauth2 now maps to an executable config (as do bearer/basic/apikey).
Options & lossy behavior¶
This parser is stub-only: it materializes the shape of each request but no values. The ImportOptions argument (importEnvironments, importScripts) is ignored - the parameter is _opts and is never read (identical to v3).
Dropped / not represented:
- Scripts: all
preRequestScript/postRequestScriptare""(Swagger has no scripts;importScriptshas no effect here). - Environments: none produced (
environments: [],meta.environmentCount: 0). Swagger has no environment concept; thescheme/host/basePathtriple becomes a singlebaseUrlcollection variable. - Additional schemes: only
schemes[0]is used; other schemes are dropped. collectionFormat, parametertype/items,required,default,enumon params: not consumed - query/header/form params are always empty-value stubs.produces, response schemas, response examples: not consumed.authorization/content-typeheader parameters: dropped (Vayu manages them).- Path parameters as params: not emitted (path params live in the URL only).
- Multi-tag grouping: only the first tag groups an operation.
SkippedItems: never emitted -meta.skippedis always[].
meta population: format = "OpenAPI 2.0 (Swagger)", requestCount = total operations built, folderCount = number of tag collections (tagCollections.size), environmentCount = 0, skipped = [], nonExecutableAuth = 0 (oauth2 is now executable).
Differences from OpenAPI 3.0¶
See OpenAPI v3 for the v3 reference. Key contrasts:
| Aspect | v2 (Swagger) | v3 (OpenAPI 3.0) |
|---|---|---|
| Detection | swagger === "2.0" (exact) |
openapi is a string starting with "3." |
| Base URL | schemes[0] + host + basePath |
servers[0].url |
| Request body | in: "body" / in: "formData" parameters |
dedicated op.requestBody with content map |
| Body content-type decision | consumes (op → spec → JSON default) |
media-type keys of requestBody.content |
| Text/non-JSON body | sampled schema serialized as JSON text | text/plain → empty string |
| Form bodies | in: "formData" params → form-data (overrides body param) |
multipart/form-data / x-www-form-urlencoded from content |
$ref namespace |
#/definitions/... |
#/components/schemas/... (resolver is generic in both) |
| Auth schemes | securityDefinitions (basic, apiKey, oauth2) |
components.securitySchemes (http/bearer/basic, apiKey, oauth2) |
| Auth helper | swaggerSchemeToAuth |
schemeToAuth |
| Collection build | inline in parse |
helper makeTagCollection |
Shared between both: tree-by-first-tag, {{baseUrl}}-prefixed URLs, normalizeVars path conversion, sampleSchema, request auth: inherit, ImportOptions ignored, meta.skipped always empty.
Shared helpers used¶
| Helper | Source | Use in this parser |
|---|---|---|
normalizeVars |
var-normalize.ts |
convert Swagger {param} path templates → Vayu {{param}} in request URLs |
sampleSchema |
schema-sampler.ts |
generate a sample JSON body from an in: "body" parameter schema (bounded, ref-resolving) |
This parser does not use the Postman/Insomnia helpers in shared.ts (asString, toVarRecord, mapKeyValues, mapPostmanAuth, rawBody, joinExec); it builds drafts directly. See the index for the full shared-helper reference.