mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
fix(elasticsearch): format Kibana-style REST requests
This commit is contained in:
@@ -27,6 +27,7 @@ import { buildSqlInConditionFromPasteSource, insertTextForSqlInCondition } from
|
||||
import { resolveSqlSingleQuoteKeyAction } from "@/lib/sql/sqlQuoteCaret";
|
||||
import { convertSqlSelectionCase, type SqlSelectionCaseMode } from "@/lib/sql/sqlSelectionCase";
|
||||
import { formatMongoShellText } from "@/lib/mongo/mongoFormatter";
|
||||
import { detectAndFormatElasticsearchRequests } from "@/lib/elasticsearch/elasticsearchFormatter";
|
||||
import { useConnectionStore, COMPLETION_METADATA_CONCURRENCY } from "@/stores/connectionStore";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import { useTheme } from "@/composables/useTheme";
|
||||
@@ -2932,19 +2933,27 @@ async function formatCurrentSql() {
|
||||
if (props.databaseType === "mongodb") {
|
||||
formatted = formatMongoShellText(source, settingsStore.editorSettings.sqlFormatter);
|
||||
} else {
|
||||
const structured = detectAndFormatStructured(source, {
|
||||
indentSize: settingsStore.editorSettings.sqlFormatter.tabWidth,
|
||||
useTabs: settingsStore.editorSettings.sqlFormatter.useTabs,
|
||||
});
|
||||
if (structured.kind === "json" || structured.kind === "xml") {
|
||||
formatted = structured.formatted;
|
||||
} else if (structured.kind === "unsupported") {
|
||||
// Keep invalid structured text untouched — the SQL formatter would
|
||||
// silently corrupt XML-looking content.
|
||||
const esRequest = detectAndFormatElasticsearchRequests(source, props.databaseType, settingsStore.editorSettings.sqlFormatter.tabWidth);
|
||||
if (esRequest.kind === "elasticsearch") {
|
||||
formatted = esRequest.formatted;
|
||||
} else if (esRequest.kind === "unsupported") {
|
||||
toast(t("toolbar.formatAutoDetectFailed"), 3000);
|
||||
return;
|
||||
} else {
|
||||
formatted = await formatSqlForEditing(source, props.formatDialect ?? props.dialect ?? "generic", settingsStore.editorSettings.sqlFormatter);
|
||||
const structured = detectAndFormatStructured(source, {
|
||||
indentSize: settingsStore.editorSettings.sqlFormatter.tabWidth,
|
||||
useTabs: settingsStore.editorSettings.sqlFormatter.useTabs,
|
||||
});
|
||||
if (structured.kind === "json" || structured.kind === "xml") {
|
||||
formatted = structured.formatted;
|
||||
} else if (structured.kind === "unsupported") {
|
||||
// Keep invalid structured text untouched — the SQL formatter would
|
||||
// silently corrupt XML-looking content.
|
||||
toast(t("toolbar.formatAutoDetectFailed"), 3000);
|
||||
return;
|
||||
} else {
|
||||
formatted = await formatSqlForEditing(source, props.formatDialect ?? props.dialect ?? "generic", settingsStore.editorSettings.sqlFormatter);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (view.value !== currentView || currentView.state !== originalState || currentView.state.sliceDoc(from, to) !== source) {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { detectAndFormatElasticsearchRequests } from "@/lib/elasticsearch/elasticsearchFormatter";
|
||||
|
||||
describe("elasticsearchFormatter", () => {
|
||||
describe("detectAndFormatElasticsearchRequests", () => {
|
||||
it("pretty-prints a single-line GET request with a JSON body", () => {
|
||||
const source = `GET /master_base/_search {"query":{"bool":{"must":[{"term":{"masterId":"47526596395"}}]}}}`;
|
||||
const result = detectAndFormatElasticsearchRequests(source, "elasticsearch", 2);
|
||||
expect(result).toEqual({
|
||||
kind: "elasticsearch",
|
||||
formatted: `GET /master_base/_search
|
||||
{
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{
|
||||
"term": {
|
||||
"masterId": "47526596395"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}`,
|
||||
});
|
||||
});
|
||||
|
||||
it("leaves a request with no body as just the method/path line", () => {
|
||||
const result = detectAndFormatElasticsearchRequests("GET /_cat/indices", "elasticsearch", 2);
|
||||
expect(result).toEqual({ kind: "elasticsearch", formatted: "GET /_cat/indices" });
|
||||
});
|
||||
|
||||
it("formats multiple requests separated by a blank line, keeping the separator", () => {
|
||||
const source = `GET /a/_search {"query":{"match_all":{}}}\n\nPOST /b/_refresh`;
|
||||
const result = detectAndFormatElasticsearchRequests(source, "easysearch", 2);
|
||||
expect(result).toEqual({
|
||||
kind: "elasticsearch",
|
||||
formatted: `GET /a/_search\n{\n "query": {\n "match_all": {}\n }\n}\n\nPOST /b/_refresh`,
|
||||
});
|
||||
});
|
||||
|
||||
it("reports unsupported (and keeps caller from touching the text) for an invalid JSON body", () => {
|
||||
const result = detectAndFormatElasticsearchRequests(`GET /a/_search {"query":`, "elasticsearch", 2);
|
||||
expect(result).toEqual({ kind: "unsupported" });
|
||||
});
|
||||
|
||||
it("reports not-elasticsearch for bare JSON with no method/path line", () => {
|
||||
const result = detectAndFormatElasticsearchRequests(`{"query":{"match_all":{}}}`, "elasticsearch", 2);
|
||||
expect(result).toEqual({ kind: "not-elasticsearch" });
|
||||
});
|
||||
|
||||
it("reports not-elasticsearch for non-elasticsearch database types", () => {
|
||||
const source = `GET /master_base/_search {"query":{"match_all":{}}}`;
|
||||
expect(detectAndFormatElasticsearchRequests(source, "mysql", 2)).toEqual({ kind: "not-elasticsearch" });
|
||||
expect(detectAndFormatElasticsearchRequests(source, undefined, 2)).toEqual({ kind: "not-elasticsearch" });
|
||||
});
|
||||
|
||||
it("formats meilisearch requests too", () => {
|
||||
const result = detectAndFormatElasticsearchRequests(`POST /indexes/movies/search {"q":"batman"}`, "meilisearch", 2);
|
||||
expect(result).toEqual({
|
||||
kind: "elasticsearch",
|
||||
formatted: `POST /indexes/movies/search\n{\n "q": "batman"\n}`,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import { formatJsonSource } from "@/lib/common/safeJsonFormat";
|
||||
import { elasticsearchRestRequestRanges } from "@/lib/sql/sqlStatementRanges";
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
|
||||
const ELASTICSEARCH_REQUEST_LINE = /^(GET|POST|PUT|PATCH|DELETE|HEAD)\s+(\S+)/i;
|
||||
|
||||
export type ElasticsearchFormatResult = { kind: "elasticsearch"; formatted: string } | { kind: "unsupported" } | { kind: "not-elasticsearch" };
|
||||
|
||||
function formatSingleElasticsearchRequest(requestText: string, indentSize: number): string | null {
|
||||
const match = requestText.match(ELASTICSEARCH_REQUEST_LINE);
|
||||
if (!match) return null;
|
||||
const method = match[1].toUpperCase();
|
||||
const rawPath = match[2];
|
||||
const path = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
|
||||
const body = requestText.slice(match[0].length).trim();
|
||||
if (!body) return `${method} ${path}`;
|
||||
try {
|
||||
return `${method} ${path}\n${formatJsonSource(body, indentSize)}`;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats Kibana-console-style Elasticsearch/Meilisearch REST requests
|
||||
* (`METHOD /path {json body}`, possibly several in one document) by
|
||||
* pretty-printing each request's JSON body and normalizing its method/path
|
||||
* onto its own line. Text between requests (blank lines, comments) is kept
|
||||
* as-is via {@link elasticsearchRestRequestRanges}.
|
||||
*/
|
||||
export function detectAndFormatElasticsearchRequests(text: string, databaseType: DatabaseType | undefined, indentSize: number): ElasticsearchFormatResult {
|
||||
const ranges = elasticsearchRestRequestRanges(text, databaseType);
|
||||
if (ranges.length === 0) return { kind: "not-elasticsearch" };
|
||||
|
||||
let result = "";
|
||||
let cursor = 0;
|
||||
for (const range of ranges) {
|
||||
const formattedOne = formatSingleElasticsearchRequest(range.sql, indentSize);
|
||||
if (formattedOne === null) return { kind: "unsupported" };
|
||||
result += text.slice(cursor, range.from) + formattedOne;
|
||||
cursor = range.to;
|
||||
}
|
||||
result += text.slice(cursor);
|
||||
return { kind: "elasticsearch", formatted: result };
|
||||
}
|
||||
Reference in New Issue
Block a user