BadRequestExceptionμ ν΄λΌμ΄μΈνΈμ μμ²μ΄ μλͺ»λ κ²½μ° μ¬μ©νλ μμΈμ
λλ€. HTTP 400 μν μ½λλ₯Ό λ°ννλ©°, μ ν¨μ± κ²μ¦ μ€ν¨, μλͺ»λ λ§€κ°λ³μ, μμ² νμ μ€λ₯ λ±μ μ¬μ©λ©λλ€.
κΈ°λ³Έ μ¬μ©λ²
class BadRequestException extends SoException {
constructor(message: LocalizedString, payload?: unknown);
}
@api()
async createPost(title: string, content: string) {
if (!title || title.trim().length === 0) {
throw new BadRequestException("μ λͺ©μ νμμ
λλ€");
}
if (content.length > 10000) {
throw new BadRequestException("λ³Έλ¬Έμ 10,000μλ₯Ό μ΄κ³Όν μ μμ΅λλ€");
}
return this.create({ title, content });
}
μ€μ© μμ
μ ν¨μ± κ²μ¦
@api()
async updateUser(
userId: number,
email?: string,
age?: number,
phoneNumber?: string
) {
const errors: Array<{ field: string; message: string }> = [];
// μ΄λ©μΌ κ²μ¦
if (email && !email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
errors.push({
field: "email",
message: "μ ν¨νμ§ μμ μ΄λ©μΌ νμμ
λλ€"
});
}
// λμ΄ κ²μ¦
if (age !== undefined && (age < 0 || age > 150)) {
errors.push({
field: "age",
message: "λμ΄λ 0~150 μ¬μ΄μ¬μΌ ν©λλ€"
});
}
// μ νλ²νΈ κ²μ¦
if (phoneNumber && !phoneNumber.match(/^\d{3}-\d{3,4}-\d{4}$/)) {
errors.push({
field: "phoneNumber",
message: "μ νλ²νΈ νμμ΄ μ¬λ°λ₯΄μ§ μμ΅λλ€ (μ: 010-1234-5678)"
});
}
if (errors.length > 0) {
throw new BadRequestException("μ
λ ₯ λ°μ΄ν°κ° μ ν¨νμ§ μμ΅λλ€", {
errors
});
}
return this.update(userId, { email, age, phoneNumber });
}
λΉμ¦λμ€ κ·μΉ κ²μ¦
@api()
async transferMoney(
ctx: Context,
fromAccountId: number,
toAccountId: number,
amount: number
) {
// κΈμ‘ κ²μ¦
if (amount <= 0) {
throw new BadRequestException(
"μ΄μ²΄ κΈμ‘μ 0λ³΄λ€ μ»€μΌ ν©λλ€",
{ amount }
);
}
if (amount > 10000000) {
throw new BadRequestException(
"1ν μ΄μ²΄ νλλ μ²λ§μμ
λλ€",
{ amount, limit: 10000000 }
);
}
// κ³μ’ κ²μ¦
if (fromAccountId === toAccountId) {
throw new BadRequestException("λμΌν κ³μ’λ‘λ μ΄μ²΄ν μ μμ΅λλ€");
}
const fromAccount = await AccountModel.findById(fromAccountId);
if (fromAccount.userId !== ctx.user!.id) {
throw new BadRequestException("λ³ΈμΈ κ³μ’μμλ§ μ΄μ²΄ν μ μμ΅λλ€");
}
if (fromAccount.balance < amount) {
throw new BadRequestException(
"μμ‘μ΄ λΆμ‘±ν©λλ€",
{
balance: fromAccount.balance,
requested: amount,
shortage: amount - fromAccount.balance
}
);
}
// μ΄μ²΄ μ€ν
return this.executeTransfer(fromAccountId, toAccountId, amount);
}
νμΌ μ λ‘λ κ²μ¦
@upload()
async uploadProfileImage(ctx: Context) {
const { bufferedFiles } = Sonamu.getContext();
const file = bufferedFiles?.[0]; // 첫 λ²μ§Έ νμΌ μ¬μ©
if (!file) {
throw new BadRequestException("νμΌμ΄ νμν©λλ€");
}
// νμΌ ν¬κΈ° κ²μ¦ (5MB)
const maxSize = 5 * 1024 * 1024;
if (file.size > maxSize) {
throw new BadRequestException(
"νμΌ ν¬κΈ°λ 5MBλ₯Ό μ΄κ³Όν μ μμ΅λλ€",
{
size: file.size,
maxSize,
sizeMB: (file.size / 1024 / 1024).toFixed(2),
maxSizeMB: 5
}
);
}
// νμΌ νμ κ²μ¦
const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
if (!allowedTypes.includes(file.mimetype)) {
throw new BadRequestException(
"μ§μνμ§ μλ μ΄λ―Έμ§ νμμ
λλ€",
{
provided: file.mimetype,
allowed: allowedTypes
}
);
}
// μ΄λ―Έμ§ μ μ₯
return Sonamu.storage.save({
file: file.buffer,
filename: file.filename,
bucket: "profile-images"
});
}
λ μ§/μκ° κ²μ¦
@api()
async createReservation(
userId: number,
startDate: Date,
endDate: Date,
guestCount: number
) {
const now = new Date();
// κ³Όκ±° λ μ§ μ²΄ν¬
if (startDate < now) {
throw new BadRequestException(
"μμ½ μμμΌμ νμ¬ μκ° μ΄νμ¬μΌ ν©λλ€",
{ startDate, now }
);
}
// μμμΌ/μ’
λ£μΌ μμ 체ν¬
if (startDate >= endDate) {
throw new BadRequestException(
"μ’
λ£μΌμ μμμΌλ³΄λ€ μ΄νμ¬μΌ ν©λλ€",
{ startDate, endDate }
);
}
// μ΅λ μμ½ κΈ°κ° μ²΄ν¬ (30μΌ)
const maxDays = 30;
const daysDiff = Math.ceil(
(endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)
);
if (daysDiff > maxDays) {
throw new BadRequestException(
`μμ½ κΈ°κ°μ μ΅λ ${maxDays}μΌκΉμ§ κ°λ₯ν©λλ€`,
{ requestedDays: daysDiff, maxDays }
);
}
// μΈμ 체ν¬
if (guestCount < 1 || guestCount > 10) {
throw new BadRequestException(
"μμ½ μΈμμ 1~10λͺ
μ¬μ΄μ¬μΌ ν©λλ€",
{ guestCount }
);
}
return this.create({ userId, startDate, endDate, guestCount });
}
λ°°μ΄/κ°μ²΄ κ²μ¦
@api()
async createOrder(
userId: number,
items: Array<{ productId: number; quantity: number }>
) {
// λΉ μ£Όλ¬Έ 체ν¬
if (!items || items.length === 0) {
throw new BadRequestException("μ΅μ 1κ° μ΄μμ μνμ΄ νμν©λλ€");
}
// μ΅λ μ£Όλ¬Έ μλ 체ν¬
if (items.length > 50) {
throw new BadRequestException(
"ν λ²μ μ΅λ 50κ° μνκΉμ§ μ£Όλ¬Έν μ μμ΅λλ€",
{ itemCount: items.length, maxItems: 50 }
);
}
// κ° νλͺ© κ²μ¦
const errors: Array<{ index: number; message: string }> = [];
items.forEach((item, index) => {
if (!item.productId || item.productId <= 0) {
errors.push({
index,
message: "μ ν¨νμ§ μμ μν IDμ
λλ€"
});
}
if (!item.quantity || item.quantity <= 0) {
errors.push({
index,
message: "μλμ 1κ° μ΄μμ΄μ΄μΌ ν©λλ€"
});
}
if (item.quantity > 999) {
errors.push({
index,
message: "μνλΉ μ΅λ 999κ°κΉμ§ μ£Όλ¬Έν μ μμ΅λλ€"
});
}
});
if (errors.length > 0) {
throw new BadRequestException("μ£Όλ¬Έ νλͺ©μ΄ μ ν¨νμ§ μμ΅λλ€", {
errors
});
}
return this.createOrder(userId, items);
}
Zod κ²μ¦κ³Όμ μ‘°ν©
Sonamuλ API νλΌλ―Έν°λ₯Ό μλμΌλ‘ Zod μ€ν€λ§λ‘ κ²μ¦νμ§λ§, μΆκ° λΉμ¦λμ€ λ‘μ§ κ²μ¦μλBadRequestExceptionμ μ¬μ©ν©λλ€:
@api()
async createProduct(
name: string, // Zodλ‘ μλ κ²μ¦: λ¬Έμμ΄ νμ
price: number, // Zodλ‘ μλ κ²μ¦: μ«μ νμ
categoryId: number // Zodλ‘ μλ κ²μ¦: μ«μ νμ
) {
// μΆκ° λΉμ¦λμ€ λ‘μ§ κ²μ¦
if (price < 0) {
throw new BadRequestException("κ°κ²©μ 0 μ΄μμ΄μ΄μΌ ν©λλ€");
}
const category = await CategoryModel.findById(categoryId);
if (!category) {
throw new BadRequestException(
"μ‘΄μ¬νμ§ μλ μΉ΄ν
κ³ λ¦¬μ
λλ€",
{ categoryId }
);
}
if (!category.isActive) {
throw new BadRequestException(
"λΉνμ±νλ μΉ΄ν
κ³ λ¦¬μλ μνμ μΆκ°ν μ μμ΅λλ€",
{ categoryId, categoryName: category.name }
);
}
return this.create({ name, price, categoryId });
}
payload νμ© ν¨ν΄
νλλ³ μ€λ₯ λͺ©λ‘
throw new BadRequestException("μ
λ ₯ λ°μ΄ν° κ²μ¦ μ€ν¨", {
errors: [
{ field: "email", code: "INVALID_FORMAT" },
{ field: "password", code: "TOO_SHORT", minLength: 8 },
],
});
μ ν μ¬ν μ 보
throw new BadRequestException("νμΌ ν¬κΈ° μ΄κ³Ό", {
size: file.size,
limit: 5242880, // 5MB in bytes
unit: "bytes",
});
μ¬μλ κ°λ₯ μ 보
throw new BadRequestException("μΌμΌ μμ² νλ μ΄κ³Ό", {
limit: 100,
used: 100,
resetsAt: new Date("2024-01-01T00:00:00Z"),
});
ν΄λΌμ΄μΈνΈ μλ΅ μμ
κΈ°λ³Έ μλ΅
{
"statusCode": 400,
"message": "μ λͺ©μ νμμ
λλ€"
}
payload ν¬ν¨ μλ΅
{
"statusCode": 400,
"message": "μ
λ ₯ λ°μ΄ν°κ° μ ν¨νμ§ μμ΅λλ€",
"payload": {
"errors": [
{
"field": "email",
"message": "μ ν¨νμ§ μμ μ΄λ©μΌ νμμ
λλ€"
},
{
"field": "age",
"message": "λμ΄λ 0~150 μ¬μ΄μ¬μΌ ν©λλ€"
}
]
}
}