public void ValidateRelationshipConfigCorrectness()

in src/Core/Configurations/RuntimeConfigValidator.cs [227:324]


    public void ValidateRelationshipConfigCorrectness(RuntimeConfig runtimeConfig)
    {
        // Loop through each entity in the config and verify its relationship.
        foreach ((string entityName, Entity entity) in runtimeConfig.Entities)
        {
            // Skipping relationship validation if entity has no relationship
            // or if graphQL is disabled.
            if (entity.Relationships is null || !entity.GraphQL.Enabled)
            {
                continue;
            }

            if (entity.Source.Type is not EntitySourceType.Table && entity.Relationships is not null
                && entity.Relationships.Count > 0)
            {
                HandleOrRecordException(new DataApiBuilderException(
                        message: $"Cannot define relationship for entity: {entityName}",
                        statusCode: HttpStatusCode.ServiceUnavailable,
                        subStatusCode: DataApiBuilderException.SubStatusCodes.ConfigValidationError));
            }

            string databaseName = runtimeConfig.GetDataSourceNameFromEntityName(entityName);

            foreach ((string relationshipName, EntityRelationship relationship) in entity.Relationships!)
            {
                // Validate if entity referenced in relationship is defined in the config.
                if (!runtimeConfig.Entities.TryGetValue(relationship.TargetEntity, out Entity? targetEntity))
                {
                    HandleOrRecordException(new DataApiBuilderException(
                        message: $"Entity: {relationship.TargetEntity} used for relationship is not defined in the config.",
                        statusCode: HttpStatusCode.ServiceUnavailable,
                        subStatusCode: DataApiBuilderException.SubStatusCodes.ConfigValidationError));
                }

                // Validation to ensure that an entity with graphQL disabled cannot be referenced in a relationship by other entities
                EntityGraphQLOptions? targetEntityGraphQLDetails = targetEntity is not null ? targetEntity.GraphQL : null;
                if (targetEntityGraphQLDetails is not null && !targetEntityGraphQLDetails.Enabled)
                {
                    HandleOrRecordException(new DataApiBuilderException(
                        message: $"Entity: {relationship.TargetEntity} is disabled for GraphQL.",
                        statusCode: HttpStatusCode.ServiceUnavailable,
                        subStatusCode: DataApiBuilderException.SubStatusCodes.ConfigValidationError));
                }

                // Linking object is null and therefore we have a many-one or a one-many relationship. These relationships
                // must be validated separately from many-many relationships. In one-many and many-one relationships, the count
                // of source and target fields need to match, or if one is null the other must be as well.
                // If both of these sets of fields are null, foreign key information, inferred from the database metadata,
                // will be used to define the relationship.
                // see: https://learn.microsoft.com/en-us/azure/data-api-builder/relationships
                if (string.IsNullOrWhiteSpace(relationship.LinkingObject))
                {
                    // Validation to ensure that source and target fields are both null or both not null.
                    if (relationship.SourceFields is null ^ relationship.TargetFields is null)
                    {
                        HandleOrRecordException(new DataApiBuilderException(
                            message: $"Entity: {entityName} has a relationship: {relationshipName}, which has source and target fields " +
                                $"where one is null and the other is not.",
                            statusCode: HttpStatusCode.ServiceUnavailable,
                            subStatusCode: DataApiBuilderException.SubStatusCodes.ConfigValidationError));
                    }

                    // In one-one, one-many, or many-one relationships when both source and target are non null their size must match.
                    if ((relationship.SourceFields is not null && relationship.TargetFields is not null) &&
                        relationship.SourceFields.Length != relationship.TargetFields.Length)
                    {
                        HandleOrRecordException(new DataApiBuilderException(
                            message: $"Entity: {entityName} has a relationship: {relationshipName}, which has {relationship.SourceFields.Length} source fields defined, " +
                                $"but {relationship.TargetFields.Length} target fields defined.",
                            statusCode: HttpStatusCode.ServiceUnavailable,
                            subStatusCode: DataApiBuilderException.SubStatusCodes.ConfigValidationError));
                    }
                }

                // Linking object exists and we therefore have a many-many relationship. Validation here differs from one-many and many-one in that
                // the source and target fields are now only indirectly related through the linking object. Therefore, it is the source and linkingSource
                // along with the target and linkingTarget fields that must match, respectively. Source and linkingSource fields provide the relationship
                // from the source entity to the linkingObject while target and linkingTarget fields provide the relationship from the target entity to the
                // linkingObject.
                // see: https://learn.microsoft.com/en-us/azure/data-api-builder/relationships#many-to-many-relationship
                if (!string.IsNullOrWhiteSpace(relationship.LinkingObject))
                {
                    ValidateFieldsAndAssociatedLinkingFields(
                        fields: relationship.SourceFields,
                        linkingFields: relationship.LinkingSourceFields,
                        fieldType: "source",
                        entityName: entityName,
                        relationshipName: relationshipName);
                    ValidateFieldsAndAssociatedLinkingFields(
                        fields: relationship.TargetFields,
                        linkingFields: relationship.LinkingTargetFields,
                        fieldType: "target",
                        entityName: entityName,
                        relationshipName: relationshipName);
                }
            }
        }
    }