mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-28 04:10:44 -04:00
* copies v2 form components from POC branch * fixes issue in form-config-generator when path parameters are not defined * adds api code-generator for snippet creation * expands cli and terraform code generators * updates form-config-generator to return api path from spec * fixes issue setting field value in v2-form class * updates form-config types * updates v2 form and renderer components to conditional render fields * adds v2 form apply component * updates v2 form wizard component to support apply step * add support for field types (text input variants, text area, checkbox, radio, masked input) and add test coverage * Dynamic field visibility and Select field support * [POC] Public PKI (mocked) Wizard - revert this before merging * Revert "[POC] Public PKI (mocked) Wizard - revert this before merging" This reverts commit 66646f1d7a71d0e67028ebcabcfe33925197ffc9. * cleanup & address copilot pr comments * address PR comments --------- Co-authored-by: Shannon Roberts (Beagin) <beagins@users.noreply.github.com> Co-authored-by: Jordan Reimer <jordan.reimer@hashicorp.com>
180 lines
5.7 KiB
TypeScript
180 lines
5.7 KiB
TypeScript
/**
|
|
* Copyright IBM Corp. 2016, 2025
|
|
* SPDX-License-Identifier: BUSL-1.1
|
|
*/
|
|
|
|
import { get } from '@ember/object';
|
|
import type { FieldValue, FormField } from './form-config';
|
|
import { validators } from './form-validators';
|
|
|
|
/**
|
|
* Options that can be passed to validators.
|
|
*
|
|
* @property {boolean} [nullable] - For 'length' and 'number': allow null/undefined values
|
|
* @property {number} [min] - For 'min': minimum numeric value
|
|
* @property {number} [max] - For 'max': maximum numeric value
|
|
* @property {number} [minLength] - For 'minLength': minimum string length
|
|
* @property {number} [maxLength] - For 'maxLength': maximum string length
|
|
* @property {string | RegExp} [pattern] - For 'pattern': regex pattern string or RegExp object
|
|
* @property {string} [flags] - For 'pattern': regex flags (e.g., 'i', 'g', 'gi')
|
|
* @property {string | number | boolean} [value] - For 'isNot': value to compare against
|
|
*/
|
|
export interface ValidatorOptions {
|
|
nullable?: boolean;
|
|
min?: number;
|
|
max?: number;
|
|
minLength?: number;
|
|
maxLength?: number;
|
|
pattern?: string | RegExp;
|
|
flags?: string;
|
|
value?: string | number | boolean;
|
|
}
|
|
|
|
/**
|
|
* Named validator using a predefined validator type.
|
|
* References a validator in validators.js (e.g., 'required', 'email', 'url').
|
|
*
|
|
* @property {ValidatorType} type - Reference to a validator in validators.js (e.g., 'required', 'email', 'url')
|
|
* @property {string | ((formData: Record<string, unknown>) => string)} message - Error message shown when validation fails
|
|
* @property {ValidatorOptions} [options] - Options passed to the validator function (e.g., { min: 3, max: 10 })
|
|
*/
|
|
interface NamedValidationRule {
|
|
type: ValidatorType;
|
|
message: string | ((formData: Record<string, unknown>) => string);
|
|
options?: ValidatorOptions;
|
|
}
|
|
|
|
/**
|
|
* Custom validation rule with a custom validator function.
|
|
* The validator function receives the entire form data and returns true if valid.
|
|
*
|
|
* @property {(formData: Record<string, unknown>, options?: ValidatorOptions) => boolean} validator - Custom validator function that receives entire form data
|
|
* @property {string | ((formData: Record<string, unknown>) => string)} message - Error message shown when validation fails
|
|
* @property {ValidatorOptions} [options] - Optional configuration for the validator
|
|
*/
|
|
interface CustomValidationRule {
|
|
validator: (formData: Record<string, unknown>, options?: ValidatorOptions) => boolean;
|
|
message: string | ((formData: Record<string, unknown>) => string);
|
|
options?: ValidatorOptions;
|
|
}
|
|
|
|
/**
|
|
* Validation rule for a form field.
|
|
* Can be either a named validator (with type) or a custom validator function.
|
|
*/
|
|
export type ValidationRule = NamedValidationRule | CustomValidationRule;
|
|
|
|
/**
|
|
* HTML5 standard validator types.
|
|
* These correspond to built-in validators in form-validators.ts.
|
|
*/
|
|
export type ValidatorType =
|
|
| 'required'
|
|
| 'email'
|
|
| 'url'
|
|
| 'pattern'
|
|
| 'minLength'
|
|
| 'maxLength'
|
|
| 'min'
|
|
| 'max';
|
|
|
|
/**
|
|
* Default error messages for built-in validators.
|
|
* Used as fallback when validation rule doesn't provide a message.
|
|
*/
|
|
const DEFAULT_MESSAGES: Record<ValidatorType, string> = {
|
|
required: 'This field is required',
|
|
email: 'Please enter a valid email address',
|
|
url: 'Please enter a valid URL',
|
|
pattern: 'Invalid format',
|
|
minLength: 'Value is too short',
|
|
maxLength: 'Value is too long',
|
|
min: 'Value is too small',
|
|
max: 'Value is too large',
|
|
};
|
|
|
|
/**
|
|
* Validate a single field and return validation errors.
|
|
*
|
|
* @param field - The field configuration to validate
|
|
* @param value - The current value of the field
|
|
* @param payload - The entire form payload for cross-field validation
|
|
* @returns Array of error messages (empty if valid)
|
|
*/
|
|
export function validateField<TPayload extends object>(
|
|
field: FormField,
|
|
value: FieldValue,
|
|
payload: TPayload
|
|
): string[] {
|
|
if (!field.validations || field.validations.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
return field.validations
|
|
.filter((rule) => !runValidator(rule, value, payload))
|
|
.map((rule) => {
|
|
// Function message (dynamic based on form data)
|
|
if (typeof rule.message === 'function') {
|
|
return rule.message(payload as Record<string, unknown>);
|
|
}
|
|
// Explicit message provided
|
|
if (rule.message) {
|
|
return rule.message;
|
|
}
|
|
// Fallback to default message for named validators
|
|
if ('type' in rule && rule.type in DEFAULT_MESSAGES) {
|
|
return DEFAULT_MESSAGES[rule.type];
|
|
}
|
|
// Last resort fallback
|
|
return 'Validation failed';
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Validate all fields in a form and return a map of field names to errors.
|
|
*
|
|
* @param fields - Array of all field configurations in the form
|
|
* @param payload - The entire form payload
|
|
* @returns Map of field names to their validation errors
|
|
*/
|
|
export function validateAllFields<TPayload extends object>(
|
|
fields: FormField[],
|
|
payload: TPayload
|
|
): Map<string, string[]> {
|
|
const errors = new Map<string, string[]>();
|
|
|
|
for (const field of fields) {
|
|
const fieldErrors = validateField(field, get(payload, field.name) as FieldValue, payload);
|
|
if (fieldErrors.length > 0) {
|
|
errors.set(field.name, fieldErrors);
|
|
}
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
/**
|
|
* Execute a single validation rule on a field value.
|
|
*/
|
|
function runValidator<TPayload extends object>(
|
|
rule: ValidationRule,
|
|
value: FieldValue,
|
|
payload: TPayload
|
|
): boolean {
|
|
// Named validator (type-based)
|
|
if ('type' in rule) {
|
|
const validatorFn = validators[rule.type];
|
|
if (!validatorFn) {
|
|
console.warn(`Unknown validator type: ${rule.type}`);
|
|
return true;
|
|
}
|
|
return validatorFn(value, rule.options || {});
|
|
}
|
|
|
|
// Custom validator function
|
|
if ('validator' in rule) {
|
|
return rule.validator(payload as Record<string, unknown>, rule.options);
|
|
}
|
|
|
|
return true;
|
|
}
|