fix(elasticsearch): format Kibana-style REST requests
This commit is contained in:
1 parent
f5a3652f6a
commit
185ff68d78
3 files changed
+130
-10
No files matched your search
@@ -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