diff --git a/README.ko.md b/README.ko.md index 10a85cf..660ef7d 100644 --- a/README.ko.md +++ b/README.ko.md @@ -122,6 +122,7 @@ app.listen(3000) | `@Uuid(version?, message?)` | 값이 유효한 UUID 형식이어야 하며, 선택적으로 특정 버전(v1, v3, v4, v5)으로 제한할 수 있습니다. | `@Uuid('v4') requestId!: string` | | `@Alpha(message?: string)` | 문자열에 알파벳(A–Z, a–z)만 포함되어야 합니다. | @Alpha() firstName!: string | | `@Alphanumeric(message?: string)` | 필드에 알파벳과 숫자(A-Z, a-z, 0-9)만 포함되어야 합니다. | `@Alphanumeric() productCode!: string` | +| `@IsUppercase(message?: string)` | 필드에 대문자만 포함되어야 합니다. | `@IsUppercase() countryCode!: string` | | `@IsLowercase(message?: string)` | 필드에 소문자만 포함되어야 합니다. | `@IsLowercase() username!: string` | | `@With(fieldName: string)` | 데코레이터가 적용된 필드에 값이 있을 경우, 지정된 대상 필드 (fieldName)도 반드시 값을 가져야 함을 검증하여, 두 필드 간의 필수적인 의존 관계를 설정합니다. | `@With('price') discountRate?: number` | | `@Without(fieldName: string)` | 데코레이터가 선언된 필드에 값이 있을 경우, 지정된 타겟 필드(fieldName)는 반드시 값이 없어야 함을 검증하여 상호 배타적 관계를 설정합니다. | `@Without('isGuest') password?: string` | diff --git a/README.md b/README.md index aea6e1e..8c2622d 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,7 @@ Full guide and API reference: | `@Uuid(version?, message?)` | Validates that the field is a valid UUID, optionally restricted to a specific version (v1, v3, v4, or v5). | `@Uuid('v4') requestId!: string` | | `@Alpha(message?: string)` | Validates that the field contains alphabetic characters (A–Z, a–z) only. | @Alpha() firstName!: string | | `@Alphanumeric(message?: string)` | Validates that the field contains alphanumeric characters (A-Z, a-z, 0-9) only. | `@Alphanumeric() productCode!: string` | +| `@IsUppercase(message?: string)` | Validates that the field contains only uppercase characters. | `@IsUppercase() countryCode!: string` | | `@IsLowercase(message?: string)` | Validates that the field contains only lowercase characters. | `@IsLowercase() username!: string` | | `@With(fieldName: string)` | Validates that if the decorated field has a value, the specified target field (fieldName) must also have a value, establishing a mandatory dependency. | `@With('price') discountRate?: number` | | `@Without(fieldName: string)` | Validates that if the decorated field has a value, the specified target field (fieldName) must NOT have a value, establishing a mutually exclusive relationship. | `@Without('isGuest') password?: string` | diff --git a/apps/docs/docs/decorators/validators.md b/apps/docs/docs/decorators/validators.md index f58b136..106e81e 100644 --- a/apps/docs/docs/decorators/validators.md +++ b/apps/docs/docs/decorators/validators.md @@ -132,6 +132,12 @@ Validates that the decorated field contains alphanumeric characters only (Englis - **`message`** (optional): The error message to display when validation fails. If omitted, a default message will be used. +### `@IsUppercase(message?: string)` + +Validates that the decorated field contains only uppercase characters. + +- **`message`** (optional): The error message to display when validation fails. If omitted, a default message will be used. + ### `@IsLowercase(message?: string)` Validates that the decorated field contains only lowercase characters. diff --git a/apps/docs/i18n/de/docusaurus-plugin-content-docs/current/decorators/validators.md b/apps/docs/i18n/de/docusaurus-plugin-content-docs/current/decorators/validators.md index e2f11e3..e5a5780 100644 --- a/apps/docs/i18n/de/docusaurus-plugin-content-docs/current/decorators/validators.md +++ b/apps/docs/i18n/de/docusaurus-plugin-content-docs/current/decorators/validators.md @@ -132,12 +132,19 @@ Validiert, dass das dekorierte Feld nur alphanumerische Zeichen enthält (englis - **`message`** (optional): Die Fehlermeldung, die angezeigt wird, wenn die Validierung fehlschlägt. Wenn weggelassen, wird eine Standardmeldung verwendet. +### `@IsUppercase(message?: string)` + +Validiert, dass das dekorierte Feld nur Großbuchstaben enthält. + +- **`message`** (optional): Die Fehlermeldung, die angezeigt wird, wenn die Validierung fehlschlägt. Wenn weggelassen, wird eine Standardmeldung verwendet. + ### `@IsLowercase(message?: string)` Validiert, dass das dekorierte Feld nur Kleinbuchstaben enthält. - **`message`** (optional): Die Fehlermeldung, die angezeigt wird, wenn die Validierung fehlschlägt. Wenn weggelassen, wird eine Standardmeldung verwendet. + ### `@With(fieldName: string, message?: string)` Validiert, dass, wenn das dekorierte Feld einen Wert hat, das angegebene Zielfeld (fieldName) ebenfalls einen Wert haben muss, wodurch eine zwingende Abhängigkeit zwischen den beiden Feldern hergestellt wird. diff --git a/apps/docs/i18n/fr/docusaurus-plugin-content-docs/current/decorators/validators.md b/apps/docs/i18n/fr/docusaurus-plugin-content-docs/current/decorators/validators.md index 3fceb70..4357369 100644 --- a/apps/docs/i18n/fr/docusaurus-plugin-content-docs/current/decorators/validators.md +++ b/apps/docs/i18n/fr/docusaurus-plugin-content-docs/current/decorators/validators.md @@ -132,12 +132,19 @@ Valide que le champ décoré contient uniquement des caractères alphanumérique - **`message`** (optionnel) : Le message d'erreur à afficher lorsque la validation échoue. S'il est omis, un message par défaut sera utilisé. +### `@IsUppercase(message?: string)` + +Valide que le champ décoré contient uniquement des caractères en majuscules. + +- **`message`** (optionnel) : Le message d'erreur à afficher lorsque la validation échoue. S'il est omis, un message par défaut sera utilisé. + ### `@IsLowercase(message?: string)` Valide que le champ décoré contient uniquement des caractères en minuscules. - **`message`** (optionnel) : Le message d'erreur à afficher lorsque la validation échoue. S'il est omis, un message par défaut sera utilisé. + ### `@With(fieldName: string, message?: string)` Valide que si le champ décoré a une valeur, le champ cible spécifié (fieldName) doit également avoir une valeur, établissant une dépendance obligatoire entre les deux champs. diff --git a/apps/docs/i18n/ko/docusaurus-plugin-content-docs/current/decorators/validators.md b/apps/docs/i18n/ko/docusaurus-plugin-content-docs/current/decorators/validators.md index 20dc295..66453c2 100644 --- a/apps/docs/i18n/ko/docusaurus-plugin-content-docs/current/decorators/validators.md +++ b/apps/docs/i18n/ko/docusaurus-plugin-content-docs/current/decorators/validators.md @@ -174,12 +174,20 @@ title: 유효성 검사 데코레이터 - **`message`** (선택 사항): 검증 실패 시 표시할 메시지. 생략하면 기본 메시지가 사용됩니다. +### `@IsUppercase(message?: string)` + +데코레이터가 적용된 필드에 대문자만 포함되어 있는지 검증합니다. + +- **`message`** (선택 사항): 검증 실패 시 표시할 메시지. 생략하면 기본 메시지가 사용됩니다. + ### `@IsLowercase(message?: string)` 데코레이터가 적용된 필드에 소문자만 포함되어 있는지 검증합니다. - **`message`** (선택 사항): 검증 실패 시 표시할 메시지. 생략하면 기본 메시지가 사용됩니다. +--- + ### `@With(fieldName: string, message?: string)` 데코레이터가 적용된 속성이 값을 가질 경우, 지정된 대상 속성 또한 반드시 값을 가지고 있어야 함을 검증하여 두 속성 간에 필수적인 의존 관계를 설정합니다. diff --git a/apps/docs/i18n/ru/docusaurus-plugin-content-docs/current/decorators/validators.md b/apps/docs/i18n/ru/docusaurus-plugin-content-docs/current/decorators/validators.md index beabccf..ada199d 100644 --- a/apps/docs/i18n/ru/docusaurus-plugin-content-docs/current/decorators/validators.md +++ b/apps/docs/i18n/ru/docusaurus-plugin-content-docs/current/decorators/validators.md @@ -132,12 +132,19 @@ Express-Cargo использует декораторы для валидаци - **`message`** (необязательно): Сообщение об ошибке, которое будет отображаться при сбое валидации. Если опущено, будет использоваться сообщение по умолчанию. +### `@IsUppercase(message?: string)` + +Проверяет, что декорированное поле содержит только символы верхнего регистра. + +- **`message`** (необязательно): Сообщение об ошибке, которое будет отображаться при сбое валидации. Если опущено, будет использоваться сообщение по умолчанию. + ### `@IsLowercase(message?: string)` Проверяет, что декорированное поле содержит только символы нижнего регистра. - **`message`** (необязательно): Сообщение об ошибке, которое будет отображаться при сбое валидации. Если опущено, будет использоваться сообщение по умолчанию. + ### `@With(fieldName: string, message?: string)` Проверяет, что если декорированное поле имеет значение, указанное целевое поле (fieldName) также должно иметь значение, устанавливая обязательную зависимость между двумя полями. diff --git a/apps/example/README.md b/apps/example/README.md index ffc506b..553aa6e 100644 --- a/apps/example/README.md +++ b/apps/example/README.md @@ -770,6 +770,28 @@ curl -X POST 'http://localhost:3000/alphanumeric' \ ``` --- +### @IsUppercase + +```typescript +class IsUppercaseExample { + @Body() + @IsUppercase() + text!: string +} + +router.post('/is-uppercase', bindingCargo(IsUppercaseExample), (req, res) => { + const cargo = getCargo(req) + res.json(cargo) +}) +``` + +```shell +curl -X POST 'http://localhost:3000/is-uppercase' \ + -H 'Content-Type: application/json' \ + -d '{ "text": "HELLO" }' +``` +--- + ### @IsLowercase ```typescript diff --git a/apps/example/src/routers/validator.ts b/apps/example/src/routers/validator.ts index c20bb15..1e79fb9 100644 --- a/apps/example/src/routers/validator.ts +++ b/apps/example/src/routers/validator.ts @@ -23,6 +23,7 @@ import { Alpha, Uuid, Alphanumeric, + IsUppercase, IsLowercase, With, Without, @@ -272,6 +273,17 @@ router.post('/alphanumeric', bindingCargo(AlphanumericExample), (req, res) => { res.json(cargo) }) +class IsUppercaseExample { + @Body() + @IsUppercase() + text!: string +} + +router.post('/is-uppercase', bindingCargo(IsUppercaseExample), (req, res) => { + const cargo = getCargo(req) + res.json(cargo) +}) + class IsLowercaseExample { @Body() @IsLowercase() diff --git a/packages/express-cargo/src/validator.ts b/packages/express-cargo/src/validator.ts index c23209e..b4c4e64 100644 --- a/packages/express-cargo/src/validator.ts +++ b/packages/express-cargo/src/validator.ts @@ -398,6 +398,25 @@ export function Alphanumeric(message?: cargoErrorMessage): TypedPropertyDecorato } } +/** + * Checks if the string contains only uppercase characters. + * @param message - Optional custom error message. + */ +export function IsUppercase(message?: cargoErrorMessage): TypedPropertyDecorator { + return (target, propertyKey): void => { + addValidator( + target, + propertyKey, + new ValidatorRule( + propertyKey, + 'isUppercase', + (value: unknown) => typeof value === 'string' && value.toUpperCase() === value, + message || `${String(propertyKey)} should be uppercase`, + ), + ) + } +} + /** * Checks if the string contains only lowercase characters. * @param message - Optional custom error message. diff --git a/packages/express-cargo/tests/validator/isUppercase.test.ts b/packages/express-cargo/tests/validator/isUppercase.test.ts new file mode 100644 index 0000000..8047d57 --- /dev/null +++ b/packages/express-cargo/tests/validator/isUppercase.test.ts @@ -0,0 +1,61 @@ +import { CargoFieldError, IsUppercase } from '../../src' +import { CargoClassMetadata } from '../../src/metadata' + +describe('isUppercase decorator', () => { + class Sample { + @IsUppercase() + uppercaseValue!: string + + noValidatorValue!: string + } + + const classMeta = new CargoClassMetadata(Sample.prototype) + const meta = classMeta.getFieldMetadata('uppercaseValue') + const isUppercaseRule = meta.getValidators()?.find(v => v.type === 'isUppercase') + + it('should have isUppercase validator', () => { + expect(isUppercaseRule).toBeDefined() + expect(isUppercaseRule?.message).toBe('uppercaseValue should be uppercase') + }) + + it('should pass for all-uppercase strings', () => { + expect(isUppercaseRule?.validate('HELLO')).toBeNull() + expect(isUppercaseRule?.validate('HELLO WORLD')).toBeNull() + expect(isUppercaseRule?.validate('HELLO123')).toBeNull() + expect(isUppercaseRule?.validate('')).toBeNull() + }) + + it('should fail for strings with lowercase characters', () => { + expect(isUppercaseRule?.validate('hello')).toBeInstanceOf(CargoFieldError) + expect(isUppercaseRule?.validate('Hello')).toBeInstanceOf(CargoFieldError) + expect(isUppercaseRule?.validate('helloWorld')).toBeInstanceOf(CargoFieldError) + }) + + it('should fail for non-string values', () => { + expect(isUppercaseRule?.validate(null)).toBeInstanceOf(CargoFieldError) + expect(isUppercaseRule?.validate(undefined)).toBeInstanceOf(CargoFieldError) + expect(isUppercaseRule?.validate(123)).toBeInstanceOf(CargoFieldError) + }) + + it('should not have isUppercase validator on undecorated field', () => { + const meta = classMeta.getFieldMetadata('noValidatorValue') + const isUppercaseRule = meta.getValidators()?.find(v => v.type === 'isUppercase') + + expect(isUppercaseRule).toBeUndefined() + }) + + it('should support custom error message', () => { + class CustomMessage { + @IsUppercase('custom error') + value!: string + } + + const customMeta = new CargoClassMetadata(CustomMessage.prototype) + const meta = customMeta.getFieldMetadata('value') + const rule = meta.getValidators()?.find(v => v.type === 'isUppercase') + + const error = rule?.validate('lower') + expect(error).toBeInstanceOf(CargoFieldError) + expect(error?.message).toBe('custom error') + }) +})