# class-utils

This package contains all `class-validator` and `class-tranformer` functions with custom class decorators that can be used to validate a class.

## Usage

```javascript
import { IsString } from '@apizr/class-utils/validators';
import { Transform } from '@apizr/class-utils/transformers';

class MyClass {
  @IsString()
  myString: string;

  @Transform((value) => value === 'true')
  myBoolean: boolean;
}
```

## Dependencies

This package depends on `jsonpath-plus` to be able to use some decorators (e.g. `ValidateSubKeyExistIf`).
See [documentation](https://www.npmjs.com/package/jsonpath-plus) for more information.

## class-validator decorators and class-transformer

See [documentation](https://github.com/typestack/class-validator/blob/develop/README.md) of `class-validator` for the list of default exports.
See [documentation](https://github.com/typestack/class-transformer/blob/develop/README.md) of `class-transformer` for the list of default export.

## Custom transformers

### SplitString

`SplitString(separator: string = ',')`

Will transform a string received to an array of string split by separator.

#### Usage

```javascript
import { SplitString } from '@apizr/class-utils/tranformers';

class MyStringClass {
  @SplitString()
  myString: string[];
}
```

## Custom decorators

### IsBeforeDate

`IsBeforeDate(property: string, options: ValidationOptions)`

Checks if the date is before the date present in the same objet at the specified key.

#### Usage

```javascript
import { IsBeforeDate } from '@apizr/class-utils/validators';

class Dates {
  @IsBeforeDate('end')
  start: Date;

  end: Date;
}
```

If the other date is optional, you can specify a flag to prevent an error if no date is present

```javascript
import { IsBeforeDate } from '@apizr/class-utils/validators';

class Dates {
  @IsBeforeDate('end', { context: { otherDateIsOptional: true } })
  start: Date;

  end?: Date;
}
```

### IsAfterDate

`IsAfterDate(property: string, options: ValidationOptions)`

Checks if the date is after the date present in the same objet at the specified key.

#### Usage

```javascript
import { IsAfterDate } from '@apizr/class-utils/validators';

class Dates {
  start: Date;

  @IsAfterDate('start')
  end: Date;
}
```

If the other date is optional, you can specify a flag to prevent an error if no date is present

```javascript
import { IsAfterDate } from '@apizr/class-utils/validators';

class Dates {
  start?: Date;

  @IsAfterDate('start', { context: { otherDateIsOptional: true } })
  end: Date;
}
```

### IsFutureDate

`IsFutureDate(void)`

Checks if the date is in the future.

#### Usage

```javascript
import { IsFutureDate } from '@apizr/class-utils/validators';

class Date {
  @IsFutureDate()
  end: Date;
}
```

### StartsWith

`StartsWith(startValue: string | string[])`

Checks if the string starts with the specified value

#### Usage

```javascript
import { StartsWith } from '@apizr/class-utils/validators';

class Order {
  @StartsWith('PROD-')
  id: string;
}
```

### OneOfKeysIsDefined

`OneOfKeysIsDefined(keysToValidate: string[])`

Checks if at least one of the key has value (not `null` or `undefined`).

#### Usage

```javascript
import { OneOfKeysIsDefined } from '@apizr/class-utils/validators';

class Product {
  id: string;
  description?: string;
  name?: string;
}

class Order {
  @ValidateNested({ each: true })
  @Type(() => Product)
  @OneOfKeysIsDefined(['description', 'name'], { each: true })
  products: Product[];
}
```

### ValidateSort

`ValidateSort(allowedSort: string | string[])`

Checks that the string array contains valid keys with valid sorting directions (`asc` or `desc`).

#### usage

```javascript
import { ValidateSort } from '@apizr/class-utils/validators';

class Filters {
  @ValidateSort(['date.creation', 'date.modification'])
  sort: string[];
}
```

This will allow `date.creation:asc` or `date.modification:desc` for example.

### ValidateAtLeastOneKeyGroupExists

`ValidateAtLeastOneKeyGroupExists(groups: string[][])`

Checks if one of the keys groups exist in class.

#### Usage

```javascript
@ValidateAtLeastOneKeyGroupExists([
  ['key1', 'key2'],
  ['key2', 'key3']
])
```

Will validate that (`key1` and `key2`) or (`key2` and `key3`) exists in the class.

#### Usage

```javascript
import { ValidateAtLeastOneKeyGroupExists } from '@apizr/class-utils/validators';

@ValidateAtLeastOneKeyGroupExists([
  ['email', 'birthday'],
  ['firstname', 'lastname'],
])
class Customer {
  email: string;

  firstname: string;

  lastname: string;

  birthday: string;
}
```

### ValidateOnlyOneKeyGroupExists

`ValidateOnlyOneKeyGroupExists(groups: string[][])`

Checks if keys of a key groups exist in class.

#### Usage

```javascript
@ValidateOneKeyGroupExists([
  ['key1', 'key2'],
  ['key2', 'key3']
])
```

Will validate that (`key1` and `key2`) xor (exclusive or) (`key2` and `key3`) exists in the class.

#### Usage

```javascript
import { ValidateOnlyOneKeyGroupExists } from '@apizr/class-utils/validators';

@ValidateOnlyOneKeyGroupExists([
  ['email', 'birthday'],
  ['firstname', 'lastname'],
])
class Customer {
  email: string;

  firstname: string;

  lastname: string;

  birthday: string;
}
```

### ValidateSubKeyExistIf

`ValidateSubKeyExistIf(path: string, condition: (value: any) => boolean)`

Allows validating the presence of a specified key in a child object based on a given condition.
The child value described by the path option must have the @IsOptional() decorator (see example below).

#### Usage

```typescript
import { ValidateSubKeyExistIf } from '@apizr/class-utils/validators';

class ChildClass {
    @IsOptional() // Mandatory when this decorator is used
    targetedValue?: any;
}

@ValidateSubKeyExistIf('child.targetedValue',(o) => o.conditionalValue !== false)
class ParentClass {
    @IsString()
    conditionalValue: boolean;

    @ValidateNested({ each: true })
    @Type(() => ChildClass)
    child: ChildClass;
}
```

You can also use `jsonpath-plus` to validate a key in an array of objects:

```typescript
import { ValidateSubKeyExistIf } from '@apizr/class-utils/validators';

class LittleChildClass {
    @IsOptional() // Mandatory when this decorator is used
    targetedValue?: string;
}

class ChildClass {
    @IsOptional()
    @ValidateNested({ each: true })
    @Type(() => LittleChildClass)
    myOtherValue?: LittleChildClass[];
}

@ValidateSubKeyExistIf('child.myOtherValue[*].targetedValue',(o) => o.conditionalValue !== false)
class ParentClass {
    conditionalValue: boolean;
    @ValidateNested({ each: true })
    @Type(() => ChildClass)
    child: ChildClass;
}
```

### IsRecord

`IsRecord(allowedRecordType: RecordAllowedType | RecordAllowedType[], options: IsRecordValidateOptions)`

Check if the record is compliant to specified definition.

By default, the key type will be checked to be a string.

#### Usage

```typescript
import { IsRecord } from '@apizr/class-utils/validators';

class ChildClass {
  @IsString()
  key: string;
}

class ExampleClass {
  @IsRecord('boolean')
  record: Record<string, boolean>;

  @IsRecord(['string', 'undefined'])
  record: Record<string, string | undefined>;

  @IsRecord(ChildClass)
  record: Record<string, ChildClass>;

  @IsRecord('string', { key: 'number' })
  record: Record<number, string>;

  @IsRecord(ChildClass, { each: true })
  records: Record<string, ChildClass>[];

  @IsRecord([ChildClass, ChildClass2, "undefined"])
  record: Record<string, ChildClass | ChildClass2 | undefined>;

  @IsRecord([{ type: ChildClass, array: true }, ChildClass2])
  record: Record<string, ChildClass[] | ChildClass2>;

  @IsRecord([{ type: [ChildClass, ChildClass2], array: true }, ChildClass3])
  record: Record<string, (Class1 | ChildClass2)[] | ChildClass3>;
}
```

### IsCron

`IsCron(inputOptions?: InputOptions['override'])`

Checks if the string is cron expression valid

See [documentation](https://github.com/Airfooox/cron-validate) of cron-validate lib to see the options.

#### Usage

```javascript
import { IsCron } from '@apizr/class-utils/validators';

class ExampleClass {
  @IsCron()
  cronExpression: string;

  @IsCron({
    useSeconds: true,
  })
  cronExpression: string;
}
```