Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cli): regular relation fields are incorrectly recognized as self-relation #1790

Merged
merged 3 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,7 @@ import {
isEnum,
isStringLiteral,
} from '@zenstackhq/language/ast';
import {
getLiteral,
getModelFieldsWithBases,
getModelIdFields,
getModelUniqueFields,
isDelegateModel,
} from '@zenstackhq/sdk';
import { getModelFieldsWithBases, getModelIdFields, getModelUniqueFields, isDelegateModel } from '@zenstackhq/sdk';
import { AstNode, DiagnosticInfo, ValidationAcceptor, getDocument } from 'langium';
import { findUpInheritance } from '../../utils/ast-utils';
import { IssueCodes, SCALAR_TYPES } from '../constants';
Expand Down Expand Up @@ -147,15 +141,15 @@ export default class DataModelValidator implements AstValidator<DataModel> {
}
}

if (!fields && !references) {
return { attr: relAttr, name, fields, references, valid: true };
}

if (!fields || !references) {
if (this.isSelfRelation(field, name)) {
// self relations are partial
// https://www.prisma.io/docs/concepts/components/prisma-schema/relations/self-relations
} else {
if (accept) {
accept('error', `Both "fields" and "references" must be provided`, { node: relAttr });
}
if (accept) {
accept('error', `"fields" and "references" must be provided together`, { node: relAttr });
}
// }
} else {
// validate "fields" and "references" typing consistency
if (fields.length !== references.length) {
Expand Down Expand Up @@ -203,34 +197,8 @@ export default class DataModelValidator implements AstValidator<DataModel> {
return { attr: relAttr, name, fields, references, valid };
}

private isSelfRelation(field: DataModelField, relationName?: string) {
if (field.type.reference?.ref === field.$container) {
// field directly references back to its type
return true;
}

if (relationName) {
// field's relation points to another type, and that type's opposite relation field
// points back
const oppositeModel = field.type.reference?.ref as DataModel;
if (oppositeModel) {
const oppositeModelFields = getModelFieldsWithBases(oppositeModel);
for (const oppositeField of oppositeModelFields) {
// find the opposite relation with the matching name
const relAttr = oppositeField.attributes.find((a) => a.decl.ref?.name === '@relation');
if (relAttr) {
const relNameExpr = relAttr.args.find((a) => !a.name || a.name === 'name');
const relName = getLiteral<string>(relNameExpr?.value);
if (relName === relationName && oppositeField.type.reference?.ref === field.$container) {
// found an opposite relation field that points back to this field's type
return true;
}
}
}
}
}

return false;
private isSelfRelation(field: DataModelField) {
return field.type.reference?.ref === field.$container;
}

private validateRelationField(contextModel: DataModel, field: DataModelField, accept: ValidationAcceptor) {
Expand Down Expand Up @@ -330,10 +298,10 @@ export default class DataModelValidator implements AstValidator<DataModel> {
// if both the field is array, then it's an implicit many-to-many relation
if (!(field.type.array && oppositeField.type.array)) {
[field, oppositeField].forEach((f) => {
if (!this.isSelfRelation(f, thisRelation.name)) {
if (!this.isSelfRelation(f)) {
accept(
'error',
'Field for one side of relation must carry @relation attribute with both "fields" and "references" fields',
'Field for one side of relation must carry @relation attribute with both "fields" and "references"',
{ node: f }
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ describe('Data Model Validation Tests', () => {
aId String
}
`)
).toMatchObject(errorLike(`Both "fields" and "references" must be provided`));
).toMatchObject(errorLike(`"fields" and "references" must be provided together`));

// one-to-one inconsistent attribute
expect(
Expand Down Expand Up @@ -541,7 +541,7 @@ describe('Data Model Validation Tests', () => {
`)
).toMatchObject(
errorLike(
`Field for one side of relation must carry @relation attribute with both "fields" and "references" fields`
`Field for one side of relation must carry @relation attribute with both "fields" and "references"`
)
);

Expand Down
Loading