diff --git a/apps/example/src/errors/cargoErrorHandler.ts b/apps/example/src/errors/cargoErrorHandler.ts index ec142ea7..97e6498c 100644 --- a/apps/example/src/errors/cargoErrorHandler.ts +++ b/apps/example/src/errors/cargoErrorHandler.ts @@ -4,7 +4,7 @@ import { Request, Response, NextFunction } from 'express' setCargoErrorHandler((err: CargoValidationError, req: Request, res: Response, next: NextFunction) => { res.status(422).json({ code: 'VALIDATION_ERROR', - message: '입력값이 올바르지 않습니다.', + message: 'The provided input is invalid.', details: err.errors.map(e => ({ name: e.name, message: e.message, diff --git a/packages/express-cargo/src/enum.ts b/packages/express-cargo/src/enum.ts index b13585a0..a600f71a 100644 --- a/packages/express-cargo/src/enum.ts +++ b/packages/express-cargo/src/enum.ts @@ -31,11 +31,11 @@ export function Enum(enumObj: any, message?: cargoErrorMessage): TypedPropert const enumValues = enumKeys.map(k => enumObj[k as keyof typeof enumObj]) const validInputs = [...enumKeys, ...enumValues] - // 1. enum 타입 정보 저장 + // 1. Store the enum type information fieldMeta.setEnumType(enumObj) fieldMeta.pushAppliedDecorator({ name: Enum.name, category: 'type-helper', args: [enumObj, message] }) - // 2. enum validator 추가 + // 2. Add the enum validator fieldMeta.addValidator( new ValidatorRule( propertyKey, @@ -45,21 +45,21 @@ export function Enum(enumObj: any, message?: cargoErrorMessage): TypedPropert ), ) - // 3. enum transformer 추가 + // 3. Add the enum transformer const transformer = (value: any): any => { if (value === null || value === undefined) return value const enumKeys = Object.keys(enumObj).filter(k => isNaN(Number(k))) - // 1. 입력값이 enum 키('ADMIN') 인 경우 + // 1. When the input is an enum key (e.g. 'ADMIN') if (typeof value === 'string' && enumKeys.includes(value)) { return enumObj[value as keyof typeof enumObj] } - // 비교를 위해 숫자형 문자열을 숫자로 변환 + // Convert a numeric string to a number for comparison const comparableValue = typeof value === 'string' && !isNaN(Number(value)) ? Number(value) : value - // 2. 입력값이 enum 값(예: 0 또는 'admin')인 경우 + // 2. When the input is an enum value (e.g. 0 or 'admin') for (const key of enumKeys) { if (enumObj[key as keyof typeof enumObj] === comparableValue) { return comparableValue @@ -70,7 +70,7 @@ export function Enum(enumObj: any, message?: cargoErrorMessage): TypedPropert } fieldMeta.setTransformer(transformer) - // 메타데이터 저장 + // Store the metadata classMeta.setFieldMetadata(propertyKey, fieldMeta) } } diff --git a/packages/express-cargo/tests/binding/requestBinding.test.ts b/packages/express-cargo/tests/binding/requestBinding.test.ts index b26e519b..85d8942e 100644 --- a/packages/express-cargo/tests/binding/requestBinding.test.ts +++ b/packages/express-cargo/tests/binding/requestBinding.test.ts @@ -34,7 +34,7 @@ class RequiredRequestDTO { } describe('request binding', () => { - it('request transformer로 값 바인딩', () => { + it('binds values via request transformer', () => { const middleware = bindingCargo(RequestDTO) const req = makeMockReq({ @@ -53,7 +53,7 @@ describe('request binding', () => { expect(dto.optionalField).toBeNull() }) - it('validator 실패 시 CargoValidationError 발생', () => { + it('throws CargoValidationError when a validator fails', () => { const middleware = bindingCargo(RequestDTO) const req = makeMockReq({ @@ -69,7 +69,7 @@ describe('request binding', () => { expect(err.errors).toEqual(expect.arrayContaining([expect.objectContaining({ message: expect.stringContaining('score') })])) }) - it('optional 필드가 없으면 null 처리', () => { + it('sets an optional field to null when it is missing', () => { const middleware = bindingCargo(RequestDTO) const req = makeMockReq({ @@ -84,7 +84,7 @@ describe('request binding', () => { expect(dto.optionalField).toBeNull() }) - it('optional request field가 없으면 validation을 건너뛴다', () => { + it('skips validation when an optional request field is missing', () => { const middleware = bindingCargo(RequestDTO) const req = makeMockReq({ @@ -100,21 +100,21 @@ describe('request binding', () => { expect(dto.optionalScore).toBeNull() }) - it('Source 데코레이터와 @Request 를 함께 쓰면 B2 위반으로 거부된다', () => { + it('rejects combining a source decorator with @Request', () => { expect(() => bindingCargo(MixedBindingDTO)).toThrow(CargoSchemaError) }) - it('bindingCargo 미들웨어 없이 getCargo를 호출하면 에러를 던진다', () => { + it('throws when getCargo is called without the bindingCargo middleware', () => { const req = makeMockReq() expect(() => getCargo(req)).toThrow(/bindingCargo/) }) - it('_cargo가 null이어도 getCargo는 에러를 던진다', () => { + it('throws from getCargo even when _cargo is null', () => { const req = makeMockReq({ _cargo: null } as any) expect(() => getCargo(req)).toThrow(/bindingCargo/) }) - it('required request field가 없으면 validator를 건너뛴다', () => { + it('skips validators when a required request field is missing', () => { const middleware = bindingCargo(RequiredRequestDTO) const req = makeMockReq({ diff --git a/packages/express-cargo/tests/binding/sourceBinding.test.ts b/packages/express-cargo/tests/binding/sourceBinding.test.ts index ace3df5f..5c528ac8 100644 --- a/packages/express-cargo/tests/binding/sourceBinding.test.ts +++ b/packages/express-cargo/tests/binding/sourceBinding.test.ts @@ -13,7 +13,7 @@ class TestDTO { } describe('source decorator binding', () => { - it('요청 데이터가 정확히 바인딩되고 타입 캐스팅됨', () => { + it('binds request data accurately and casts types', () => { const middleware = bindingCargo(TestDTO) const req = makeMockReq({ @@ -35,11 +35,11 @@ describe('source decorator binding', () => { expect(dto.id).toBe(10) expect(dto.isAdmin).toBe(true) expect(dto.loginAt).toEqual(new Date('2025-09-20T17:00:00.000Z')) - expect(dto.nickname).toBeNull() // optional 처리 + expect(dto.nickname).toBeNull() // optional handling expect(dto.score).toBe(5) }) - it('validator 실패 시 CargoValidationError 발생', () => { + it('throws CargoValidationError when a validator fails', () => { const middleware = bindingCargo(TestDTO) const req = makeMockReq({ @@ -59,7 +59,7 @@ describe('source decorator binding', () => { expect(err.errors).toEqual(expect.arrayContaining([expect.objectContaining({ message: expect.stringContaining('score') })])) }) - it('optional 필드가 없으면 null 처리', () => { + it('sets an optional field to null when it is missing', () => { const middleware = bindingCargo(TestDTO) const req = makeMockReq({ @@ -78,10 +78,10 @@ describe('source decorator binding', () => { expect(dto.nickname).toBeNull() }) - it('targetClass가 Object인 경우(any 타입 등) 원본 객체 데이터를 보존함', () => { + it('preserves the raw object data when targetClass is Object (e.g. any type)', () => { class AnyDataDTO { @Body('data') - data!: any // Object로 리플렉션됨 + data!: any // reflected as Object } const middleware = bindingCargo(AnyDataDTO) @@ -94,11 +94,11 @@ describe('source decorator binding', () => { expect(next).toHaveBeenCalledWith() const dto = getCargo(req)! - // 빈 객체 {}가 아닌 원본 데이터와 일치해야 함 + // should match the raw data, not an empty object {} expect(dto.data).toEqual(rawData) }) - it('required source field가 없으면 validator를 건너뛴다', () => { + it('skips validators when a required source field is missing', () => { const middleware = bindingCargo(TestDTO) const req = makeMockReq({ diff --git a/packages/express-cargo/tests/binding/virtualBinding.test.ts b/packages/express-cargo/tests/binding/virtualBinding.test.ts index 2efa7e89..a96db446 100644 --- a/packages/express-cargo/tests/binding/virtualBinding.test.ts +++ b/packages/express-cargo/tests/binding/virtualBinding.test.ts @@ -23,7 +23,7 @@ class RequiredVirtualDTO { } describe('virtual binding', () => { - it('virtual transformer가 정상 동작', () => { + it('runs the virtual transformer correctly', () => { const middleware = bindingCargo(VirtualDTO) const req = makeMockReq({ @@ -41,7 +41,7 @@ describe('virtual binding', () => { expect(dto.nameLength).toBe(11) }) - it('validator 실패 시 CargoValidationError 발생', () => { + it('throws CargoValidationError when a validator fails', () => { const middleware = bindingCargo(VirtualDTO) const req = makeMockReq({ @@ -57,7 +57,7 @@ describe('virtual binding', () => { expect(err.errors).toEqual(expect.arrayContaining([expect.objectContaining({ message: expect.stringContaining('nameLength') })])) }) - it('optional virtual field가 빈 문자열을 반환해도 optional 검증을 건너뛴다', () => { + it('skips optional validation even when an optional virtual field returns an empty string', () => { const middleware = bindingCargo(VirtualDTO) const req = makeMockReq({ @@ -73,7 +73,7 @@ describe('virtual binding', () => { expect(dto.optionalNameLength).toBeNull() }) - it('required virtual field가 없으면 validator를 건너뛴다', () => { + it('skips validators when a required virtual field is missing', () => { const middleware = bindingCargo(RequiredVirtualDTO) const req = makeMockReq({ diff --git a/packages/express-cargo/tests/rules/dynamicValidation.test.ts b/packages/express-cargo/tests/rules/dynamicValidation.test.ts index a93b4a2e..30841177 100644 --- a/packages/express-cargo/tests/rules/dynamicValidation.test.ts +++ b/packages/express-cargo/tests/rules/dynamicValidation.test.ts @@ -2,10 +2,10 @@ import { bindingCargo, Body, Query, Type, CargoSchemaError } from '../../src' import { makeMockReq, makeMockRes, makeNext } from '../binding/testUtils' describe('schema validation — dynamic runtime validation', () => { - it('런타임에 결정된 클래스가 규칙을 위반하면 CargoSchemaError 발생', () => { + it('throws CargoSchemaError when a class resolved at runtime violates the rules', () => { class InvalidDynamicDto { @Body() - @Query() // 중복 소스 규칙 위반 + @Query() // violates the duplicate-source rule foo!: string } @@ -17,7 +17,7 @@ describe('schema validation — dynamic runtime validation', () => { const middleware = bindingCargo(RootDto) - // InvalidDynamicDto를 사용하도록 요청 + // request that resolves to InvalidDynamicDto const req = makeMockReq({ body: { data: { kind: 'invalid', foo: 'bar' }, @@ -28,16 +28,16 @@ describe('schema validation — dynamic runtime validation', () => { middleware(req, res, next) - // validateAnalysis가 에러를 던지고, middleware의 try-catch에서 잡혀 next(err)로 전달됨 + // validateAnalysis throws, the middleware's try-catch catches it and forwards via next(err) const err = next.mock.calls[0][0] expect(err).toBeInstanceOf(CargoSchemaError) expect(err.message).toContain('InvalidDynamicDto') expect(err.message).toContain('foo') }) - it('한 번 검증된 동적 클래스는 다음 요청에서 다시 검증하지 않음 (캐시 확인)', () => { - // 이 테스트는 기능적으로는 동일하지만, validateAnalysis 내부의 VALIDATED 캐시가 - // 오작동하지 않고 정상적으로 다음 바인딩을 허용하는지 확인하는 의미가 있음 + it('does not re-validate a dynamic class that was already validated (cache check)', () => { + // functionally similar to the previous case, but verifies that the VALIDATED cache inside + // validateAnalysis does not misbehave and still allows the next binding to succeed class ValidDynamicDto { @Body() foo!: string @@ -52,13 +52,13 @@ describe('schema validation — dynamic runtime validation', () => { const middleware = bindingCargo(RootDto) const res = makeMockRes() - // 첫 번째 요청: 검증 및 바인딩 성공 + // first request: validation and binding succeed const req1 = makeMockReq({ body: { data: { foo: 'bar' } } }) const next1 = makeNext() middleware(req1, res, next1) expect(next1).toHaveBeenCalledWith() - // 두 번째 요청: 캐시된 검증 결과 사용 및 바인딩 성공 + // second request: uses the cached validation result and binding succeeds const req2 = makeMockReq({ body: { data: { foo: 'baz' } } }) const next2 = makeNext() middleware(req2, res, next2) diff --git a/packages/express-cargo/tests/rules/kindCategory.test.ts b/packages/express-cargo/tests/rules/kindCategory.test.ts index bda08e6f..0e5a98cf 100644 --- a/packages/express-cargo/tests/rules/kindCategory.test.ts +++ b/packages/express-cargo/tests/rules/kindCategory.test.ts @@ -2,7 +2,7 @@ import { Body, Each, Header, List, Min, Optional, Params, Query, Request, Sessio import { expectViolation, validateCargoSchema } from './testUtils' describe('schema validation — kind category rules', () => { - it('두 Source 데코레이터 공존 시 거부', () => { + it('rejects two coexisting source decorators', () => { class MultipleSourcesDto { @Body() @Query() @@ -12,7 +12,7 @@ describe('schema validation — kind category rules', () => { expectViolation(() => validateCargoSchema(MultipleSourcesDto), 'foo', 'pick a single source') }) - it('Source + @Request 공존 시 거부', () => { + it('rejects a source decorator coexisting with @Request', () => { class SourceWithRequestDto { @Body() @Request(req => req.headers['x-foo']) @@ -22,7 +22,7 @@ describe('schema validation — kind category rules', () => { expectViolation(() => validateCargoSchema(SourceWithRequestDto), 'foo', 'cannot be combined with @Request') }) - it('Source + @Virtual 공존 시 거부', () => { + it('rejects a source decorator coexisting with @Virtual', () => { class SourceWithVirtualDto { @Body() @Virtual(obj => obj.bar) @@ -32,7 +32,7 @@ describe('schema validation — kind category rules', () => { expectViolation(() => validateCargoSchema(SourceWithVirtualDto), 'foo', 'cannot be combined with @Virtual') }) - it('@Request + @Virtual 공존 시 거부', () => { + it('rejects @Request coexisting with @Virtual', () => { class RequestWithVirtualDto { @Request(req => req.ip) @Virtual(obj => obj.bar) @@ -42,7 +42,7 @@ describe('schema validation — kind category rules', () => { expectViolation(() => validateCargoSchema(RequestWithVirtualDto), 'foo', '@Request cannot be combined with @Virtual') }) - it('top-level: kind 데코레이터 없는 필드 거부', () => { + it('top-level: rejects a field without a kind decorator', () => { class MissingKindDto { @Optional() foo!: string @@ -51,7 +51,7 @@ describe('schema validation — kind category rules', () => { expectViolation(() => validateCargoSchema(MissingKindDto), 'foo', 'field must be decorated') }) - it('nested: @Type 으로 참조된 클래스의 필드도 검증', () => { + it('nested: also validates fields of a class referenced via @Type', () => { class NestedDto { @Optional() foo!: string @@ -65,7 +65,7 @@ describe('schema validation — kind category rules', () => { expectViolation(() => validateCargoSchema(OuterDto), 'foo', 'field must be decorated') }) - it('nested via @List: 배열 요소 클래스의 필드도 검증', () => { + it('nested via @List: also validates fields of the array element class', () => { class ItemDto { @Optional() foo!: string @@ -79,7 +79,7 @@ describe('schema validation — kind category rules', () => { expectViolation(() => validateCargoSchema(ListContainerDto), 'foo', 'field must be decorated') }) - it('정상 케이스: 단일 Source 데코레이터는 통과', () => { + it('happy path: a single source decorator passes', () => { class HappyDto { @Body() foo!: string @@ -91,7 +91,7 @@ describe('schema validation — kind category rules', () => { expect(() => validateCargoSchema(HappyDto)).not.toThrow() }) - it('정상 케이스: 모든 종류의 kind 카테고리가 섞여 있어도 충돌만 없으면 통과', () => { + it('happy path: a mix of every kind category passes as long as there is no conflict', () => { class MixedHappyDto { @Body() a!: string @@ -118,7 +118,7 @@ describe('schema validation — kind category rules', () => { expect(() => validateCargoSchema(MixedHappyDto)).not.toThrow() }) - it('정상 케이스: @Each 가 validator 만 감싸는 경우 통과', () => { + it('happy path: passes when @Each wraps only validators', () => { class EachHappyDto { @Body() @Each(Min(0)) diff --git a/packages/express-cargo/tests/rules/primitiveSkip.test.ts b/packages/express-cargo/tests/rules/primitiveSkip.test.ts index 52ec03ff..8b2e81c2 100644 --- a/packages/express-cargo/tests/rules/primitiveSkip.test.ts +++ b/packages/express-cargo/tests/rules/primitiveSkip.test.ts @@ -2,7 +2,7 @@ import { Body, List, Type } from '../../src' import { validateCargoSchema } from './testUtils' describe('schema validation — primitive constructor skip', () => { - it('@List(String)같은 프리미티브 element는 nested 검증 대상에서 제외', () => { + it('excludes primitive elements like @List(String) from nested validation', () => { class PrimitiveListDto { @Body() @List(String) @@ -12,7 +12,7 @@ describe('schema validation — primitive constructor skip', () => { expect(() => validateCargoSchema(PrimitiveListDto)).not.toThrow() }) - it('@List(Array) 처럼 Array가 elementType으로 들어가도 traversal이 그래도 종료된다', () => { + it('terminates traversal even when Array is the elementType, as in @List(Array)', () => { class ArrayElementDto { @Body() @List(Array) @@ -22,7 +22,7 @@ describe('schema validation — primitive constructor skip', () => { expect(() => validateCargoSchema(ArrayElementDto)).not.toThrow() }) - it('@Type(Object)처럼 Object가 typeFn으로 들어가도 traversal이 그래도 종료된다', () => { + it('terminates traversal even when Object is the typeFn, as in @Type(Object)', () => { class ObjectTypeDto { @Body() @Type(Object) @@ -32,7 +32,7 @@ describe('schema validation — primitive constructor skip', () => { expect(() => validateCargoSchema(ObjectTypeDto)).not.toThrow() }) - it('@List(Date)같은 Date도 nested 검증 대상에서 제외', () => { + it('excludes Date elements like @List(Date) from nested validation', () => { class DateListDto { @Body() @List(Date)