Skip to content

Commit

Permalink
chore: refactor error types & validation messages
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuaellis committed May 10, 2024
1 parent f4e57ad commit 22b2996
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 220 deletions.
40 changes: 27 additions & 13 deletions packages/core/admin/admin/src/components/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -228,18 +228,7 @@ const Form = React.forwardRef<HTMLFormElement, FormProps>(
return { data };
} catch (err) {
if (isErrorYupValidationError(err)) {
let errors: FormErrors = {};

if (err.inner) {
if (err.inner.length === 0) {
return setIn(errors, err.path!, err.message);
}
for (const error of err.inner) {
if (!getIn(errors, error.path!)) {
errors = setIn(errors, error.path!, error.message);
}
}
}
const errors = getYupValidationErrors(err);

if (shouldSetErrors) {
setErrors(errors);
Expand Down Expand Up @@ -470,6 +459,31 @@ const isErrorYupValidationError = (err: any): err is Yup.ValidationError =>
typeof err.name === 'string' &&
err.name === 'ValidationError';

/* -------------------------------------------------------------------------------------------------
* getYupValidationErrors
* -----------------------------------------------------------------------------------------------*/

/**
* @description handy utility to convert a yup validation error into a form
* error object. To be used elsewhere.
*/
const getYupValidationErrors = (err: Yup.ValidationError): FormErrors => {
let errors: FormErrors = {};

if (err.inner) {
if (err.inner.length === 0) {
return setIn(errors, err.path!, err.message);
}
for (const error of err.inner) {
if (!getIn(errors, error.path!)) {
errors = setIn(errors, error.path!, error.message);
}
}
}

return errors;
};

/* -------------------------------------------------------------------------------------------------
* reducer
* -----------------------------------------------------------------------------------------------*/
Expand Down Expand Up @@ -777,7 +791,7 @@ const Blocker = ({ onProceed = () => {}, onCancel = () => {} }: BlockerProps) =>
return null;
};

export { Form, Blocker, useField, useForm };
export { Form, Blocker, useField, useForm, getYupValidationErrors };
export type {
FormHelpers,
FormProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ const GridLayout = ({ size, children }: GridLayoutProps) => {
};

export { GridLayout };
export type { GridColSize };
export type { GridLayoutProps, GridColSize };
4 changes: 2 additions & 2 deletions packages/core/admin/admin/src/components/Layouts/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { styled } from 'styled-components';

import { ActionLayout } from './ActionLayout';
import { ContentLayout } from './ContentLayout';
import { GridLayout } from './GridLayout';
import { GridLayout, GridLayoutProps } from './GridLayout';
import { HeaderLayout, BaseHeaderLayout } from './HeaderLayout';

interface LayoutProps {
Expand Down Expand Up @@ -40,4 +40,4 @@ const Layouts = {
Content: ContentLayout,
};

export { Layouts, type LayoutProps };
export { Layouts, type LayoutProps, type GridLayoutProps };
26 changes: 12 additions & 14 deletions packages/core/content-manager/admin/src/hooks/useDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,26 @@

import * as React from 'react';

import { useNotification, useAPIErrorHandler, useQueryParams } from '@strapi/admin/strapi-admin';
import {
useNotification,
useAPIErrorHandler,
useQueryParams,
FormErrors,
getYupValidationErrors,
} from '@strapi/admin/strapi-admin';
import { Modules } from '@strapi/types';
import { useParams } from 'react-router-dom';
import { ValidationError } from 'yup';

import { SINGLE_TYPES } from '../constants/collections';
import { useGetDocumentQuery } from '../services/documents';
import { buildValidParams } from '../utils/api';
import { createYupSchema, getInnerErrors } from '../utils/validation';
import { createYupSchema } from '../utils/validation';

import { useContentTypeSchema, ComponentsDictionary } from './useContentTypeSchema';

import type { FindOne } from '../../../shared/contracts/collection-types';
import type { ContentType } from '../../../shared/contracts/content-types';
import type { MessageDescriptor, PrimitiveType } from 'react-intl';

interface UseDocumentArgs {
collectionType: string;
Expand Down Expand Up @@ -51,9 +56,7 @@ type UseDocument = (
* This is the schema of the content type, it is not the same as the layout.
*/
schema?: Schema;
validate: (
document: Document
) => null | Record<string, MessageDescriptor & { values?: Record<string, PrimitiveType> }>;
validate: (document: Document) => null | FormErrors;
};

/* -------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -118,9 +121,7 @@ const useDocument: UseDocument = (args, opts) => {
}, [schema, components]);

const validate = React.useCallback(
(
document: Modules.Documents.AnyDocument
): Record<string, MessageDescriptor & { values?: Record<string, PrimitiveType> }> | null => {
(document: Modules.Documents.AnyDocument): FormErrors | null => {
if (!validationSchema) {
throw new Error(
'There is no validation schema generated, this is likely due to the schema not being loaded yet.'
Expand All @@ -132,13 +133,10 @@ const useDocument: UseDocument = (args, opts) => {
return null;
} catch (error) {
if (error instanceof ValidationError) {
const formattedErrors = getInnerErrors(error);
const formattedErrors = getYupValidationErrors(error);
// Ensure that formattedErrors is of the correct type
// You may need to transform formattedErrors to match the expected type
return formattedErrors as unknown as Record<
string,
MessageDescriptor & { values?: Record<string, PrimitiveType> }
>;
return formattedErrors;
}

throw error;
Expand Down
125 changes: 0 additions & 125 deletions packages/core/content-manager/admin/src/utils/tests/validation.test.ts

This file was deleted.

44 changes: 2 additions & 42 deletions packages/core/content-manager/admin/src/utils/validation.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { translatedErrors, FormErrors } from '@strapi/admin/strapi-admin';
import { translatedErrors } from '@strapi/admin/strapi-admin';
import pipe from 'lodash/fp/pipe';
import { type MessageDescriptor } from 'react-intl';
import * as yup from 'yup';

import { DOCUMENT_META_FIELDS } from '../constants/attributes';

import { getIn, setIn } from './objects';

import type { ComponentsDictionary, Schema } from '../hooks/useDocument';
import type { Schema as SchemaUtils } from '@strapi/types';
import type { ObjectShape } from 'yup/lib/object';
Expand Down Expand Up @@ -338,41 +335,4 @@ const addRegexValidation: ValidationFn =
return schema;
};

/* -------------------------------------------------------------------------------------------------
* getInnerErrors
* -----------------------------------------------------------------------------------------------*/

const isErrorMessageDescriptor = (object?: string | object): object is MessageDescriptor => {
return (
typeof object === 'object' && object !== null && 'id' in object && 'defaultMessage' in object
);
};

const getInnerErrors = (error: yup.ValidationError): FormErrors => {
let errors: FormErrors = {};

if (error.inner) {
if (error.inner.length === 0 && error.path) {
errors = setIn(errors, error.path, error.message);
return errors;
}

error.inner.forEach((innerError) => {
if (innerError.path && !getIn(errors, innerError.path)) {
const message = isErrorMessageDescriptor(innerError.message)
? innerError.message
: {
id: innerError.message,
defaultMessage: innerError.message,
values: innerError.params,
};

errors = setIn(errors, innerError.path, message);
}
});
}

return errors;
};

export { createYupSchema, getInnerErrors };
export { createYupSchema };

0 comments on commit 22b2996

Please sign in to comment.