Updated UI
This commit is contained in:
@@ -0,0 +1,399 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user