19c9f957b1
Two challenges with error messages - always provide a useful/meaningful error message for the end user instead of the generic one. eg: show "Wrong password" and not "An error occured" - avoid technical details unless error regards a technical feature. eg: show "An error occured" and not "Invalid post-hook payload."; but do show "Invalid issuer URL." as it occurs while configuring SSO What this PR does - Make userFriendlyMessage mandatory for widely used GraphqlQueryRunnerException and CommonQueryRunnerException, so that developers are forced to ask themselves what the error message should be, and as it contains very wide error codes (eg: "Bad request") which should not be mapped to just one default message - Keep userFriendlyMessage optional for service-specific exceptions (eg: workflowStepExecutorException), but convert the error code to userFriendlyMessage mapper to a switch case function with a typecheck ensuring that all codes are mapped to a message. These default messages are still overridable where they are thrown.
98 lines
3.9 KiB
TypeScript
98 lines
3.9 KiB
TypeScript
import { type ObjectRecord } from 'twenty-shared/types';
|
|
import { isDefined } from 'twenty-shared/utils';
|
|
|
|
import {
|
|
type ObjectRecordCursorLeafCompositeValue,
|
|
type ObjectRecordCursorLeafScalarValue,
|
|
type ObjectRecordOrderBy,
|
|
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
|
|
|
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
|
|
import {
|
|
GraphqlQueryRunnerException,
|
|
GraphqlQueryRunnerExceptionCode,
|
|
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
|
import { buildCursorCompositeFieldWhereCondition } from 'src/engine/api/utils/build-cursor-composite-field-where-condition.utils';
|
|
import { computeOperator } from 'src/engine/api/utils/compute-operator.utils';
|
|
import { isAscendingOrder } from 'src/engine/api/utils/is-ascending-order.utils';
|
|
import { validateAndGetOrderByForScalarField } from 'src/engine/api/utils/validate-and-get-order-by.utils';
|
|
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
|
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
|
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
|
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
|
import { buildFieldMapsFromFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/build-field-maps-from-flat-object-metadata.util';
|
|
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
|
|
|
type BuildCursorWhereConditionParams = {
|
|
cursorKey: keyof ObjectRecord;
|
|
cursorValue:
|
|
| ObjectRecordCursorLeafScalarValue
|
|
| ObjectRecordCursorLeafCompositeValue;
|
|
flatObjectMetadata: FlatObjectMetadata;
|
|
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
|
orderBy: ObjectRecordOrderBy;
|
|
isForwardPagination: boolean;
|
|
isEqualityCondition?: boolean;
|
|
};
|
|
|
|
export const buildCursorWhereCondition = ({
|
|
cursorKey,
|
|
cursorValue,
|
|
flatObjectMetadata,
|
|
flatFieldMetadataMaps,
|
|
orderBy,
|
|
isForwardPagination,
|
|
isEqualityCondition = false,
|
|
}: BuildCursorWhereConditionParams): Record<string, unknown> => {
|
|
const { fieldIdByName } = buildFieldMapsFromFlatObjectMetadata(
|
|
flatFieldMetadataMaps,
|
|
flatObjectMetadata,
|
|
);
|
|
|
|
const fieldMetadataId = fieldIdByName[cursorKey];
|
|
|
|
const fieldMetadata = findFlatEntityByIdInFlatEntityMaps({
|
|
flatEntityMaps: flatFieldMetadataMaps,
|
|
flatEntityId: fieldMetadataId,
|
|
});
|
|
|
|
if (!fieldMetadata) {
|
|
throw new GraphqlQueryRunnerException(
|
|
`Field metadata not found for key: ${cursorKey}`,
|
|
GraphqlQueryRunnerExceptionCode.INVALID_CURSOR,
|
|
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
|
|
);
|
|
}
|
|
|
|
if (isCompositeFieldMetadataType(fieldMetadata.type)) {
|
|
return buildCursorCompositeFieldWhereCondition({
|
|
fieldType: fieldMetadata.type,
|
|
fieldKey: cursorKey,
|
|
orderBy,
|
|
cursorValue: cursorValue as ObjectRecordCursorLeafCompositeValue,
|
|
isForwardPagination,
|
|
isEqualityCondition,
|
|
});
|
|
}
|
|
|
|
if (isEqualityCondition) {
|
|
return { [cursorKey]: { eq: cursorValue } };
|
|
}
|
|
|
|
const keyOrderBy = validateAndGetOrderByForScalarField(cursorKey, orderBy);
|
|
const orderByDirection = keyOrderBy[cursorKey];
|
|
|
|
if (!isDefined(orderByDirection)) {
|
|
throw new GraphqlQueryRunnerException(
|
|
'Invalid cursor',
|
|
GraphqlQueryRunnerExceptionCode.INVALID_CURSOR,
|
|
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
|
|
);
|
|
}
|
|
|
|
const isAscending = isAscendingOrder(orderByDirection);
|
|
const computedOperator = computeOperator(isAscending, isForwardPagination);
|
|
|
|
return { [cursorKey]: { [computedOperator]: cursorValue } };
|
|
};
|