import yaml from 'js-yaml' import type { RequestFile, RequestInfo, HttpConfig, ParamItem, HeaderItem, BodyConfig, AuthConfig, RuntimeConfig, ScriptItem, AssertionItem, VarItem, RequestSettings, BodyType, FormField, GraphQLBody } from '../types.js' const VALID_BODY_TYPES: BodyType[] = ['json', 'text', 'xml', 'sparql', 'graphql', 'form-urlencoded', 'multipart-form', 'file', 'none'] function parseInfo(raw: Record, sourceFile: string): RequestInfo { if (!raw.name || typeof raw.name !== 'string' || raw.name.trim() === '') { throw new Error(`${sourceFile}: Missing required field 'info.name'`) } return { name: raw.name, type: typeof raw.type === 'string' ? raw.type : 'http', seq: typeof raw.seq === 'number' ? raw.seq : undefined, tags: Array.isArray(raw.tags) ? raw.tags.map(String) : undefined, } } function parseParams(raw: unknown): ParamItem[] { if (!Array.isArray(raw)) return [] return raw.map((p: Record) => ({ name: String(p.name ?? ''), value: String(p.value ?? ''), type: p.type === 'path' ? 'path' : 'query', disabled: Boolean(p.disabled), })) } function parseHeaders(raw: unknown): HeaderItem[] { if (!Array.isArray(raw)) return [] return raw.map((h: Record) => ({ name: String(h.name ?? ''), value: String(h.value ?? ''), disabled: Boolean(h.disabled), })) } function parseBody(raw: unknown): BodyConfig | undefined { if (!raw || typeof raw !== 'object') return undefined const r = raw as Record const type = String(r.type ?? 'none') as BodyType if (!VALID_BODY_TYPES.includes(type)) return { type: 'none' } if (type === 'none') return { type: 'none' } if (type === 'form-urlencoded' || type === 'multipart-form') { const data: FormField[] = Array.isArray(r.data) ? r.data.map((f: Record) => ({ name: String(f.name ?? ''), value: String(f.value ?? ''), enabled: f.enabled !== false, type: typeof f.type === 'string' ? f.type : undefined, })) : [] return { type, data } } if (type === 'graphql') { const d = r.data as Record | undefined const gql: GraphQLBody = { query: typeof d?.query === 'string' ? d.query : '', variables: typeof d?.variables === 'string' ? d.variables : undefined, } return { type, data: gql } } return { type, data: typeof r.data === 'string' ? r.data : '' } } function parseAuth(raw: unknown): AuthConfig | undefined { if (!raw || typeof raw !== 'object') return undefined const r = raw as Record const type = String(r.type ?? 'none') const auth: AuthConfig = { type } if (type === 'basic' && r.basic && typeof r.basic === 'object') { const b = r.basic as Record auth.basic = { username: String(b.username ?? ''), password: String(b.password ?? '') } } if (type === 'bearer' && r.bearer && typeof r.bearer === 'object') { const b = r.bearer as Record auth.bearer = { token: String(b.token ?? '') } } if (type === 'apikey' && r.apikey && typeof r.apikey === 'object') { const b = r.apikey as Record auth.apikey = { key: String(b.key ?? ''), value: String(b.value ?? ''), placement: String(b.placement ?? 'header') } } if (type === 'digest' && r.digest && typeof r.digest === 'object') { const b = r.digest as Record auth.digest = { username: String(b.username ?? ''), password: String(b.password ?? '') } } if (type === 'ntlm' && r.ntlm && typeof r.ntlm === 'object') { const b = r.ntlm as Record auth.ntlm = { username: String(b.username ?? ''), password: String(b.password ?? ''), domain: typeof b.domain === 'string' ? b.domain : undefined } } if (type === 'wsse' && r.wsse && typeof r.wsse === 'object') { const b = r.wsse as Record auth.wsse = { username: String(b.username ?? ''), password: String(b.password ?? '') } } if (type === 'awsv4' && r.awsv4 && typeof r.awsv4 === 'object') { const b = r.awsv4 as Record auth.awsv4 = { accessKeyId: String(b.accessKeyId ?? ''), secretAccessKey: String(b.secretAccessKey ?? ''), sessionToken: typeof b.sessionToken === 'string' ? b.sessionToken : undefined, service: typeof b.service === 'string' ? b.service : undefined, region: typeof b.region === 'string' ? b.region : undefined, profileName: typeof b.profileName === 'string' ? b.profileName : undefined, } } if (type === 'oauth2') { auth.oauth2 = typeof r.oauth2 === 'object' && r.oauth2 !== null ? r.oauth2 as Record : {} } return auth } function parseHttp(raw: Record, sourceFile: string): HttpConfig { if (!raw.method || typeof raw.method !== 'string' || raw.method.trim() === '') { throw new Error(`${sourceFile}: Missing required field 'http.method'`) } if (!raw.url || typeof raw.url !== 'string' || raw.url.trim() === '') { throw new Error(`${sourceFile}: Missing required field 'http.url'`) } return { method: raw.method.toUpperCase(), url: raw.url, params: parseParams(raw.params), headers: parseHeaders(raw.headers), body: parseBody(raw.body), auth: parseAuth(raw.auth), } } function parseVars(raw: unknown): VarItem[] { if (!Array.isArray(raw)) return [] return raw.map((v: Record) => ({ name: String(v.name ?? ''), value: String(v.value ?? ''), enabled: v.enabled !== false, local: Boolean(v.local), })) } function parseRuntime(raw: unknown): RuntimeConfig | undefined { if (!raw || typeof raw !== 'object') return undefined const r = raw as Record const scripts: ScriptItem[] = Array.isArray(r.scripts) ? r.scripts .filter((s: Record) => ['before-request', 'after-response', 'tests'].includes(String(s.type))) .map((s: Record) => ({ type: String(s.type) as ScriptItem['type'], code: String(s.code ?? ''), })) : [] const assertions: AssertionItem[] = Array.isArray(r.assertions) ? r.assertions.map((a: Record) => ({ expression: String(a.expression ?? ''), operator: String(a.operator ?? 'eq'), value: typeof a.value === 'string' ? a.value : undefined, disabled: Boolean(a.disabled), })) : [] const varsRaw = r.vars as Record | undefined const vars = varsRaw ? { req: parseVars(varsRaw.req), res: parseVars(varsRaw.res), } : undefined if (scripts.length === 0 && assertions.length === 0 && !vars) return undefined return { scripts: scripts.length ? scripts : undefined, assertions: assertions.length ? assertions : undefined, vars } } function parseSettings(raw: unknown): RequestSettings | undefined { if (!raw || typeof raw !== 'object') return undefined const r = raw as Record return { encodeUrl: typeof r.encodeUrl === 'boolean' ? r.encodeUrl : undefined, timeout: typeof r.timeout === 'number' ? r.timeout : undefined, followRedirects: typeof r.followRedirects === 'boolean' ? r.followRedirects : undefined, maxRedirects: typeof r.maxRedirects === 'number' ? r.maxRedirects : undefined, } } export function parseRequest(yamlContent: string, sourceFile = ''): RequestFile { const raw = yaml.load(yamlContent) as Record if (!raw || typeof raw !== 'object') { throw new Error(`${sourceFile}: Invalid YAML — expected an object`) } const info = parseInfo((raw.info as Record) ?? {}, sourceFile) const http = parseHttp((raw.http as Record) ?? {}, sourceFile) const runtime = parseRuntime(raw.runtime) const settings = parseSettings(raw.settings) const docs = typeof raw.docs === 'string' ? raw.docs : undefined return { info, http, runtime, settings, docs } }