Relation field metadata strict validation (#16890)

# Introduction

Now validating relation related object and field foreignkey integrity
This commit is contained in:
Paul Rastoin
2026-01-01 15:09:48 +01:00
committed by GitHub
parent dff5e3cd7b
commit 1d9eb07ee0
3 changed files with 751 additions and 7 deletions
@@ -73,13 +73,51 @@ export const validateMorphOrRelationFlatFieldMetadata = ({
: 'Relation field target metadata not found',
userFriendlyMessage: msg`Relation field target metadata not found`,
});
} else if (!isMorphOrRelationFlatFieldMetadata(targetFlatFieldMetadata)) {
errors.push({
code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
message:
'A relation field can only target a MORPH_RELATION or another RELATION field',
userFriendlyMessage: msg`Invalid relation field target`,
});
} else {
if (!isMorphOrRelationFlatFieldMetadata(targetFlatFieldMetadata)) {
errors.push({
code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
message:
'A relation field can only target a MORPH_RELATION or another RELATION field',
userFriendlyMessage: msg`Invalid relation field target`,
});
} else {
if (
targetFlatFieldMetadata.objectMetadataId !==
flatFieldMetadataToValidate.relationTargetObjectMetadataId
) {
errors.push({
code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
message:
'Target field metadata does not belong to the expected target object',
userFriendlyMessage: msg`Target relation field does not belong to the expected object`,
});
}
if (
targetFlatFieldMetadata.relationTargetObjectMetadataId !==
flatFieldMetadataToValidate.objectMetadataId
) {
errors.push({
code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
message:
'Target field metadata does not point back to the source object',
userFriendlyMessage: msg`Target relation field does not reference the source object`,
});
}
if (
targetFlatFieldMetadata.relationTargetFieldMetadataId !==
flatFieldMetadataToValidate.id
) {
errors.push({
code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
message:
'Target field metadata does not point back to the source field',
userFriendlyMessage: msg`Target relation field does not reference this field`,
});
}
}
}
if (
@@ -377,6 +377,79 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela
}
`;
exports[`Field metadata relation creation should fail relation MANY_TO_ONE (relationCreationPayload) when targetObjectMetadataId is not a valid uuid format 1`] = `
{
"extensions": {
"code": "METADATA_VALIDATION_FAILED",
"errors": {
"agent": [],
"cronTrigger": [],
"databaseEventTrigger": [],
"fieldMetadata": [],
"index": [],
"objectMetadata": [
{
"errors": [
{
"code": "FIELD_METADATA_RELATION_MALFORMED",
"message": "Relation creation payload is invalid",
"userFriendlyMessage": "Invalid relation creation payload",
"value": {
"targetFieldIcon": "IconBuildingSkyscraper",
"targetFieldLabel": "defaultTargetFieldLabel",
"targetObjectMetadataId": Any<String>,
"type": "MANY_TO_ONE",
},
},
],
"flatEntityMinimalInformation": {},
"type": "create_field",
},
],
"pageLayout": [],
"pageLayoutTab": [],
"pageLayoutWidget": [],
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [],
"viewFilter": [],
"viewGroup": [],
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidAgent": 0,
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidFieldMetadata": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidPageLayout": 0,
"invalidPageLayoutTab": 0,
"invalidPageLayoutWidget": 0,
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
"invalidViewFilter": 0,
"invalidViewGroup": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
},
"message": "Multiple validation errors occurred while creating field",
"name": "GraphQLError",
}
`;
exports[`Field metadata relation creation should fail relation MANY_TO_ONE (relationCreationPayload) when targetObjectMetadataId is unknown 1`] = `
{
"extensions": {
@@ -700,6 +773,257 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE when
}
`;
exports[`Field metadata relation creation should fail relation MANY_TO_ONE when self-relation source field join column would conflict with target field name 1`] = `
{
"extensions": {
"code": "METADATA_VALIDATION_FAILED",
"errors": {
"agent": [],
"cronTrigger": [],
"databaseEventTrigger": [],
"fieldMetadata": [],
"index": [],
"objectMetadata": [
{
"errors": [
{
"code": "FIELD_METADATA_RELATION_MALFORMED",
"message": "Relation creation payload is invalid",
"userFriendlyMessage": "Invalid relation creation payload",
"value": {
"targetFieldIcon": "IconBuildingSkyscraper",
"targetFieldLabel": "Parent Id",
"targetObjectMetadataId": Any<String>,
"type": "MANY_TO_ONE",
},
},
],
"flatEntityMinimalInformation": {},
"type": "create_field",
},
],
"pageLayout": [],
"pageLayoutTab": [],
"pageLayoutWidget": [],
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [],
"viewFilter": [],
"viewGroup": [],
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidAgent": 0,
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidFieldMetadata": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidPageLayout": 0,
"invalidPageLayoutTab": 0,
"invalidPageLayoutWidget": 0,
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
"invalidViewFilter": 0,
"invalidViewGroup": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
},
"message": "Multiple validation errors occurred while creating field",
"name": "GraphQLError",
}
`;
exports[`Field metadata relation creation should fail relation MANY_TO_ONE when self-relation target join column would conflict with source field name 1`] = `
{
"extensions": {
"code": "METADATA_VALIDATION_FAILED",
"errors": {
"agent": [],
"cronTrigger": [],
"databaseEventTrigger": [],
"fieldMetadata": [],
"index": [],
"objectMetadata": [
{
"errors": [
{
"code": "FIELD_METADATA_RELATION_MALFORMED",
"message": "Relation creation payload is invalid",
"userFriendlyMessage": "Invalid relation creation payload",
"value": {
"targetFieldIcon": "IconBuildingSkyscraper",
"targetFieldLabel": "Children",
"targetObjectMetadataId": Any<String>,
"type": "MANY_TO_ONE",
},
},
],
"flatEntityMinimalInformation": {},
"type": "create_field",
},
],
"pageLayout": [],
"pageLayoutTab": [],
"pageLayoutWidget": [],
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [],
"viewFilter": [],
"viewGroup": [],
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidAgent": 0,
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidFieldMetadata": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidPageLayout": 0,
"invalidPageLayoutTab": 0,
"invalidPageLayoutWidget": 0,
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
"invalidViewFilter": 0,
"invalidViewGroup": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
},
"message": "Multiple validation errors occurred while creating field",
"name": "GraphQLError",
}
`;
exports[`Field metadata relation creation should fail relation MANY_TO_ONE when source field name conflicts with existing field name 1`] = `
{
"extensions": {
"code": "METADATA_VALIDATION_FAILED",
"errors": {
"agent": [],
"cronTrigger": [],
"databaseEventTrigger": [],
"fieldMetadata": [
{
"errors": [
{
"code": "NOT_AVAILABLE",
"message": "Name "fieldName" is not available as it is already used by another field",
"userFriendlyMessage": "Name "fieldName" is not available as it is already used by another field",
"value": "fieldName",
},
],
"flatEntityMinimalInformation": {
"id": Any<String>,
"name": "fieldName",
"objectMetadataId": Any<String>,
},
"status": "fail",
"type": "create_field",
},
{
"errors": [
{
"code": "FIELD_METADATA_NOT_FOUND",
"message": "Relation field target metadata not found in both existing and about to be created field metadatas",
"userFriendlyMessage": "Relation field target metadata not found",
},
],
"flatEntityMinimalInformation": {
"id": Any<String>,
"name": "uniqueTargetLabel",
"objectMetadataId": Any<String>,
},
"status": "fail",
"type": "create_field",
},
],
"index": [
{
"errors": [
{
"code": "INDEX_FIELD_NOT_FOUND",
"message": "Could not find index field related field metadata",
"userFriendlyMessage": "Field referenced in index does not exist",
},
],
"flatEntityMinimalInformation": {
"id": Any<String>,
"name": "IDX_7ee6c7ff1e4648b0665f60cd5e4",
},
"status": "fail",
"type": "create_index",
},
],
"objectMetadata": [],
"pageLayout": [],
"pageLayoutTab": [],
"pageLayoutWidget": [],
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [],
"viewFilter": [],
"viewGroup": [],
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidAgent": 0,
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidFieldMetadata": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidPageLayout": 0,
"invalidPageLayoutTab": 0,
"invalidPageLayoutWidget": 0,
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
"invalidViewFilter": 0,
"invalidViewGroup": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
},
"message": "Multiple validation errors occurred while creating fields",
"name": "GraphQLError",
}
`;
exports[`Field metadata relation creation should fail relation MANY_TO_ONE when target and source are the same object and name are the same 1`] = `
{
"extensions": {
@@ -1240,6 +1564,81 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela
]
`;
exports[`Field metadata relation creation should fail relation ONE_TO_MANY (relationCreationPayload) when targetObjectMetadataId is not a valid uuid format 1`] = `
[
{
"extensions": {
"code": "METADATA_VALIDATION_FAILED",
"errors": {
"agent": [],
"cronTrigger": [],
"databaseEventTrigger": [],
"fieldMetadata": [],
"index": [],
"objectMetadata": [
{
"errors": [
{
"code": "FIELD_METADATA_RELATION_MALFORMED",
"message": "Relation creation payload is invalid",
"userFriendlyMessage": "Invalid relation creation payload",
"value": {
"targetFieldIcon": "IconBuildingSkyscraper",
"targetFieldLabel": "defaultTargetFieldLabel",
"targetObjectMetadataId": Any<String>,
"type": "ONE_TO_MANY",
},
},
],
"flatEntityMinimalInformation": {},
"type": "create_field",
},
],
"pageLayout": [],
"pageLayoutTab": [],
"pageLayoutWidget": [],
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [],
"viewFilter": [],
"viewGroup": [],
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidAgent": 0,
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidFieldMetadata": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidPageLayout": 0,
"invalidPageLayoutTab": 0,
"invalidPageLayoutWidget": 0,
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
"invalidViewFilter": 0,
"invalidViewGroup": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
},
"message": "Multiple validation errors occurred while creating field",
"name": "GraphQLError",
},
]
`;
exports[`Field metadata relation creation should fail relation ONE_TO_MANY (relationCreationPayload) when targetObjectMetadataId is unknown 1`] = `
[
{
@@ -1571,6 +1970,263 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY when
]
`;
exports[`Field metadata relation creation should fail relation ONE_TO_MANY when self-relation source field join column would conflict with target field name 1`] = `
[
{
"extensions": {
"code": "METADATA_VALIDATION_FAILED",
"errors": {
"agent": [],
"cronTrigger": [],
"databaseEventTrigger": [],
"fieldMetadata": [],
"index": [],
"objectMetadata": [
{
"errors": [
{
"code": "FIELD_METADATA_RELATION_MALFORMED",
"message": "Relation creation payload is invalid",
"userFriendlyMessage": "Invalid relation creation payload",
"value": {
"targetFieldIcon": "IconBuildingSkyscraper",
"targetFieldLabel": "Parent Id",
"targetObjectMetadataId": Any<String>,
"type": "ONE_TO_MANY",
},
},
],
"flatEntityMinimalInformation": {},
"type": "create_field",
},
],
"pageLayout": [],
"pageLayoutTab": [],
"pageLayoutWidget": [],
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [],
"viewFilter": [],
"viewGroup": [],
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidAgent": 0,
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidFieldMetadata": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidPageLayout": 0,
"invalidPageLayoutTab": 0,
"invalidPageLayoutWidget": 0,
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
"invalidViewFilter": 0,
"invalidViewGroup": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
},
"message": "Multiple validation errors occurred while creating field",
"name": "GraphQLError",
},
]
`;
exports[`Field metadata relation creation should fail relation ONE_TO_MANY when self-relation target join column would conflict with source field name 1`] = `
[
{
"extensions": {
"code": "METADATA_VALIDATION_FAILED",
"errors": {
"agent": [],
"cronTrigger": [],
"databaseEventTrigger": [],
"fieldMetadata": [],
"index": [],
"objectMetadata": [
{
"errors": [
{
"code": "FIELD_METADATA_RELATION_MALFORMED",
"message": "Relation creation payload is invalid",
"userFriendlyMessage": "Invalid relation creation payload",
"value": {
"targetFieldIcon": "IconBuildingSkyscraper",
"targetFieldLabel": "Children",
"targetObjectMetadataId": Any<String>,
"type": "ONE_TO_MANY",
},
},
],
"flatEntityMinimalInformation": {},
"type": "create_field",
},
],
"pageLayout": [],
"pageLayoutTab": [],
"pageLayoutWidget": [],
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [],
"viewFilter": [],
"viewGroup": [],
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidAgent": 0,
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidFieldMetadata": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidPageLayout": 0,
"invalidPageLayoutTab": 0,
"invalidPageLayoutWidget": 0,
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
"invalidViewFilter": 0,
"invalidViewGroup": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
},
"message": "Multiple validation errors occurred while creating field",
"name": "GraphQLError",
},
]
`;
exports[`Field metadata relation creation should fail relation ONE_TO_MANY when source field name conflicts with existing field name 1`] = `
[
{
"extensions": {
"code": "METADATA_VALIDATION_FAILED",
"errors": {
"agent": [],
"cronTrigger": [],
"databaseEventTrigger": [],
"fieldMetadata": [
{
"errors": [
{
"code": "NOT_AVAILABLE",
"message": "Name "fieldName" is not available as it is already used by another field",
"userFriendlyMessage": "Name "fieldName" is not available as it is already used by another field",
"value": "fieldName",
},
],
"flatEntityMinimalInformation": {
"id": Any<String>,
"name": "fieldName",
"objectMetadataId": Any<String>,
},
"status": "fail",
"type": "create_field",
},
{
"errors": [
{
"code": "FIELD_METADATA_NOT_FOUND",
"message": "Relation field target metadata not found in both existing and about to be created field metadatas",
"userFriendlyMessage": "Relation field target metadata not found",
},
],
"flatEntityMinimalInformation": {
"id": Any<String>,
"name": "uniqueTargetLabel",
"objectMetadataId": Any<String>,
},
"status": "fail",
"type": "create_field",
},
],
"index": [
{
"errors": [
{
"code": "INDEX_FIELD_NOT_FOUND",
"message": "Could not find index field related field metadata",
"userFriendlyMessage": "Field referenced in index does not exist",
},
],
"flatEntityMinimalInformation": {
"id": Any<String>,
"name": "IDX_d41e56dfc90f36d133336f65736",
},
"status": "fail",
"type": "create_index",
},
],
"objectMetadata": [],
"pageLayout": [],
"pageLayoutTab": [],
"pageLayoutWidget": [],
"role": [],
"roleTarget": [],
"routeTrigger": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [],
"serverlessFunction": [],
"view": [],
"viewField": [],
"viewFilter": [],
"viewGroup": [],
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidAgent": 0,
"invalidCronTrigger": 0,
"invalidDatabaseEventTrigger": 0,
"invalidFieldMetadata": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidPageLayout": 0,
"invalidPageLayoutTab": 0,
"invalidPageLayoutWidget": 0,
"invalidRole": 0,
"invalidRoleTarget": 0,
"invalidRouteTrigger": 0,
"invalidRowLevelPermissionPredicate": 0,
"invalidRowLevelPermissionPredicateGroup": 0,
"invalidServerlessFunction": 0,
"invalidView": 0,
"invalidViewField": 0,
"invalidViewFilter": 0,
"invalidViewGroup": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
},
"message": "Multiple validation errors occurred while creating fields",
"name": "GraphQLError",
},
]
`;
exports[`Field metadata relation creation should fail relation ONE_TO_MANY when target and source are the same object and name are the same 1`] = `
[
{
@@ -154,6 +154,56 @@ describe('Field metadata relation creation should fail', () => {
},
},
},
{
title:
'(relationCreationPayload) when targetObjectMetadataId is not a valid uuid format',
context: {
input: {
relationCreationPayload: {
targetObjectMetadataId: 'not-a-valid-uuid',
},
},
},
},
{
title: 'when source field name conflicts with existing field name',
context: ({ collisionFieldName }) => ({
input: {
name: collisionFieldName,
relationCreationPayload: {
targetFieldLabel: 'Unique Target Label',
},
},
}),
},
{
title:
'when self-relation source field join column would conflict with target field name',
context: {
input: {
name: 'parent',
relationCreationPayload: {
targetObjectMetadataId:
globalTestContext.objectMetadataIds.sourceObjectId,
targetFieldLabel: 'Parent Id',
},
},
},
},
{
title:
'when self-relation target join column would conflict with source field name',
context: {
input: {
name: 'childrenId',
relationCreationPayload: {
targetObjectMetadataId:
globalTestContext.objectMetadataIds.sourceObjectId,
targetFieldLabel: 'Children',
},
},
},
},
];
beforeAll(async () => {