diff --git a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap
index a534931bd..c40cab271 100644
--- a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap
+++ b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap
@@ -12553,6 +12553,523 @@ export default function CreateOwnerForm(props: CreateOwnerFormProps): React.Reac
"
`;
+exports[`amplify form renderer tests NoApi form tests should render custom data form successfully with no configured API 1`] = `
+"/* eslint-disable */
+import * as React from \\"react\\";
+import {
+ Autocomplete,
+ Badge,
+ Button,
+ Divider,
+ Flex,
+ Grid,
+ Icon,
+ ScrollView,
+ Text,
+ TextField,
+ useTheme,
+} from \\"@aws-amplify/ui-react\\";
+import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\";
+import { fetchByPath, validateField } from \\"./utils\\";
+function ArrayField({
+ items = [],
+ onChange,
+ label,
+ inputFieldRef,
+ children,
+ hasError,
+ setFieldValue,
+ currentFieldValue,
+ defaultFieldValue,
+ lengthLimit,
+ getBadgeText,
+ errorMessage,
+}) {
+ const labelElement = {label};
+ const {
+ tokens: {
+ components: {
+ fieldmessages: { error: errorStyles },
+ },
+ },
+ } = useTheme();
+ const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState();
+ const [isEditing, setIsEditing] = React.useState();
+ React.useEffect(() => {
+ if (isEditing) {
+ inputFieldRef?.current?.focus();
+ }
+ }, [isEditing]);
+ const removeItem = async (removeIndex) => {
+ const newItems = items.filter((value, index) => index !== removeIndex);
+ await onChange(newItems);
+ setSelectedBadgeIndex(undefined);
+ };
+ const addItem = async () => {
+ if (
+ currentFieldValue !== undefined &&
+ currentFieldValue !== null &&
+ currentFieldValue !== \\"\\" &&
+ !hasError
+ ) {
+ const newItems = [...items];
+ if (selectedBadgeIndex !== undefined) {
+ newItems[selectedBadgeIndex] = currentFieldValue;
+ setSelectedBadgeIndex(undefined);
+ } else {
+ newItems.push(currentFieldValue);
+ }
+ await onChange(newItems);
+ setIsEditing(false);
+ }
+ };
+ const arraySection = (
+
+ {!!items?.length && (
+
+ {items.map((value, index) => {
+ return (
+ {
+ setSelectedBadgeIndex(index);
+ setFieldValue(items[index]);
+ setIsEditing(true);
+ }}
+ >
+ {getBadgeText ? getBadgeText(value) : value.toString()}
+ {
+ event.stopPropagation();
+ removeItem(index);
+ }}
+ />
+
+ );
+ })}
+
+ )}
+
+
+ );
+ if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) {
+ return (
+
+ {labelElement}
+ {arraySection}
+
+ );
+ }
+ return (
+
+ {labelElement}
+ {isEditing && children}
+ {!isEditing ? (
+ <>
+
+ {errorMessage && hasError && (
+
+ {errorMessage}
+
+ )}
+ >
+ ) : (
+
+ {(currentFieldValue || isEditing) && (
+
+ )}
+
+
+ )}
+ {arraySection}
+
+ );
+}
+export default function CustomDataForm(props) {
+ const { onSubmit, onCancel, onValidate, onChange, overrides, ...rest } =
+ props;
+ const initialValues = {
+ name: \\"John Doe\\",
+ email: [\\"johndoe@amplify.com\\"],
+ phone: [\\"+1-401-152-6995\\"],
+ city: undefined,
+ };
+ const [name, setName] = React.useState(initialValues.name);
+ const [email, setEmail] = React.useState(initialValues.email);
+ const [phone, setPhone] = React.useState(initialValues.phone);
+ const [city, setCity] = React.useState(initialValues.city);
+ const [errors, setErrors] = React.useState({});
+ const resetStateValues = () => {
+ setName(initialValues.name);
+ setEmail(initialValues.email);
+ setCurrentEmailValue(\\"\\");
+ setPhone(initialValues.phone);
+ setCurrentPhoneValue(\\"\\");
+ setCity(initialValues.city);
+ setErrors({});
+ };
+ const [currentEmailValue, setCurrentEmailValue] = React.useState(\\"\\");
+ const emailRef = React.createRef();
+ const [currentPhoneValue, setCurrentPhoneValue] = React.useState(\\"\\");
+ const phoneRef = React.createRef();
+ const validations = {
+ name: [{ type: \\"Required\\" }],
+ email: [{ type: \\"Required\\" }],
+ phone: [{ type: \\"Required\\" }, { type: \\"Phone\\" }],
+ city: [],
+ };
+ const runValidationTasks = async (
+ fieldName,
+ currentValue,
+ getDisplayValue
+ ) => {
+ const value =
+ currentValue && getDisplayValue
+ ? getDisplayValue(currentValue)
+ : currentValue;
+ let validationResponse = validateField(value, validations[fieldName]);
+ const customValidator = fetchByPath(onValidate, fieldName);
+ if (customValidator) {
+ validationResponse = await customValidator(value, validationResponse);
+ }
+ setErrors((errors) => ({ ...errors, [fieldName]: validationResponse }));
+ return validationResponse;
+ };
+ return (
+ {
+ event.preventDefault();
+ const modelFields = {
+ name,
+ email,
+ phone,
+ city,
+ };
+ const validationResponses = await Promise.all(
+ Object.keys(validations).reduce((promises, fieldName) => {
+ if (Array.isArray(modelFields[fieldName])) {
+ promises.push(
+ ...modelFields[fieldName].map((item) =>
+ runValidationTasks(fieldName, item)
+ )
+ );
+ return promises;
+ }
+ promises.push(
+ runValidationTasks(fieldName, modelFields[fieldName])
+ );
+ return promises;
+ }, [])
+ );
+ if (validationResponses.some((r) => r.hasError)) {
+ return;
+ }
+ await onSubmit(modelFields);
+ }}
+ {...getOverrideProps(overrides, \\"CustomDataForm\\")}
+ {...rest}
+ >
+
+ name
+ *
+
+ }
+ isRequired={true}
+ value={name}
+ onChange={(e) => {
+ let { value } = e.target;
+ if (onChange) {
+ const modelFields = {
+ name: value,
+ email,
+ phone,
+ city,
+ };
+ const result = onChange(modelFields);
+ value = result?.name ?? value;
+ }
+ if (errors.name?.hasError) {
+ runValidationTasks(\\"name\\", value);
+ }
+ setName(value);
+ }}
+ onBlur={() => runValidationTasks(\\"name\\", name)}
+ errorMessage={errors.name?.errorMessage}
+ hasError={errors.name?.hasError}
+ {...getOverrideProps(overrides, \\"name\\")}
+ >
+ {
+ let values = items;
+ if (onChange) {
+ const modelFields = {
+ name,
+ email: values,
+ phone,
+ city,
+ };
+ const result = onChange(modelFields);
+ values = result?.email ?? values;
+ }
+ setEmail(values);
+ setCurrentEmailValue(\\"\\");
+ }}
+ currentFieldValue={currentEmailValue}
+ label={
+
+ E-mail
+ *
+
+ }
+ items={email}
+ hasError={errors?.email?.hasError}
+ errorMessage={errors?.email?.errorMessage}
+ setFieldValue={setCurrentEmailValue}
+ inputFieldRef={emailRef}
+ defaultFieldValue={\\"\\"}
+ >
+ {
+ let { value } = e.target;
+ if (errors.email?.hasError) {
+ runValidationTasks(\\"email\\", value);
+ }
+ setCurrentEmailValue(value);
+ }}
+ onBlur={() => runValidationTasks(\\"email\\", currentEmailValue)}
+ errorMessage={errors.email?.errorMessage}
+ hasError={errors.email?.hasError}
+ ref={emailRef}
+ labelHidden={true}
+ {...getOverrideProps(overrides, \\"email\\")}
+ >
+
+ {
+ let values = items;
+ if (onChange) {
+ const modelFields = {
+ name,
+ email,
+ phone: values,
+ city,
+ };
+ const result = onChange(modelFields);
+ values = result?.phone ?? values;
+ }
+ setPhone(values);
+ setCurrentPhoneValue(\\"\\");
+ }}
+ currentFieldValue={currentPhoneValue}
+ label={
+
+ phone
+ *
+
+ }
+ items={phone}
+ hasError={errors?.phone?.hasError}
+ errorMessage={errors?.phone?.errorMessage}
+ setFieldValue={setCurrentPhoneValue}
+ inputFieldRef={phoneRef}
+ defaultFieldValue={\\"\\"}
+ >
+ {
+ let { value } = e.target;
+ if (errors.phone?.hasError) {
+ runValidationTasks(\\"phone\\", value);
+ }
+ setCurrentPhoneValue(value);
+ }}
+ onBlur={() => runValidationTasks(\\"phone\\", currentPhoneValue)}
+ errorMessage={errors.phone?.errorMessage}
+ hasError={errors.phone?.hasError}
+ ref={phoneRef}
+ labelHidden={true}
+ {...getOverrideProps(overrides, \\"phone\\")}
+ >
+
+ {
+ setCity(id);
+ runValidationTasks(\\"city\\", id);
+ }}
+ onClear={() => {
+ setCity(\\"\\");
+ }}
+ onChange={(e) => {
+ let { value } = e.target;
+ if (onChange) {
+ const modelFields = {
+ name,
+ email,
+ phone,
+ city: value,
+ };
+ const result = onChange(modelFields);
+ value = result?.city ?? value;
+ }
+ if (errors.city?.hasError) {
+ runValidationTasks(\\"city\\", value);
+ }
+ setCity(value);
+ }}
+ onBlur={() => runValidationTasks(\\"city\\", city)}
+ errorMessage={errors.city?.errorMessage}
+ hasError={errors.city?.hasError}
+ labelHidden={false}
+ {...getOverrideProps(overrides, \\"city\\")}
+ >
+
+
+
+
+
+
+
+
+ );
+}
+"
+`;
+
+exports[`amplify form renderer tests NoApi form tests should render custom data form successfully with no configured API 2`] = `
+"import * as React from \\"react\\";
+import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\";
+import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\";
+export declare type ValidationResponse = {
+ hasError: boolean;
+ errorMessage?: string;
+};
+export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise;
+export declare type CustomDataFormInputValues = {
+ name?: string;
+ email?: string[];
+ phone?: string[];
+ city?: string;
+};
+export declare type CustomDataFormValidationValues = {
+ name?: ValidationFunction;
+ email?: ValidationFunction;
+ phone?: ValidationFunction;
+ city?: ValidationFunction;
+};
+export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes;
+export declare type CustomDataFormOverridesProps = {
+ CustomDataFormGrid?: PrimitiveOverrideProps;
+ name?: PrimitiveOverrideProps;
+ email?: PrimitiveOverrideProps;
+ phone?: PrimitiveOverrideProps;
+ city?: PrimitiveOverrideProps;
+} & EscapeHatchProps;
+export declare type CustomDataFormProps = React.PropsWithChildren<{
+ overrides?: CustomDataFormOverridesProps | undefined | null;
+} & {
+ onSubmit: (fields: CustomDataFormInputValues) => void;
+ onCancel?: () => void;
+ onChange?: (fields: CustomDataFormInputValues) => CustomDataFormInputValues;
+ onValidate?: CustomDataFormValidationValues;
+} & React.CSSProperties>;
+export default function CustomDataForm(props: CustomDataFormProps): React.ReactElement;
+"
+`;
+
exports[`amplify form renderer tests datastore form tests custom form tests should render a create form for child of 1:m relationship 1`] = `
"/* eslint-disable */
import * as React from \\"react\\";
diff --git a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap
index d726ab447..fb10f6743 100644
--- a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap
+++ b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap
@@ -10248,6 +10248,43 @@ export default function TextFieldPrimitive(
"
`;
+exports[`amplify render tests renderer configurations with NoApi should render component without data binding successfully 1`] = `
+"/* eslint-disable */
+import * as React from \\"react\\";
+import {
+ EscapeHatchProps,
+ getOverrideProps,
+} from \\"@aws-amplify/ui-react/internal\\";
+import { Button, ButtonProps } from \\"@aws-amplify/ui-react\\";
+
+export declare type PrimitiveOverrideProps = Partial &
+ React.DOMAttributes;
+export declare type CustomButtonOverridesProps = {
+ CustomButton?: PrimitiveOverrideProps;
+} & EscapeHatchProps;
+export type CustomButtonProps = React.PropsWithChildren<
+ Partial & {
+ overrides?: CustomButtonOverridesProps | undefined | null;
+ }
+>;
+export default function CustomButton(
+ props: CustomButtonProps
+): React.ReactElement {
+ const { overrides, ...rest } = props;
+ return (
+ /* @ts-ignore: TS2322 */
+
+ );
+}
+"
+`;
+
exports[`amplify render tests sample code snippet tests should generate a sample code snippet for components 1`] = `
"/* eslint-disable */
import * as React from \\"react\\";
diff --git a/packages/codegen-ui-react/lib/__tests__/__utils__/amplify-renderer-generator.ts b/packages/codegen-ui-react/lib/__tests__/__utils__/amplify-renderer-generator.ts
index 3ff813c2f..416e33977 100644
--- a/packages/codegen-ui-react/lib/__tests__/__utils__/amplify-renderer-generator.ts
+++ b/packages/codegen-ui-react/lib/__tests__/__utils__/amplify-renderer-generator.ts
@@ -167,3 +167,9 @@ export const rendererConfigWithGraphQL: ReactRenderConfig = {
fragmentsFilePath: '../graphql/fragments',
},
};
+
+export const rendererConfigWithNoApi: ReactRenderConfig = {
+ apiConfiguration: {
+ dataApi: 'NoApi',
+ },
+};
diff --git a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts
index 95019e99e..2d2dc672f 100644
--- a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts
+++ b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts
@@ -14,6 +14,7 @@
limitations under the License.
*/
/* eslint-disable no-template-curly-in-string */
+import { NoApiError } from '@aws-amplify/codegen-ui';
import { ImportSource } from '../imports';
import { ReactRenderConfig } from '../react-render-config';
import {
@@ -21,6 +22,7 @@ import {
generateComponentOnlyWithAmplifyFormRenderer,
generateWithAmplifyFormRenderer,
rendererConfigWithGraphQL,
+ rendererConfigWithNoApi,
} from './__utils__';
describe('amplify form renderer tests', () => {
@@ -1025,6 +1027,32 @@ describe('amplify form renderer tests', () => {
});
});
+ describe('NoApi form tests', () => {
+ it('should throw if form has data dependency with no configured API', () => {
+ expect(() => {
+ generateWithAmplifyFormRenderer(
+ 'forms/comment-datastore-create',
+ 'datastore/comment-hasMany-belongsTo-relationships',
+ { ...defaultCLIRenderConfig, ...rendererConfigWithNoApi },
+ { isNonModelSupported: true, isRelationshipSupported: true },
+ );
+ }).toThrow(NoApiError);
+ });
+
+ it('should render custom data form successfully with no configured API', () => {
+ const { componentText, declaration } = generateWithAmplifyFormRenderer(
+ 'forms/custom-with-array-field',
+ undefined,
+ { ...defaultCLIRenderConfig, ...rendererConfigWithNoApi },
+ { isNonModelSupported: true, isRelationshipSupported: true },
+ );
+ expect(componentText).not.toContain('DataStore.save');
+ expect(componentText).toContain('resetStateValues();');
+ expect(componentText).toMatchSnapshot();
+ expect(declaration).toMatchSnapshot();
+ });
+ });
+
it('should render form for child of bidirectional 1:m when field defined on parent', () => {
const { componentText, declaration } = generateWithAmplifyFormRenderer(
'forms/car-datastore-update',
diff --git a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts
index e39a00ab1..32279d618 100644
--- a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts
+++ b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts
@@ -13,6 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
+import { NoApiError } from '@aws-amplify/codegen-ui';
import { ModuleKind, ScriptTarget, ScriptKind } from '..';
import {
authorHasManySchema,
@@ -20,6 +21,7 @@ import {
generateWithAmplifyRenderer,
rendererConfigWithGraphQL,
userSchema,
+ rendererConfigWithNoApi,
} from './__utils__';
describe('amplify render tests', () => {
@@ -98,6 +100,19 @@ describe('amplify render tests', () => {
});
});
+ describe('renderer configurations with NoApi', () => {
+ it('should throw if component has data binding', () => {
+ expect(() => {
+ generateWithAmplifyRenderer('workflow/dataStoreCreateItem', rendererConfigWithNoApi);
+ }).toThrow(NoApiError);
+ });
+
+ it('should render component without data binding successfully', () => {
+ const generatedCode = generateWithAmplifyRenderer('buttonGolden', rendererConfigWithNoApi);
+ expect(generatedCode.componentText).toMatchSnapshot();
+ });
+ });
+
describe('collection', () => {
it('should render collection with data binding', () => {
const generatedCode = generateWithAmplifyRenderer('collectionWithBinding');
diff --git a/packages/codegen-ui-react/lib/forms/react-form-renderer.ts b/packages/codegen-ui-react/lib/forms/react-form-renderer.ts
index 9f9458129..b01905af7 100644
--- a/packages/codegen-ui-react/lib/forms/react-form-renderer.ts
+++ b/packages/codegen-ui-react/lib/forms/react-form-renderer.ts
@@ -29,6 +29,8 @@ import {
StudioTemplateRenderer,
validateFormSchema,
FormFeatureFlags,
+ NoApiError,
+ formRequiresDataApi,
} from '@aws-amplify/codegen-ui';
import { EOL } from 'os';
import {
@@ -170,6 +172,11 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer<
this.primaryKeys = dataSchemaMetadata.models[dataTypeName].primaryKeys;
}
}
+
+ // validate inputs for renderer
+ if (formRequiresDataApi(component) && renderConfig.apiConfiguration?.dataApi === 'NoApi') {
+ throw new NoApiError('Form cannot be rendered without a data API');
+ }
}
@handleCodegenErrors
diff --git a/packages/codegen-ui-react/lib/react-render-config.ts b/packages/codegen-ui-react/lib/react-render-config.ts
index d887027a3..6141976e7 100644
--- a/packages/codegen-ui-react/lib/react-render-config.ts
+++ b/packages/codegen-ui-react/lib/react-render-config.ts
@@ -18,7 +18,7 @@ import { ScriptKind, ScriptTarget, ModuleKind } from 'typescript';
export { ScriptKind, ScriptTarget, ModuleKind } from 'typescript';
-export type DataApiKind = 'DataStore' | 'GraphQL';
+export type DataApiKind = 'DataStore' | 'GraphQL' | 'NoApi';
export type ReactRenderConfig = FrameworkRenderConfig & {
script?: ScriptKind;
@@ -26,7 +26,7 @@ export type ReactRenderConfig = FrameworkRenderConfig & {
module?: ModuleKind;
renderTypeDeclarations?: boolean;
inlineSourceMap?: boolean;
- apiConfiguration?: GraphqlRenderConfig | DataStoreRenderConfig;
+ apiConfiguration?: GraphqlRenderConfig | DataStoreRenderConfig | NoApiRenderConfig;
};
export type GraphqlRenderConfig = {
@@ -42,6 +42,10 @@ export type DataStoreRenderConfig = {
dataApi: 'DataStore';
};
+export type NoApiRenderConfig = {
+ dataApi: 'NoApi';
+};
+
export function scriptKindToFileExtension(scriptKind: ScriptKind): string {
switch (scriptKind) {
case ScriptKind.TSX:
diff --git a/packages/codegen-ui-react/lib/react-studio-template-renderer.ts b/packages/codegen-ui-react/lib/react-studio-template-renderer.ts
index bffc4a609..94dfab421 100644
--- a/packages/codegen-ui-react/lib/react-studio-template-renderer.ts
+++ b/packages/codegen-ui-react/lib/react-studio-template-renderer.ts
@@ -38,6 +38,8 @@ import {
isValidVariableName,
InternalError,
resolveBetweenPredicateToMultiplePredicates,
+ NoApiError,
+ componentRequiresDataApi,
} from '@aws-amplify/codegen-ui';
import { EOL } from 'os';
import ts, {
@@ -153,7 +155,11 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer
this.importCollection = new ImportCollection({ rendererConfig: renderConfig });
this.importCollection.ingestComponentMetadata(this.componentMetadata);
addBindingPropertiesImports(this.component, this.importCollection);
+
// TODO: throw warnings on invalid config combinations. i.e. CommonJS + JSX
+ if (componentRequiresDataApi(component) && renderConfig.apiConfiguration?.dataApi === 'NoApi') {
+ throw new NoApiError('Component cannot be rendered without a data API');
+ }
}
@handleCodegenErrors
diff --git a/packages/codegen-ui-react/lib/utils/graphql.ts b/packages/codegen-ui-react/lib/utils/graphql.ts
index 2e5ca69fb..fd2c187ce 100644
--- a/packages/codegen-ui-react/lib/utils/graphql.ts
+++ b/packages/codegen-ui-react/lib/utils/graphql.ts
@@ -29,7 +29,7 @@ import { ImportCollection, ImportValue } from '../imports';
import { capitalizeFirstLetter, getSetNameIdentifier, lowerCaseFirst } from '../helpers';
import { isBoundProperty, isConcatenatedProperty } from '../react-component-render-helper';
import { Primitive } from '../primitive';
-import { DataStoreRenderConfig, GraphqlRenderConfig } from '../react-render-config';
+import { DataStoreRenderConfig, GraphqlRenderConfig, NoApiRenderConfig } from '../react-render-config';
export enum ActionType {
CREATE = 'create',
@@ -42,7 +42,7 @@ export enum ActionType {
/* istanbul ignore next */
export const isGraphqlConfig = (
- apiConfiguration?: GraphqlRenderConfig | DataStoreRenderConfig,
+ apiConfiguration?: GraphqlRenderConfig | DataStoreRenderConfig | NoApiRenderConfig,
): apiConfiguration is GraphqlRenderConfig => {
return apiConfiguration?.dataApi === 'GraphQL';
};
diff --git a/packages/codegen-ui/lib/errors/error-transformer.ts b/packages/codegen-ui/lib/errors/error-transformer.ts
index 76ad597d5..a403ca0ca 100644
--- a/packages/codegen-ui/lib/errors/error-transformer.ts
+++ b/packages/codegen-ui/lib/errors/error-transformer.ts
@@ -13,10 +13,10 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
-import { InternalError, InvalidInputError } from './error-types';
+import { InternalError, InvalidInputError, NoApiError } from './error-types';
export const transformCodegenError = (error: any | unknown): InternalError | InvalidInputError => {
- if (error instanceof InternalError || error instanceof InvalidInputError) {
+ if (error instanceof InternalError || error instanceof InvalidInputError || error instanceof NoApiError) {
return error;
}
diff --git a/packages/codegen-ui/lib/errors/error-types.ts b/packages/codegen-ui/lib/errors/error-types.ts
index b3ba64075..6ef1263e0 100644
--- a/packages/codegen-ui/lib/errors/error-types.ts
+++ b/packages/codegen-ui/lib/errors/error-types.ts
@@ -36,3 +36,13 @@ export class InvalidInputError extends Error {
Object.setPrototypeOf(this, InvalidInputError.prototype);
}
}
+
+/**
+ * Entity requires a working data API to produce a working component but no valid API configuration was provided.
+ */
+export class NoApiError extends Error {
+ constructor(message: string) {
+ super(message);
+ Object.setPrototypeOf(this, NoApiError.prototype);
+ }
+}