/** * Advanced Request Builder Module * Handles parameter composition, validation, defaults, and intelligent request building */ const RequestBuilder = (() => { "use strict"; /** * Parameter state management */ class ParameterState { constructor() { this.path = {}; this.query = {}; this.header = {}; this.cookie = {}; this.body = null; this.bodyType = 'json'; // json, form, raw, binary this.formData = {}; } clone() { const copy = new ParameterState(); copy.path = { ...this.path }; copy.query = { ...this.query }; copy.header = { ...this.header }; copy.cookie = { ...this.cookie }; copy.body = this.body; copy.bodyType = this.bodyType; copy.formData = { ...this.formData }; return copy; } clear() { this.path = {}; this.query = {}; this.header = {}; this.cookie = {}; this.body = null; this.bodyType = 'json'; this.formData = {}; } } /** * Build request object from endpoint definition and parameter state */ function buildRequest(endpoint, paramState, baseUrl) { const req = { method: endpoint.method, path: resolvePath(endpoint.path, paramState.path), url: buildUrl(baseUrl, endpoint.path, paramState), headers: buildHeaders(endpoint, paramState), query: paramState.query, body: buildBody(endpoint, paramState), cookies: paramState.cookie }; return req; } /** * Resolve path parameters */ function resolvePath(pathTemplate, pathParams) { let path = pathTemplate; Object.entries(pathParams).forEach(([key, value]) => { // Encode URI component but preserve slashes in path path = path.replace(`:${key}`, encodeURIComponent(String(value || ''))); path = path.replace(`{${key}}`, encodeURIComponent(String(value || ''))); }); return path; } /** * Build complete URL with query parameters */ function buildUrl(baseUrl, path, paramState) { let resolvedPath = resolvePath(path, paramState.path); let url = baseUrl.replace(/\/$/, '') + resolvedPath; const queryParams = new URLSearchParams(); Object.entries(paramState.query).forEach(([key, value]) => { if (value !== undefined && value !== null && value !== '') { queryParams.append(key, String(value)); } }); const qs = queryParams.toString(); if (qs) url += '?' + qs; return url; } /** * Build headers including auth, content-type, etc */ function buildHeaders(endpoint, paramState) { const headers = {}; // User-provided headers Object.entries(paramState.header).forEach(([key, value]) => { if (value !== undefined && value !== null && value !== '') { headers[key] = String(value); } }); // Auto content-type based on body if (paramState.body !== null && paramState.body !== undefined) { if (paramState.bodyType === 'json') { headers['Content-Type'] = 'application/json'; } else if (paramState.bodyType === 'form') { headers['Content-Type'] = 'application/x-www-form-urlencoded'; } } // Copy cookies to header if not already set if (Object.keys(paramState.cookie).length > 0) { const cookieStr = Object.entries(paramState.cookie) .filter(([, v]) => v != null && v !== '') .map(([k, v]) => `${k}=${encodeURIComponent(v)}`) .join('; '); if (cookieStr) headers['Cookie'] = cookieStr; } return headers; } /** * Build request body */ function buildBody(endpoint, paramState) { if (!paramState.body) return null; if (paramState.bodyType === 'json') { if (typeof paramState.body === 'string') { try { // Validate JSON JSON.parse(paramState.body); return paramState.body; } catch (e) { return null; // Invalid JSON } } return JSON.stringify(paramState.body); } if (paramState.bodyType === 'form') { const params = new URLSearchParams(); Object.entries(paramState.formData).forEach(([key, value]) => { if (value !== undefined && value !== null && value !== '') { params.append(key, String(value)); } }); return params.toString(); } return paramState.body; } /** * Validate parameters against endpoint schema */ function validateParameters(endpoint, paramState) { const errors = []; const warnings = []; if (!endpoint.parameters) return { errors, warnings }; endpoint.parameters.forEach((param) => { const value = paramState[param.in]?.[param.name]; const isEmpty = value === undefined || value === null || value === ''; // Check required if (param.required && isEmpty) { errors.push(`Parameter '${param.name}' is required`); } // Validate type and format if (!isEmpty) { const validation = validateParameterValue(value, param); if (!validation.valid) { errors.push(`Parameter '${param.name}': ${validation.message}`); } } // Check for deprecated params if (param.deprecated) { warnings.push(`Parameter '${param.name}' is deprecated`); } }); return { errors, warnings }; } /** * Validate a parameter value against its schema */ function validateParameterValue(value, param) { const strValue = String(value); // Validate type if (param.schema?.type) { switch (param.schema.type) { case 'integer': if (!/^-?\d+$/.test(strValue)) { return { valid: false, message: `must be an integer` }; } if (param.schema.minimum !== undefined && parseInt(strValue) < param.schema.minimum) { return { valid: false, message: `must be >= ${param.schema.minimum}` }; } if (param.schema.maximum !== undefined && parseInt(strValue) > param.schema.maximum) { return { valid: false, message: `must be <= ${param.schema.maximum}` }; } break; case 'number': if (!/^-?\d+(\.\d+)?$/.test(strValue)) { return { valid: false, message: `must be a number` }; } break; case 'boolean': if (!['true', 'false', '1', '0'].includes(strValue.toLowerCase())) { return { valid: false, message: `must be true or false` }; } break; case 'string': if (param.schema.minLength !== undefined && strValue.length < param.schema.minLength) { return { valid: false, message: `must be at least ${param.schema.minLength} characters` }; } if (param.schema.maxLength !== undefined && strValue.length > param.schema.maxLength) { return { valid: false, message: `must be at most ${param.schema.maxLength} characters` }; } if (param.schema.pattern && !new RegExp(param.schema.pattern).test(strValue)) { return { valid: false, message: `invalid format` }; } break; } } // Validate enum if (param.schema?.enum && !param.schema.enum.includes(value)) { return { valid: false, message: `must be one of: ${param.schema.enum.join(', ')}` }; } return { valid: true }; } /** * Fill parameters from examples or defaults */ function populateDefaults(endpoint, paramState) { if (!endpoint.parameters) return; endpoint.parameters.forEach((param) => { if (!paramState[param.in]) paramState[param.in] = {}; // Skip if already set if (paramState[param.in][param.name] !== undefined && paramState[param.in][param.name] !== null) { return; } // Try example if (param.example !== undefined) { paramState[param.in][param.name] = param.example; return; } // Try schema default if (param.schema?.default !== undefined) { paramState[param.in][param.name] = param.schema.default; return; } // Provide smart defaults based on type if (param.schema?.type) { switch (param.schema.type) { case 'integer': paramState[param.in][param.name] = param.schema.minimum || 1; break; case 'number': paramState[param.in][param.name] = param.schema.minimum || 0.0; break; case 'boolean': paramState[param.in][param.name] = true; break; case 'string': paramState[param.in][param.name] = ''; break; } } }); } /** * Generate example body from schema */ function generateExampleBody(schema, schemas) { if (!schema) return null; // Handle references if (schema.$ref) { const refName = schema.$ref.split('/').pop(); schema = schemas?.[refName] || schema; } if (schema.type === 'object') { const example = {}; if (schema.properties) { Object.entries(schema.properties).forEach(([key, propSchema]) => { if (propSchema.example !== undefined) { example[key] = propSchema.example; } else if (propSchema.type === 'string') { example[key] = `<${key}>`; } else if (propSchema.type === 'integer') { example[key] = propSchema.minimum || 1; } else if (propSchema.type === 'number') { example[key] = propSchema.minimum || 0.0; } else if (propSchema.type === 'boolean') { example[key] = true; } else if (propSchema.type === 'array') { example[key] = [generateExampleBody(propSchema.items, schemas)]; } }); } return example; } if (schema.type === 'array') { return [generateExampleBody(schema.items, schemas)]; } if (schema.example !== undefined) return schema.example; switch (schema.type) { case 'string': return 'example'; case 'integer': return 0; case 'number': return 0.0; case 'boolean': return true; default: return null; } } /** * Extract suggestion data from response to prefill next request */ function extractSuggestions(response, responseSchema) { const suggestions = {}; if (!response || typeof response !== 'object') return suggestions; if (responseSchema?.type === 'object' && responseSchema.properties) { Object.keys(responseSchema.properties).forEach((key) => { if (response[key] !== undefined) { suggestions[key] = response[key]; } }); } return suggestions; } /** * Check if request is ready to send */ function isRequestReady(endpoint, paramState) { if (!endpoint) return false; if (!endpoint.parameters) return true; const hasErrors = endpoint.parameters.some((param) => { if (!param.required) return false; const value = paramState[param.in]?.[param.name]; return value === undefined || value === null || value === ''; }); return !hasErrors; } // Public API return { ParameterState, buildRequest, resolvePath, buildUrl, buildHeaders, buildBody, validateParameters, validateParameterValue, populateDefaults, generateExampleBody, extractSuggestions, isRequestReady }; })(); // Export for use in other modules if (typeof module !== 'undefined' && module.exports) { module.exports = RequestBuilder; }