Files
voidraft/frontend/src/views/editor/extensions/httpclient/parser/request-parser.ts
2025-11-03 22:15:45 +08:00

481 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { EditorState } from '@codemirror/state';
import { syntaxTree } from '@codemirror/language';
import type { SyntaxNode } from '@lezer/common';
import { VariableResolver } from './variable-resolver';
/**
* HTTP 请求模型
*/
export interface HttpRequest {
/** 请求方法 */
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
/** 请求 URL */
url: string;
/** 请求头 */
headers: Record<string, string>;
/** 请求体类型 */
bodyType?: 'json' | 'formdata' | 'urlencoded' | 'text' | 'params' | 'xml' | 'html' | 'javascript' | 'binary';
/** 请求体内容 */
body?: any;
/** 原始文本位置信息(用于调试) */
position: {
from: number;
to: number;
line: number;
};
}
/**
* 节点类型常量
*/
const NODE_TYPES = {
REQUEST_STATEMENT: 'RequestStatement',
METHOD: 'Method',
URL: 'Url',
BLOCK: 'Block',
PROPERTY: 'Property',
PROPERTY_NAME: 'PropertyName',
STRING_LITERAL: 'StringLiteral',
NUMBER_LITERAL: 'NumberLiteral',
IDENTIFIER: 'identifier',
AT_RULE: 'AtRule',
JSON_RULE: 'JsonRule',
FORMDATA_RULE: 'FormDataRule',
URLENCODED_RULE: 'UrlEncodedRule',
TEXT_RULE: 'TextRule',
PARAMS_RULE: 'ParamsRule',
XML_RULE: 'XmlRule',
HTML_RULE: 'HtmlRule',
JAVASCRIPT_RULE: 'JavaScriptRule',
BINARY_RULE: 'BinaryRule',
JSON_KEYWORD: 'JsonKeyword',
FORMDATA_KEYWORD: 'FormDataKeyword',
URLENCODED_KEYWORD: 'UrlEncodedKeyword',
TEXT_KEYWORD: 'TextKeyword',
PARAMS_KEYWORD: 'ParamsKeyword',
XML_KEYWORD: 'XmlKeyword',
HTML_KEYWORD: 'HtmlKeyword',
JAVASCRIPT_KEYWORD: 'JavaScriptKeyword',
BINARY_KEYWORD: 'BinaryKeyword',
JSON_BLOCK: 'JsonBlock',
JSON_PROPERTY: 'JsonProperty',
XML_BLOCK: 'XmlBlock',
HTML_BLOCK: 'HtmlBlock',
JAVASCRIPT_BLOCK: 'JavaScriptBlock',
BINARY_BLOCK: 'BinaryBlock',
XML_KEY: 'XmlKey',
HTML_KEY: 'HtmlKey',
JAVASCRIPT_KEY: 'JavaScriptKey',
BINARY_KEY: 'BinaryKey',
VARIABLE_REF: 'VariableRef',
} as const;
/**
* HTTP 请求解析器
*/
export class HttpRequestParser {
private variableResolver: VariableResolver | null = null;
/**
* 构造函数
* @param state EditorState
* @param blockRange 块的范围(可选),如果提供则只解析该块内的变量
*/
constructor(
private state: EditorState,
private blockRange?: { from: number; to: number }
) {
}
/**
* 获取或创建变量解析器(懒加载)
*/
private getVariableResolver(): VariableResolver {
if (!this.variableResolver) {
this.variableResolver = new VariableResolver(this.state, this.blockRange);
}
return this.variableResolver;
}
/**
* 解析指定位置的 HTTP 请求
* @param pos 光标位置或请求起始位置
* @returns 解析后的 HTTP 请求对象,如果解析失败返回 null
*/
parseRequestAt(pos: number): HttpRequest | null {
const tree = syntaxTree(this.state);
// 查找包含该位置的 RequestStatement 节点
const requestNode = this.findRequestNode(tree, pos);
if (!requestNode) {
return null;
}
return this.parseRequest(requestNode);
}
/**
* 查找包含指定位置的 RequestStatement 节点
*/
private findRequestNode(tree: any, pos: number): SyntaxNode | null {
let foundNode: SyntaxNode | null = null;
tree.iterate({
enter: (node: any) => {
if (node.name === NODE_TYPES.REQUEST_STATEMENT) {
if (node.from <= pos && pos <= node.to) {
foundNode = node.node;
return false; // 停止迭代
}
}
}
});
return foundNode;
}
/**
* 解析 RequestStatement 节点
*/
private parseRequest(node: SyntaxNode): HttpRequest | null {
// 使用 Lezer API 直接获取子节点
const methodNode = node.getChild(NODE_TYPES.METHOD);
const urlNode = node.getChild(NODE_TYPES.URL);
const blockNode = node.getChild(NODE_TYPES.BLOCK);
// 验证必需节点
if (!methodNode || !urlNode || !blockNode) {
return null;
}
const method = this.getNodeText(methodNode).toUpperCase();
const url = this.parseUrl(urlNode);
// 验证 URL 非空
if (!url) {
return null;
}
const headers: Record<string, string> = {};
let bodyType: HttpRequest['bodyType'] = undefined;
let body: any = undefined;
// 解析 Block
this.parseBlock(blockNode, headers, (type, content) => {
bodyType = type;
body = content;
});
const line = this.state.doc.lineAt(node.from);
return {
method: method as HttpRequest['method'],
url,
headers,
bodyType,
body,
position: {
from: node.from,
to: node.to,
line: line.number,
},
};
}
/**
* 解析 URL 节点
*/
private parseUrl(node: SyntaxNode): string {
const urlText = this.getNodeText(node);
// 移除引号
const url = urlText.replace(/^["']|["']$/g, '');
// 替换变量
return this.getVariableResolver().replaceVariables(url);
}
/**
* 解析 Block 节点(包含 headers 和 body
*/
private parseBlock(
node: SyntaxNode,
headers: Record<string, string>,
onBody: (type: HttpRequest['bodyType'], content: any) => void
): void {
// 遍历 Block 的子节点
for (let child = node.firstChild; child; child = child.nextSibling) {
if (child.name === NODE_TYPES.PROPERTY) {
// HTTP Header 属性
const { name, value } = this.parseProperty(child);
if (name && value !== null) {
headers[name] = value;
}
} else if (child.name === NODE_TYPES.AT_RULE) {
// AtRule 节点直接获取第一个子节点JsonRule, FormDataRule等
const ruleChild = child.firstChild;
if (ruleChild) {
const { type, content } = this.parseBodyRule(ruleChild);
if (type) { // 只有有效的类型才处理
onBody(type, content);
}
}
}
}
}
/**
* 解析请求体规则
*/
private parseBodyRule(node: SyntaxNode): { type: HttpRequest['bodyType']; content: any } {
// 类型映射表
const typeMap: Record<string, HttpRequest['bodyType']> = {
[NODE_TYPES.JSON_RULE]: 'json',
[NODE_TYPES.FORMDATA_RULE]: 'formdata',
[NODE_TYPES.URLENCODED_RULE]: 'urlencoded',
[NODE_TYPES.TEXT_RULE]: 'text',
[NODE_TYPES.PARAMS_RULE]: 'params',
[NODE_TYPES.XML_RULE]: 'xml',
[NODE_TYPES.HTML_RULE]: 'html',
[NODE_TYPES.JAVASCRIPT_RULE]: 'javascript',
[NODE_TYPES.BINARY_RULE]: 'binary',
};
const type = typeMap[node.name];
// 根据不同的规则类型解析不同的块
let content: any = null;
if (node.name === NODE_TYPES.XML_RULE) {
const blockNode = node.getChild(NODE_TYPES.XML_BLOCK);
content = blockNode ? this.parseFixedKeyBlock(blockNode, 'xml') : null;
} else if (node.name === NODE_TYPES.HTML_RULE) {
const blockNode = node.getChild(NODE_TYPES.HTML_BLOCK);
content = blockNode ? this.parseFixedKeyBlock(blockNode, 'html') : null;
} else if (node.name === NODE_TYPES.JAVASCRIPT_RULE) {
const blockNode = node.getChild(NODE_TYPES.JAVASCRIPT_BLOCK);
content = blockNode ? this.parseFixedKeyBlock(blockNode, 'javascript') : null;
} else if (node.name === NODE_TYPES.BINARY_RULE) {
const blockNode = node.getChild(NODE_TYPES.BINARY_BLOCK);
content = blockNode ? this.parseFixedKeyBlock(blockNode, 'binary') : null;
} else {
// json, formdata, urlencoded, text, params 使用 JsonBlock
const blockNode = node.getChild(NODE_TYPES.JSON_BLOCK);
content = blockNode ? this.parseJsonBlock(blockNode) : null;
}
return {
type,
content
};
}
/**
* 解析固定 key 的块xml, html, javascript, binary
* 格式:{ key: "value" } 或 {}(空块)
*/
private parseFixedKeyBlock(node: SyntaxNode, keyName: string): any {
// 查找固定的 key 节点
const keyNode = node.getChild(
keyName === 'xml' ? NODE_TYPES.XML_KEY :
keyName === 'html' ? NODE_TYPES.HTML_KEY :
keyName === 'javascript' ? NODE_TYPES.JAVASCRIPT_KEY :
NODE_TYPES.BINARY_KEY
);
// 如果没有 key返回空对象支持空块
if (!keyNode) {
return {};
}
// 查找值节点(冒号后面的内容)
let value: any = null;
for (let child = node.firstChild; child; child = child.nextSibling) {
if (child.name === NODE_TYPES.STRING_LITERAL ||
child.name === NODE_TYPES.VARIABLE_REF) {
value = this.parseValue(child);
break;
}
}
// 返回格式:{ xml: "value" } 或 { html: "value" } 等
return value !== null ? { [keyName]: value } : {};
}
/**
* 解析 JsonBlock用于 @json, @form, @urlencoded
*/
private parseJsonBlock(node: SyntaxNode): any {
const result: any = {};
// 遍历 JsonProperty
for (let child = node.firstChild; child; child = child.nextSibling) {
if (child.name === NODE_TYPES.JSON_PROPERTY) {
const { name, value } = this.parseJsonProperty(child);
if (name && value !== null) {
result[name] = value;
}
}
}
return result;
}
/**
* 解析 JsonProperty
*/
private parseJsonProperty(node: SyntaxNode): { name: string | null; value: any } {
// 使用 API 获取属性名
const nameNode = node.getChild(NODE_TYPES.PROPERTY_NAME);
if (!nameNode) {
return { name: null, value: null };
}
const name = this.getNodeText(nameNode);
// 尝试获取值节点String, Number, JsonBlock, VariableRef
let value: any = null;
for (let child = node.firstChild; child; child = child.nextSibling) {
if (child.name === NODE_TYPES.STRING_LITERAL ||
child.name === NODE_TYPES.NUMBER_LITERAL ||
child.name === NODE_TYPES.JSON_BLOCK ||
child.name === NODE_TYPES.VARIABLE_REF ||
child.name === NODE_TYPES.IDENTIFIER) {
value = this.parseValue(child);
return { name, value };
}
}
// 回退:从文本中提取值(用于 true/false 等标识符)
const fullText = this.getNodeText(node);
const colonIndex = fullText.indexOf(':');
if (colonIndex !== -1) {
const valueText = fullText.substring(colonIndex + 1).trim().replace(/,$/, '').trim();
value = this.parseValueFromText(valueText);
}
return { name, value };
}
/**
* 从文本解析值
*/
private parseValueFromText(text: string): any {
// 布尔值
if (text === 'true') return true;
if (text === 'false') return false;
if (text === 'null') return null;
// 数字
if (/^-?\d+(\.\d+)?$/.test(text)) {
return parseFloat(text);
}
// 字符串(带引号)
if ((text.startsWith('"') && text.endsWith('"')) ||
(text.startsWith("'") && text.endsWith("'"))) {
return text.slice(1, -1);
}
// 其他标识符
return text;
}
/**
* 解析 PropertyHTTP Header
*/
private parseProperty(node: SyntaxNode): { name: string | null; value: any } {
const nameNode = node.getChild(NODE_TYPES.PROPERTY_NAME);
if (!nameNode) {
return { name: null, value: null };
}
const name = this.getNodeText(nameNode);
let value: any = null;
// 查找值节点(跳过冒号和逗号)
for (let child = node.firstChild; child; child = child.nextSibling) {
if (child.name !== NODE_TYPES.PROPERTY_NAME &&
child.name !== ':' &&
child.name !== ',') {
value = this.parseValue(child);
break;
}
}
// HTTP Header 的值必须转换为字符串
if (value !== null && value !== undefined) {
value = String(value);
}
return { name, value };
}
/**
* 解析值节点(字符串、数字、标识符、嵌套块、变量引用)
*/
private parseValue(node: SyntaxNode): any {
if (node.name === NODE_TYPES.STRING_LITERAL) {
const text = this.getNodeText(node);
// 移除引号
const value = text.replace(/^["']|["']$/g, '');
// 替换字符串中的变量
return this.getVariableResolver().replaceVariables(value);
} else if (node.name === NODE_TYPES.NUMBER_LITERAL) {
const text = this.getNodeText(node);
return parseFloat(text);
} else if (node.name === NODE_TYPES.VARIABLE_REF) {
// 处理变量引用节点
const text = this.getNodeText(node);
const resolver = this.getVariableResolver();
const ref = resolver.parseVariableRef(text);
if (ref) {
return resolver.resolveVariable(ref.name, ref.defaultValue);
}
return text;
} else if (node.name === NODE_TYPES.IDENTIFIER) {
const text = this.getNodeText(node);
// 处理布尔值
if (text === 'true') return true;
if (text === 'false') return false;
// 处理 null
if (text === 'null') return null;
// 其他标识符作为字符串
return text;
} else if (node.name === NODE_TYPES.JSON_BLOCK) {
// 嵌套对象
return this.parseJsonBlock(node);
} else {
// 未知类型,返回原始文本
return this.getNodeText(node);
}
}
/**
* 获取节点的文本内容
*/
private getNodeText(node: SyntaxNode): string {
return this.state.doc.sliceString(node.from, node.to);
}
}
/**
* 便捷函数:解析指定位置的 HTTP 请求
* @param state EditorState
* @param pos 光标位置
* @param blockRange 块的范围(可选),如果提供则只解析该块内的变量
*/
export function parseHttpRequest(
state: EditorState,
pos: number,
blockRange?: { from: number; to: number }
): HttpRequest | null {
const parser = new HttpRequestParser(state, blockRange);
return parser.parseRequestAt(pos);
}