mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-12 04:15:30 +00:00
Escape regex when searching for a specific filepath + some error handling / schema adjustments
This commit is contained in:
parent
17bf94fc5f
commit
4e5deb22a1
6 changed files with 33 additions and 8 deletions
|
|
@ -26,6 +26,7 @@
|
||||||
"@uiw/react-codemirror": "^4.23.0",
|
"@uiw/react-codemirror": "^4.23.0",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"escape-string-regexp": "^5.0.0",
|
||||||
"http-status-codes": "^2.3.0",
|
"http-status-codes": "^2.3.0",
|
||||||
"lucide-react": "^0.435.0",
|
"lucide-react": "^0.435.0",
|
||||||
"next": "14.2.6",
|
"next": "14.2.6",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
|
|
||||||
export enum ErrorCode {
|
export enum ErrorCode {
|
||||||
|
UNEXPECTED_ERROR = 'UNEXPECTED_ERROR',
|
||||||
MISSING_REQUIRED_QUERY_PARAMETER = 'MISSING_REQUIRED_QUERY_PARAMETER',
|
MISSING_REQUIRED_QUERY_PARAMETER = 'MISSING_REQUIRED_QUERY_PARAMETER',
|
||||||
REPOSITORY_NOT_FOUND = 'REPOSITORY_NOT_FOUND',
|
REPOSITORY_NOT_FOUND = 'REPOSITORY_NOT_FOUND',
|
||||||
FILE_NOT_FOUND = 'FILE_NOT_FOUND',
|
FILE_NOT_FOUND = 'FILE_NOT_FOUND',
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@ export type SearchRequest = z.infer<typeof searchRequestSchema>;
|
||||||
export const searchRequestSchema = z.object({
|
export const searchRequestSchema = z.object({
|
||||||
query: z.string(),
|
query: z.string(),
|
||||||
numResults: z.number(),
|
numResults: z.number(),
|
||||||
whole: z.optional(z.boolean()),
|
whole: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
export type SearchResponse = z.infer<typeof searchResponseSchema>;
|
export type SearchResponse = z.infer<typeof searchResponseSchema>;
|
||||||
export type SearchResult = SearchResponse["Result"];
|
export type SearchResult = SearchResponse["Result"];
|
||||||
export type SearchResultFile = SearchResult["Files"][number];
|
export type SearchResultFile = NonNullable<SearchResult["Files"]>[number];
|
||||||
export type SearchResultFileMatch = SearchResultFile["ChunkMatches"][number];
|
export type SearchResultFileMatch = SearchResultFile["ChunkMatches"][number];
|
||||||
export type SearchResultRange = z.infer<typeof rangeSchema>;
|
export type SearchResultRange = z.infer<typeof rangeSchema>;
|
||||||
export type SearchResultLocation = z.infer<typeof locationSchema>;
|
export type SearchResultLocation = z.infer<typeof locationSchema>;
|
||||||
|
|
@ -50,8 +50,8 @@ export const searchResponseSchema = z.object({
|
||||||
Checksum: z.string(),
|
Checksum: z.string(),
|
||||||
Score: z.number(),
|
Score: z.number(),
|
||||||
// Set if `whole` is true.
|
// Set if `whole` is true.
|
||||||
Content: z.optional(z.string()),
|
Content: z.string().optional(),
|
||||||
})),
|
})).nullable(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { SHARD_MAX_MATCH_COUNT, TOTAL_MAX_MATCH_COUNT } from "../environment";
|
import { SHARD_MAX_MATCH_COUNT, TOTAL_MAX_MATCH_COUNT } from "../environment";
|
||||||
import { FileSourceRequest, FileSourceResponse, SearchRequest, SearchResponse, searchResponseSchema } from "../schemas";
|
import { FileSourceRequest, FileSourceResponse, SearchRequest, SearchResponse, searchResponseSchema } from "../schemas";
|
||||||
import { fileNotFound, invalidZoektResponse, ServiceError } from "../serviceError";
|
import { fileNotFound, invalidZoektResponse, schemaValidationError, ServiceError, unexpectedError } from "../serviceError";
|
||||||
import { isServiceError } from "../utils";
|
import { isServiceError } from "../utils";
|
||||||
import { zoektFetch } from "./zoektClient";
|
import { zoektFetch } from "./zoektClient";
|
||||||
|
import escapeStringRegexp from "escape-string-regexp";
|
||||||
|
|
||||||
export const search = async ({ query, numResults, whole }: SearchRequest): Promise<SearchResponse | ServiceError> => {
|
export const search = async ({ query, numResults, whole }: SearchRequest): Promise<SearchResponse | ServiceError> => {
|
||||||
const body = JSON.stringify({
|
const body = JSON.stringify({
|
||||||
|
|
@ -29,12 +30,21 @@ export const search = async ({ query, numResults, whole }: SearchRequest): Promi
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchBody = await searchResponse.json();
|
const searchBody = await searchResponse.json();
|
||||||
return searchResponseSchema.parse(searchBody);
|
const parsedSearchResponse = searchResponseSchema.safeParse(searchBody);
|
||||||
|
if (!parsedSearchResponse.success) {
|
||||||
|
console.error(`Failed to parse zoekt response. Error: ${parsedSearchResponse.error}`);
|
||||||
|
return unexpectedError(`Something went wrong while parsing the response from zoekt`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedSearchResponse.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFileSource = async ({ fileName, repository }: FileSourceRequest): Promise<FileSourceResponse | ServiceError> => {
|
export const getFileSource = async ({ fileName, repository }: FileSourceRequest): Promise<FileSourceResponse | ServiceError> => {
|
||||||
|
const escapedFileName = escapeStringRegexp(fileName);
|
||||||
|
const escapedRepository = escapeStringRegexp(repository);
|
||||||
|
|
||||||
const searchResponse = await search({
|
const searchResponse = await search({
|
||||||
query: `${fileName} repo:${repository}`,
|
query: `${escapedFileName} repo:^${escapedRepository}$`,
|
||||||
numResults: 1,
|
numResults: 1,
|
||||||
whole: true,
|
whole: true,
|
||||||
});
|
});
|
||||||
|
|
@ -45,7 +55,7 @@ export const getFileSource = async ({ fileName, repository }: FileSourceRequest)
|
||||||
|
|
||||||
const files = searchResponse.Result.Files;
|
const files = searchResponse.Result.Files;
|
||||||
|
|
||||||
if (files.length === 0) {
|
if (!files || files.length === 0) {
|
||||||
return fileNotFound(fileName, repository);
|
return fileNotFound(fileName, repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,4 +59,12 @@ export const fileNotFound = async (fileName: string, repository: string): Promis
|
||||||
errorCode: ErrorCode.FILE_NOT_FOUND,
|
errorCode: ErrorCode.FILE_NOT_FOUND,
|
||||||
message: `File "${fileName}" not found in repository "${repository}"`,
|
message: `File "${fileName}" not found in repository "${repository}"`,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const unexpectedError = (message: string): ServiceError => {
|
||||||
|
return {
|
||||||
|
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
errorCode: ErrorCode.UNEXPECTED_ERROR,
|
||||||
|
message: `Unexpected error: ${message}`,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -1607,6 +1607,11 @@ escape-string-regexp@^4.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
|
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
|
||||||
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
||||||
|
|
||||||
|
escape-string-regexp@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
|
||||||
|
integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
|
||||||
|
|
||||||
eslint-config-next@14.2.6:
|
eslint-config-next@14.2.6:
|
||||||
version "14.2.6"
|
version "14.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-14.2.6.tgz#43623683155540feff830b08956a57ece8148d8f"
|
resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-14.2.6.tgz#43623683155540feff830b08956a57ece8148d8f"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue