Escape regex when searching for a specific filepath + some error handling / schema adjustments

This commit is contained in:
bkellam 2024-09-10 12:24:12 -07:00
parent 17bf94fc5f
commit 4e5deb22a1
6 changed files with 33 additions and 8 deletions

View file

@ -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",

View file

@ -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',

View file

@ -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(),
}), }),
}); });

View file

@ -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);
} }

View file

@ -60,3 +60,11 @@ export const fileNotFound = async (fileName: string, repository: string): Promis
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}`,
};
}

View file

@ -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"