Skip to content

Commit

Permalink
api change
Browse files Browse the repository at this point in the history
  • Loading branch information
AmmarHalees committed Dec 22, 2023
1 parent c0cb99f commit 66e03a5
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 125 deletions.
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ const errors = validateField('Ammar', 'firstName', firstNameRules);
#### Methods
| Function | Description | Parameters | Returns |
|-----------------|-------------------------------------------------------|-------------------------------------------------------------------|--------------------|
| `validateField` | Validates a single form field against specified rules.| `value`: The string value of the field.<br>`name`: Name of the field.<br>`rulesObject`: Object containing validation rules. | Array of `FieldError` objects, each containing the `name` of the field and the error `message`. |
| `validateForm` | Validates an entire form. | `values`: A key-value pair object of field names and values.<br>`rulesObject`: A key-value object which maps field names to their rules . | Array of `FieldError` objects for the entire form. |
| `validateField` | Validates a single form field against specified rules.| `value`: The string value of the field.<br>`name`: Name of the field.<br>`Rules`: Object containing validation rules. | Array of `FieldError` objects, each containing the `name` of the field and the error `message`. |
| `validateForm` | Validates an entire form. | `values`: A key-value pair object of field names and values.<br>`Rules`: A key-value object which maps field names to their rules . | Array of `FieldError` objects for the entire form. |

#### Validation Rules

Expand All @@ -78,23 +78,23 @@ const errors = validateField('Ammar', 'firstName', firstNameRules);
```Typescript
import { validateField } from 'onsubmit';

const rulesObject = {
const Rules = {
minLength: { value: 3, message: 'Minimum length is 3' },
maxLength: { value: 10, message: 'Maximum length is 10' },
pattern: { value: /^[a-z]+$/, message: 'Only lowercase letters allowed' },
custom: { value: (value) => value !== 'example', message: 'Value cannot be "example"' },
required: { value: true, message: 'Field is required' },
};

const errors = validateField('exampleValue', 'fieldName', rulesObject);
const errors = validateField('exampleValue', 'fieldName', Rules);
```

#### Validate an entire form

```Typescript
import { validateForm } from 'onsubmit';

const rulesObject = {
const Rules = {
minLength: { value: 3, message: 'Minimum length is 3' },
maxLength: { value: 10, message: 'Maximum length is 10' },
pattern: { value: /^[a-z]+$/, message: 'Only lowercase letters allowed' },
Expand All @@ -108,7 +108,7 @@ const values = {
email: '[email protected]',
};

const errors = validateForm(values, rulesObject);
const errors = validateForm(values, Rules);
```

#### Validate a form with a custom rule
Expand All @@ -117,7 +117,7 @@ const errors = validateForm(values, rulesObject);

import { validateForm } from 'onsubmit';

const rulesObject = {
const Rules = {
custom: { value: (value) => value !== 'example', message: 'Value cannot be "example"' },
};

Expand All @@ -127,14 +127,14 @@ const values = {
fieldName3: 'exampleValue3',
};

const errors = validateForm(values, rulesObject);
const errors = validateForm(values, Rules);
```

## FAQ

### Which rule object has precedence?

The `required` rule has the highest precedence. The remaining rules are evaluated in the order they are specified in the `rulesObject`.
The `required` rule has the highest precedence. The remaining rules are evaluated in the order they are specified in the `Rules`.



Expand All @@ -150,7 +150,7 @@ export interface Rule {
message: string;
}

export interface RulesObject {
export interface Rules {
required?: Rule;
minLength?: Rule;
maxLength?: Rule;
Expand All @@ -167,12 +167,12 @@ export interface CustomFunction {
(value: string): boolean;
}

export interface FormDataObject {
export interface KeyValuePair {
[key: string]: string | FormDataEntryValue;
}

export interface NameRuleMap {
[key: string]: RulesObject;
[key: string]: Rules;
};

export type ConfigMap = {
Expand Down
120 changes: 96 additions & 24 deletions src/core/validateField.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,130 @@
import { RulesObject, FieldError, CustomFunction } from "../types";
import {
Rules,
FieldError,
CustomFunction,
ConfigMap,
Criterion,
ValidationFunction,
} from "../types";
import _utils from "../utils";

export function validateField(
value: string,
name: string,
rulesObject: RulesObject
Rules: Rules
): Array<FieldError> {
const errors: Array<FieldError> = [];

const configMap: {
[key: string]: (value: string, limit: any, message: string) => void;
} = {
minLength: (value: string, limit: number, message: string) => {
const configMap: ConfigMap = {
minLength: (value: string, criterion: number, message: string) => {
/*-------- Error Guards --------*/

if (!_utils.isString(value))
throw new Error(
"File value must be a string to be validated with minLength"
"Input Value for minLength must be a string to be validated"
);

if (value && value.length > 0 && !(value.length >= limit)) {
if (!_utils.isNumber(criterion))
throw new Error(
"Criterion for minLength must be a number to be validated"
);

if (!_utils.isString(message))
throw new Error(
"Message for minLength must be a string to be validated"
);

/*-------- Normal Function --------*/

if (value && value.length > 0 && !(value.length >= criterion)) {
errors.push({ name, message });
}
},
maxLength: (value: string, limit: number, message: string) => {
if (value && !(value.length <= limit)) {
maxLength: (value: string, criterion: number, message: string) => {
/*-------- Error Guards --------*/

if (!_utils.isString(value))
throw new Error(
"Input Value for maxLength must be a string to be validated"
);

if (!_utils.isNumber(criterion))
throw new Error(
"Criterion for maxLength must be a number to be validated"
);

if (!_utils.isString(message))
throw new Error(
"Message for maxLength must be a string to be validated"
);

/*-------- Normal Function --------*/
if (value && !(value.length <= criterion)) {
errors.push({ name, message });
}
},
pattern: (value: string, limit: RegExp, message: string) => {
if (value.length > 0 && !value.match(limit)) {
pattern: (value: string, criterion: RegExp, message: string) => {
/*-------- Error Guards --------*/

if (!_utils.isString(value))
throw new Error(
"Input Value for pattern must be a string to be validated"
);

// if (!_utils.isRegExp(criterion))

// throw new Error(
// "Criterion for pattern must be a RegExp to be validated"
// );

if (!_utils.isString(message))
throw new Error("Message for pattern must be a string to be validated");

if (value.length > 0 && !value.match(criterion)) {
errors.push({ name, message });
}
},
custom: (value: string, limit: CustomFunction, message: string) => {
// if the custom function limit(value) returns true: push an error
if (limit(value)) {
custom: (value: string, criterion: CustomFunction, message: string) => {
/*-------- Error Guards --------*/

if (!_utils.isString(value))
throw new Error(
"Input Value for custom must be a string to be validated"
);

// if (!_utils.isFunction(criterion))
// throw new Error(
// "Criterion for custom must be a function to be validated"
// );

if (!_utils.isString(message))
throw new Error("Message for custom must be a string to be validated");

if (criterion(value)) {
errors.push({ name, message });
}
},
required: (value: string, limit, message: string) => {
if (value.replace(/\s/g, "") === "" && limit) {
required: (value: string, criterion: boolean, message: string) => {
if (value.replace(/\s/g, "") === "" && criterion) {
errors.push({ name, message });
}
},
};

try {
Object.entries(rulesObject).forEach(
([key, { value: ruleValue, message }]) => {
if (configMap[key] && configMap[key] !== undefined) {
const defaultFunction = () => {};
(configMap[key] || defaultFunction)(value, ruleValue, message);
}
Object.entries(Rules).forEach(([key, { value: criterion, message }]) => {
// Assert that key is a key of ConfigMap

const validationFunction = configMap[
key as keyof ConfigMap
] as ValidationFunction<Criterion>;

if (validationFunction) {
validationFunction(value, criterion, message);
} else {
throw new Error(`Invalid Rule: ${key}`);
}
);
});
} catch (e) {
console.error(e);
}
Expand Down
11 changes: 5 additions & 6 deletions src/core/validateForm.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { doKeysMatch } from "../internal/do-keys-match";
import { FieldError, NameRuleMap, FormDataObject, RulesObject } from "../types";
import { FieldError, NameRuleMap, KeyValuePair, Rules } from "../types";
import _utils from "../utils";
import { validateField } from "./validateField";

export function validateForm(
data: FormDataObject | FormData,
NameRuleMap: NameRuleMap
) {
type Data = KeyValuePair | { [k: string]: FormDataEntryValue };

export function validateForm(data: Data, NameRuleMap: NameRuleMap) {
const errors: Array<FieldError> = [];

/*-------- Error Guards --------*/
Expand Down Expand Up @@ -45,7 +44,7 @@ export function validateForm(
...validateField(
stringVal,
key,
NameRuleMap[key as keyof typeof NameRuleMap] as RulesObject
NameRuleMap[key as keyof typeof NameRuleMap] as Rules
)
);
}
Expand Down
7 changes: 2 additions & 5 deletions src/internal/do-keys-match.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { FormDataObject, NameRuleMap } from "../types";
import { KeyValuePair, NameRuleMap } from "../types";

export function doKeysMatch(
data: FormDataObject | FormData,
NameRuleMap: NameRuleMap
) {
export function doKeysMatch(data: KeyValuePair | FormData, NameRuleMap: NameRuleMap) {
const dataKeys = Object.keys(data);
const NameRuleMapKeys = Object.keys(NameRuleMap);

Expand Down
Loading

0 comments on commit 66e03a5

Please sign in to comment.