12 examples

Unhandled exception

Exception occurs without proper handling.

[ FAQ1 ]

What is an unhandled exception?

An unhandled exception occurs when a program encounters an error or unexpected condition without having any code to explicitly handle it. Such exceptions propagate through the application, eventually reaching the runtime environment and causing the application to crash or terminate abruptly. Common examples include null pointer exceptions, file access errors, or network failures that aren’t wrapped in appropriate error-handling structures. Unhandled exceptions negatively impact user experience and reliability, often resulting in data loss or unstable application behavior.
[ FAQ2 ]

How to catch unhandled exceptions

To catch unhandled exceptions, use structured error-handling mechanisms such as try-catch blocks or equivalent constructs provided by the programming language. Implement a global error handler or exception handler that can intercept unexpected exceptions, log useful diagnostic information, and gracefully recover or terminate the application. Ensure critical sections and operations prone to errors are protected by localized exception handling, allowing your application to manage errors proactively. Regularly review logs and analytics to identify patterns of unhandled exceptions and continuously refine error-handling strategies for improved reliability.
diff block
import { Command } from 'commander';
+import { existsSync } from 'fs';
+import { mkdir, writeFile } from 'fs/promises';
+import prompts from 'prompts';
+import { execSync } from 'child_process';
+import path from 'path';
+
+const DEV_DEPENDENCIES = ['tailwindcss', '@tailwindcss/postcss'];
+const DEPENDENCIES = [
+ '@phosphor-icons/react',
+ '@radix-ui/react-checkbox',
+ '@radix-ui/react-icons',
+ '@radix-ui/themes',
+ 'phosphor-react',
+ 'radix-ui',
+];
export const init = new Command()
.command('init')
.description('Initialize a new project')
- .action(() => {
- console.log('Initializing the project');
+ .action(async () => {
+ const { root } = await prompts([
+ {
+ type: 'text',
+ name: 'root',
+ message: 'Where is the root of your project?',
+ initial: './',
+ },
+ ]);
+ if (!existsSync(root)) {
+ console.error('The root directory does not exist.');
+ return;
+ }
+
+ const { installDeps } = await prompts({
+ name: 'installDeps',
+ type: 'confirm',
+ message: 'Do you want to install dependencies?',
+ initial: true,
+ });
+ if (!installDeps) {
+ console.error('Skipping dependencies. You will not be able to use the design system without additional setup.');
+ } else {
+ const { packageManager } = await prompts({
+ type: 'select',
+ name: 'packageManager',
+ message: 'Which package manager do you want to use?',
+ choices: [
+ // the value is the command to run to install a package
+ { title: 'npm', value: 'npm install' },
+ { title: 'yarn', value: 'yarn add' },
+ { title: 'pnpm', value: 'pnpm add' },
+ { title: 'bun', value: 'bun add' },
+ ],
+ });
+
+ console.log('installing dependencies...');
+ execSync(`${packageManager} -D ${DEV_DEPENDENCIES.join(' ')}`);
+ execSync(`${packageManager} ${DEPENDENCIES.join(' ')}`);
+ }
+
+ const { setupTailwind } = await prompts({
+ name: 'setupTailwind',
+ type: 'confirm',
+ message: 'Do you want to set up Tailwind?',
+ initial: true,
+ });
+
+ if (setupTailwind) {
+ console.log('Setting up Tailwind v4 for Next.js...');
+ await writeFile(
+ path.join(root, 'postcss.config.mjs'),
+ `
+/** @type {import('postcss-load-config').Config} */
+const config = {
+ plugins: {
+ '@tailwindcss/postcss': {},
+ },
+};
+
+export default config;
+`.trim(),
+ 'utf8'
+ );
+ }
+
+ const { setupStyles } = await prompts({
+ name: 'setupStyles',
+ type: 'confirm',
+ message: 'Do you want to set up the Bits of Good theme?',
+ initial: true,
+ });
+
+ if (!setupStyles) {
+ console.error('Skipping the Bits of Good theme setup. Your project may not look like the Design System Website.');
+ if (setupTailwind) {
+ console.error(
+ 'You will need to finish the Tailwind setup manually. Create a css file with `@import "tailwindcss"` in it, and make sure you import it into your src/app/layout.tsx or src/pages/_app.tsx.'
+ );
+ }
+ } else {
+ const { stylePath } = await prompts({
+ name: 'stylePath',
+ type: 'text',
+ message: 'Where should the BOG theme and tailwindcss stylesheet live?',
+ initial: 'src/styles/globals.css',
+ });
+
+ const response = await fetch(
+ 'https://raw.githubusercontent.com/GTBitsOfGood/design-system/refs/heads/main/src/styles/globals.css'
+ );
+ const styles = await response.text();
Greptile
greptile
style: Missing error handling for the fetch operation. If the network request fails or the GitHub URL is unavailable, this will throw an unhandled exception. ```suggestion + try { const response = await fetch( 'https://raw.githubusercontent.com/GTBitsOfGood/design-system/refs/heads/main/src/styles/globals.css' ); + if (!response.ok) { + throw new Error(`Failed to fetch styles: ${response.status} ${response.statusText}`); } const styles = await response.text(); + } catch (error) { + console.error('Failed to download the BOG theme:', error.message); + console.error('You may need to manually create the stylesheet.'); return; } ```
diff block
+import os from "node:os";
+import { Cache, environment } from "@raycast/api";
+import { CacheEntry } from "./types";
+
+// The HNRSS service caches responses for 5 minutes: https://github.com/hnrss/hnrss/issues/71
+const CACHE_DURATION_IN_MS = 5 * 60 * 1_000;
+
+const cache = new Cache();
+const key = "hnrss-newest";
+
+export async function getStories(): Promise<CacheEntry["items"]> {
+ const cachedResponse = cache.get(key);
+ if (cachedResponse) {
+ const parsed: CacheEntry = JSON.parse(cachedResponse);
+ const elapsed = Date.now() - parsed.timestamp;
+
+ if (elapsed <= CACHE_DURATION_IN_MS) {
+ return parsed.items;
+ }
+ }
+
+ const response = await fetch(`https://hnrss.org/newest.jsonfeed?points=500`, {
+ headers: {
+ "User-Agent": `Hacker News Extension, Raycast/${environment.raycastVersion} (${os.type()} ${os.release()})`,
+ },
+ });
Greptile
greptile
logic: API call does not check if response.ok. Consider checking response.ok and handling error responses to avoid unhandled exceptions. ```suggestion const response = await fetch(`https://hnrss.org/newest.jsonfeed?points=500`, { headers: { "User-Agent": `Hacker News Extension, Raycast/${environment.raycastVersion} (${os.type()} ${os.release()})`, }, }); + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); } ```
diff block
)
def handle(self, *args, **options):
- if options["org_ids"]:
+ if options.get("org_ids"):
org_ids_input = options["org_ids"]
- # Parse comma-separated list of organization IDs
- org_ids = [int(org_id.strip()) for org_id in org_ids_input.split(",")]
- else: # backfill option
+ org_ids = [org_id.strip() for org_id in org_ids_input.split(",")]
+
+ elif options.get("rollout_date"):
self.stdout.write("Finding organizations that need RBAC migration...")
- org_ids = self.find_organizations_needing_migration()
+
+ # Parse rollout date
+ rollout_date_str = options.get("rollout_date")
+ rollout_date = datetime.strptime(rollout_date_str, "%Y-%m-%d").date()
Greptile
greptile
logic: Missing try/catch for date parsing - invalid date format will cause unhandled exception
suggested fix
rollout_date_str = options.get("rollout_date")
+ try:
rollout_date = datetime.strptime(rollout_date_str, "%Y-%m-%d").date()
+ except ValueError:
+ self.stdout.write(self.style.ERROR(f"Invalid date format. Please use YYYY-MM-DD format."))
+ return
diff block
+import axios, { AxiosResponse } from "axios";
+import { getPreferenceValues } from "@raycast/api";
+
+const preferences = getPreferenceValues();
+const omdbUrl = "http://www.omdbapi.com/";
+const watchModeBaseUrl = "https://api.watchmode.com/v1/";
+const OMDB_TOKEN = preferences.omdbToken;
+const WATCHMODE_API_KEY = preferences.watchModeApiKey;
+const region = preferences.region;
+
+export async function searchTitles(query: string, type: string) {
+ try {
+ const params = {
+ s: query,
+ apikey: OMDB_TOKEN,
+ type: type === "all" ? "" : type,
+ };
+ const response: AxiosResponse = await axios.get(omdbUrl, { params });
+
+ if (response.data.Error) {
+ throw new Error(response.data.Error);
+ }
+
+ return response.data.Search;
+ } catch (error) {
+ console.error("Error searching movies and shows:", error);
+ return [];
+ }
+}
+export async function searchSeries(id: string, season?: number) {
+ try {
+ const params = {
+ i: id,
+ apikey: OMDB_TOKEN,
+ Season: season || "",
+ };
+ const response: AxiosResponse = await axios.get(omdbUrl, { params });
+
+ return response.data;
+ } catch (error) {
+ console.error("Error searching series information:", error);
+ return null;
+ }
+}
+export async function searchID(id: string) {
+ try {
+ const params = {
+ i: id,
+ apikey: OMDB_TOKEN,
+ plot: "full",
+ };
+ const response: AxiosResponse = await axios.get(omdbUrl, { params });
+
+ return response.data;
+ } catch (error) {
+ console.error("Error searching movie or show:", error);
+ return null;
+ }
+}
+
+export async function getProviders(id: string) {
+ try {
+ const params = {
+ apiKey: WATCHMODE_API_KEY,
+ append_to_response: "sources",
+ regions: region,
+ };
+ const response: AxiosResponse = await axios.get(watchModeBaseUrl + `title/${id}/details`, { params });
+
+ return response.data.sources;
+ } catch (error) {
+ console.error("Error searching movie or show:", error);
+ return null;
+ }
+}
+
+export async function getSourceIcons() {
+ const response: AxiosResponse = await axios.get(`https://api.watchmode.com/v1/sources/?apiKey=${WATCHMODE_API_KEY}`);
+ const data = response.data;
+ return data;
+}
Greptile
greptile
logic: getSourceIcons lacks try-catch error handling and could throw unhandled exceptions
diff block
+import { MastraAuthProvider } from '@mastra/core/server';
+
+import admin from 'firebase-admin';
+import { getFirestore } from 'firebase-admin/firestore';
+
+interface MastraAuthFirebaseOptions {
+ databaseId?: string;
+ serviceAccount?: string;
+}
+
+type FirebaseUser = admin.auth.DecodedIdToken;
+
+export class MastraAuthFirebase extends MastraAuthProvider<FirebaseUser> {
+ private serviceAccount: string | undefined;
+ private databaseId: string | undefined;
+
+ constructor(options?: MastraAuthFirebaseOptions) {
+ super({ name: 'firebase' });
+
+ this.serviceAccount = options?.serviceAccount ?? process.env.FIREBASE_SERVICE_ACCOUNT;
+ this.databaseId = options?.databaseId ?? process.env.FIRESTORE_DATABASE_ID ?? process.env.FIREBASE_DATABASE_ID;
+
+ if (!admin.apps.length) {
+ admin.initializeApp({
+ credential: this.serviceAccount
+ ? admin.credential.cert(this.serviceAccount)
+ : admin.credential.applicationDefault(),
+ });
+ }
+ }
+
+ async authenticateToken(token: string): Promise<FirebaseUser | null> {
+ const decoded = await admin.auth().verifyIdToken(token);
+ return decoded;
+ }
Greptile
greptile
style: Missing try/catch around token verification - could throw unhandled exceptions
suggested fix
async authenticateToken(token: string): Promise<FirebaseUser | null> {
+ try {
const decoded = await admin.auth().verifyIdToken(token);
return decoded;
+ } catch (error) {
+ return null;
}
}
diff block
+import { BrowserExtension, environment, getPreferenceValues } from "@raycast/api";
+import { YoutubeTranscript } from "youtube-transcript";
+import fetch from "cross-fetch";
+import * as fs from "node:fs";
+import { showFailureToast } from "@raycast/utils";
+
+global.fetch = fetch;
+
+export function canAccessBrowserExtension() {
+ return environment.canAccess(BrowserExtension);
+}
+
+const DEFAULT_PROMPT = `Summarize the text below and give me a list of bullet points with key insights and the most important facts.
+
+{{content}}`;
+
+// https://i.stack.imgur.com/g2X8z.gif
+const ASCII_TABLES = Object.entries({
+ "&amp;": "&",
+ "&#32;": " ",
+ "&#33;": "!",
+ "&#34;": '"',
+ "&#35;": "#",
+ "&#36;": "$",
+ "&#37;": "%",
+ "&#38;": "&",
+ "&#39;": "'",
+ "&#40;": "(",
+ "&#41;": ")",
+ "&#42;": "*",
+ "&#43;": "+",
+ "&#44;": ",",
+ "&#45;": "-",
+ "&#46;": ".",
+ "&#47;": "/",
+ "&#91;": "[",
+ "&#92;": "\\",
+ "&#93;": "]",
+ "&#94;": "^",
+ "&#95;": "_",
+ "&#96;": "`",
+ "&#123;": "{",
+ "&#124;": "|",
+ "&#125;": "}",
+ "&#126;": "~",
+});
+
+export async function getBrowserContent() {
+ if (!canAccessBrowserExtension()) {
+ return null;
+ }
+ const promptConfig = getPreferenceValues<{
+ promptTemplate?: string;
+ promptTemplate2?: string;
+ }>();
+
+ // console.debug("promptConfig: ", promptConfig)
+ const promptTemplate = readFile(promptConfig.promptTemplate);
+ const promptTemplate2 = readFile(promptConfig.promptTemplate2);
+
+ let prompt: string;
+
+ const tabs = await BrowserExtension.getTabs();
+ const activeTab = (tabs.filter((tab) => tab.active) || [])[0];
+
+ // todo: add setting to enable/disable this feature
+ if (activeTab && activeTab.url.startsWith("https://www.youtube.com/watch?v=")) {
+ // not official API, so it may break in the future
+ const content = await YoutubeTranscript.fetchTranscript(activeTab.url, {
+ lang: "en",
+ }).then((transcript) => {
+ return transcript.map((item) => item.text).join("\n");
+ });
+ prompt = promptTemplate2 || DEFAULT_PROMPT;
+ prompt = prompt.replaceAll(/\{\{\s?content.*?}}/g, content);
+ } else {
+ prompt = await dynamicExecution(promptTemplate || DEFAULT_PROMPT);
+ }
+
+ // console.debug("prompt: ", prompt);
+ const entries = Object.entries(activeTab || []);
+ return replace(prompt, entries);
+}
+
+const regex =
+ /\{\{\s*content\s*(format="(?<format>markdown|text|html)")?\s*(cssSelector="(?<cssSelector>.+)")?\s*(tabId=(?<tabId>\d+))?\s*}}/gm;
+
+/**
+ * dynamic execution by the tag
+ * e.g.
+ * {{ content }} default to Markdown format
+ * {{ content format="markdown" }}
+ * {{ content format="html" }}
+ * {{ content format="text" cssSelector="h1" }}
+ * {{ content tabId=1 }}
+ *
+ * @param prompt
+ */
+async function dynamicExecution(prompt: string) {
+ let result = prompt;
+ const errors: string[] = [];
+ for (const m of prompt.matchAll(regex)) {
+ if (m) {
+ const groups = m.groups;
+ if (groups) {
+ const { format, cssSelector, tabId } = groups;
+ try {
+ const content = await BrowserExtension.getContent({
+ format: format ? (format as "markdown" | "text" | "html") : "markdown",
+ cssSelector: cssSelector ? cssSelector : undefined,
+ tabId: tabId ? parseInt(tabId) : undefined,
+ });
+ result = result.replace(m[0], content);
+ } catch (error) {
+ errors.push(`Tag "${m[0]}" execution failed: ${error}`);
+ }
+ }
+ }
+ }
+ if (errors.length > 0) {
+ await showFailureToast(errors.join("\n"), { title: "Dynamic execution failed" });
+ }
+ return result;
+}
+
+function replace(prompt: string, entries: [string, string][]): string {
+ prompt = prompt.replace("\\n", "\n");
+
+ let result = entries.reduce((acc, [key, value]) => {
+ const r = new RegExp(`{{\\s*${key}}}\\s*`, "g");
+ return acc.replaceAll(r, value);
+ }, prompt);
+
+ for (let i = 0; i < 2; i++) {
+ ASCII_TABLES.forEach(([key, value]) => {
+ result = result.replaceAll(key, value);
+ });
+ }
+ return result;
+}
+
+function readFile(path?: string) {
+ if (!path) {
+ return "";
+ }
+ return fs.readFileSync(path, "utf-8");
+}
Greptile
greptile
logic: readFile() needs try-catch block to handle file system errors gracefully. Currently could throw unhandled exceptions.
suggested fix
function readFile(path?: string) {
if (!path) {
return "";
}
try {
return fs.readFileSync(path, "utf-8");
} catch (error) {
+ showFailureToast("Failed to read file", { title: error instanceof Error ? error.message : String(error) });
return "";
}
}
diff block
+import { useState, useEffect } from "react";
+import { List, Icon, showToast, Toast } from "@raycast/api";
+import { showFailureToast } from "@raycast/utils";
+import { CacheManager } from "./utils/CacheManager";
+import { initializeQuickLink } from "./utils/QuickLinks";
+
+export default function Command() {
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState<string | null>(null);
+
+ useEffect(() => {
+ loadLastUsedProject();
+ }, []);
+
+ async function loadLastUsedProject() {
+ const recentlyUsed = CacheManager.getRecentlyUsedProjects();
+
+ if (recentlyUsed.length > 0) {
+ const lastProjectId = recentlyUsed[0];
+
+ initializeQuickLink(lastProjectId);
Greptile
greptile
logic: initializeQuickLink is called but not wrapped in try-catch, which could cause unhandled exceptions if there are issues with the quick link initialization
suggested fix
+ try {
+ await initializeQuickLink(lastProjectId);
+ } catch (error) {
+ showFailureToast(error, { title: "Could not initialize quick link" });
+ }
diff block
+import { InjectRepository } from '@nestjs/typeorm';
+
+import chalk from 'chalk';
+import { Command } from 'nest-commander';
+import { FieldMetadataType } from 'twenty-shared';
+import { In, Repository } from 'typeorm';
+
+import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
+
+import {
+ ActiveOrSuspendedWorkspacesMigrationCommandRunner,
+ RunOnWorkspaceArgs,
+} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
+import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
+import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
+import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
+import {
+ RelationDirection,
+ deduceRelationDirection,
+} from 'src/engine/utils/deduce-relation-direction.util';
+import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
+
+@Command({
+ name: 'upgrade:0-44:migrate-relations-to-field-metadata',
+ description: 'Migrate relations to field metadata',
+})
+export class MigrateRelationsToFieldMetadataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
+ constructor(
+ @InjectRepository(Workspace, 'core')
+ protected readonly workspaceRepository: Repository<Workspace>,
+ @InjectRepository(FieldMetadataEntity, 'metadata')
+ private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
+ protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
+ ) {
+ super(workspaceRepository, twentyORMGlobalManager);
+ }
+
+ override async runOnWorkspace({
+ index,
+ total,
+ workspaceId,
+ }: RunOnWorkspaceArgs): Promise<void> {
+ this.logger.log(
+ `Running command for workspace ${workspaceId} ${index + 1}/${total}`,
+ );
+
+ const fieldMetadataCollection = await this.fieldMetadataRepository.find({
+ where: {
+ workspaceId,
+ type: In([FieldMetadataType.RELATION, FieldMetadataType.UUID]),
+ },
+ relations: ['fromRelationMetadata', 'toRelationMetadata'],
+ });
+
+ if (!fieldMetadataCollection.length) {
+ this.logger.log(
+ chalk.yellow(
+ `No relation field metadata found for workspace ${workspaceId}.`,
+ ),
+ );
+
+ return;
+ }
+
+ const joinColumnFieldMetadataCollection = fieldMetadataCollection.filter(
+ (fieldMetadata) =>
+ isFieldMetadataOfType(fieldMetadata, FieldMetadataType.UUID),
+ // TODO: Fix this, it's working in other places but not here
+ ) as FieldMetadataEntity<FieldMetadataType.UUID>[];
+
+ const fieldMetadataToUpdateCollection = fieldMetadataCollection
+ .filter((fieldMetadata) =>
+ isFieldMetadataOfType(fieldMetadata, FieldMetadataType.RELATION),
+ )
+ .map((fieldMetadata) =>
+ this.updateRelationFieldMetadata(
+ joinColumnFieldMetadataCollection,
+ // TODO: Fix this, it's working in other places but not here
+ fieldMetadata as FieldMetadataEntity<FieldMetadataType.RELATION>,
+ ),
+ );
+
+ if (fieldMetadataToUpdateCollection.length > 0) {
+ await this.fieldMetadataRepository.save(fieldMetadataToUpdateCollection);
+ }
Greptile
greptile
style: No error handling for the save operation. If the save fails, the command will throw an unhandled exception.
diff block
+/**
+ * Send message command that allows users to send messages using saved templates.
+ * Supports editing, deleting templates and sending messages to threads.
+ */
+import React, { useEffect, useState } from "react";
+import { Action, ActionPanel, Form, List, Toast, useNavigation } from "@raycast/api";
+import { getAccessToken, withAccessToken } from "@raycast/utils";
+import { WebClient } from "@slack/web-api";
+import { SlackTemplate } from "./types";
+import { showCustomToast, slack, sendMessage, validateAndNormalizeThreadTs } from "./lib/slack";
+import { loadTemplates, updateTemplate, deleteTemplate } from "./lib/templates";
+import { useChannels, ChannelDropdown, ThreadField } from "./components/shared";
+
+/**
+ * Form component for editing existing templates
+ * Allows users to modify template name, message, channel, and thread settings
+ */
+function EditTemplateForm({ template, onUpdate }: { template: SlackTemplate; onUpdate: () => void }) {
+ const { channels, isLoading } = useChannels();
+ const { pop } = useNavigation();
+
+ return (
+ <Form
+ isLoading={isLoading}
+ actions={
+ <ActionPanel>
+ <Action.SubmitForm
+ title="Update"
+ onSubmit={async (values: {
+ name: string;
+ message: string;
+ slackChannelId: string;
+ threadTimestamp?: string;
+ }) => {
+ // Validate channel selection
+ const selectedChannel = channels.find((c) => c.id === values.slackChannelId);
+ if (!selectedChannel) {
+ throw new Error("Selected channel not found");
Greptile
greptile
logic: Throwing an error here will cause an unhandled exception. Consider using `showCustomToast` instead and returning early.
diff block
+import { getPreferenceValues } from "@raycast/api";
+import fetch from "node-fetch";
+import { BASE_URL } from "../utils/constants";
+
+export default async function () {
+ const preferences = getPreferenceValues<{
+ tessieApiKey: string;
+ VIN: string;
+ }>();
+
+ const API_KEY = preferences.tessieApiKey;
+ const VIN = preferences.VIN;
+
+ const response = await fetch(`${BASE_URL}/${VIN}/state`, {
+ headers: {
+ Authorization: `Bearer ${API_KEY}`,
+ },
+ });
Greptile
greptile
logic: Missing error handling for the fetch request. If the API is unavailable or returns an error status, this will throw an unhandled exception.
diff block
+import { Jimp } from "jimp";
+import pixelmatch from "pixelmatch";
+
+export const compare = async (
+ actual: string,
+ expected: string,
+): Promise<{ diffBuffer: Buffer; width: number; height: number }> => {
+ const actualImage = await Jimp.read(actual);
+ const expectedImage = await Jimp.read(expected);
Greptile
greptile
logic: Missing error handling for image loading failures. If either image can't be read, this will throw an unhandled exception.
diff block
+import {
+ BadRequestException,
+ Controller,
+ Get,
+ Inject,
+ OnModuleInit,
+ Param,
+} from '@nestjs/common';
+import { ClientGrpc } from '@nestjs/microservices';
+import {
+ ApiBadRequestResponse,
+ ApiBearerAuth,
+ ApiNotFoundResponse,
+ ApiOkResponse,
+ ApiOperation,
+ ApiTags,
+ ApiUnauthorizedResponse,
+} from '@nestjs/swagger';
+import { AuthCommonProto, FileConfigProto } from 'juno-proto';
+import { lastValueFrom } from 'rxjs';
+import { ApiKey } from 'src/decorators/api_key.decorator';
+import { FileConfigResponse } from 'src/models/file_config.dto';
+
+const { FILE_SERVICE_CONFIG_DB_SERVICE_NAME } = FileConfigProto;
+
+@ApiBearerAuth('api_key')
+@ApiTags('file_config')
+@Controller('file')
+export class FileConfigController implements OnModuleInit {
+ private fileConfigDBService: FileConfigProto.FileServiceConfigDbServiceClient;
+
+ constructor(
+ @Inject(FILE_SERVICE_CONFIG_DB_SERVICE_NAME)
+ private fileConfigClient: ClientGrpc,
+ ) {}
+
+ onModuleInit() {
+ this.fileConfigDBService =
+ this.fileConfigClient.getService<FileConfigProto.FileServiceConfigDbServiceClient>(
+ FILE_SERVICE_CONFIG_DB_SERVICE_NAME,
+ );
+ }
+
+ @Get('config/:projectId')
+ @ApiOperation({ summary: 'Get file configuration by project ID' })
+ @ApiBadRequestResponse({
+ description: 'Parameters are invalid',
+ })
+ @ApiNotFoundResponse({
+ description: 'No file config with specified project ID was found',
+ })
+ @ApiUnauthorizedResponse({
+ description: 'Invalid API key provided',
+ })
+ @ApiOkResponse({
+ description:
+ 'Returned the file config associated with the specified project ID',
+ type: FileConfigResponse,
+ })
+ async getFileConfigByProjectId(
+ @ApiKey() apiKey: AuthCommonProto.ApiKey,
+ @Param('projectId') projectId: string,
+ ): Promise<FileConfigResponse> {
+ const id = parseInt(projectId);
+ if (Number.isNaN(id)) {
+ throw new BadRequestException('Id must be a number');
+ }
+
+ const config = this.fileConfigDBService.getConfig({
+ id: id,
+ environment: apiKey.environment,
+ });
Greptile
greptile
logic: Missing try/catch block around gRPC call could lead to unhandled exceptions
suggested fix
+ try {
const config = this.fileConfigDBService.getConfig({
id: id,
environment: apiKey.environment,
});
+ return new FileConfigResponse(await lastValueFrom(config));
+ } catch (error) {
+ throw new BadRequestException('Failed to fetch file configuration');
}