Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/beige-pumas-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@apollo/federation-internals": patch
---

Avoid `>=` comparison for `FeatureVersion` objects
45 changes: 45 additions & 0 deletions internals-js/src/specs/__tests__/coreSpec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,51 @@ class TestFeatureDefinition extends FeatureDefinition {
}
}

describe('FeatureVersion', () => {
it('toString-based comparisons', () => {
const v2_3 = new FeatureVersion(2, 3);
const v10_0 = new FeatureVersion(10, 0);

expect(v2_3.toString()).toBe('v2.3');
expect(v10_0.toString()).toBe('v10.0');

// Operators like <, <=, >, and >= use lexicographic comparison on
// version.toString() strings, but do not perform numeric lexicographic
// comparison of the major and minor numbers, so 'v10...' < 'v2...' and the
// following comparisons produce unintuitive results.
expect([
v2_3 < v10_0,
v2_3 <= v10_0,
v2_3 > v10_0,
v2_3 >= v10_0,
]).toEqual(
// This should really be [true, true, false, false], if JavaScript
// supported more flexible/general operator overloading.
[false, false, true, true],
);

expect(v2_3.compareTo(v10_0)).toBe(-1);
expect(v10_0.compareTo(v2_3)).toBe(1);

expect(v2_3.strictlyGreaterThan(v10_0)).toBe(false);
expect(v10_0.strictlyGreaterThan(v2_3)).toBe(true);

expect(v2_3.lt(v10_0)).toBe(true);
expect(v2_3.lte(v10_0)).toBe(true);
expect(v2_3.gt(v10_0)).toBe(false);
expect(v2_3.gte(v10_0)).toBe(false);
expect(v10_0.lt(v2_3)).toBe(false);
expect(v10_0.lte(v2_3)).toBe(false);
expect(v10_0.gt(v2_3)).toBe(true);
expect(v10_0.gte(v2_3)).toBe(true);

expect(v2_3.equals(v10_0)).toBe(false);
expect(v10_0.equals(v2_3)).toBe(false);
expect(v2_3.equals(v2_3)).toBe(true);
expect(v10_0.equals(v10_0)).toBe(true);
});
});

describe('getMinimumRequiredVersion tests', () => {
it('various combinations', () => {
const versions = new FeatureDefinitions<TestFeatureDefinition>('test')
Expand Down
16 changes: 16 additions & 0 deletions internals-js/src/specs/coreSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,22 @@ export class FeatureVersion {
return 0;
}

public lt(other: FeatureVersion): boolean {
return this.compareTo(other) < 0;
}

public lte(other: FeatureVersion): boolean {
return this.compareTo(other) <= 0;
}

public gt(other: FeatureVersion): boolean {
return this.compareTo(other) > 0;
}

public gte(other: FeatureVersion): boolean {
return this.compareTo(other) >= 0;
}

/**
* Return true if this FeatureVersion is strictly greater than the provided one,
* where ordering is meant by major and then minor number.
Expand Down
10 changes: 5 additions & 5 deletions internals-js/src/specs/federationSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export class FederationSpecDefinition extends FeatureDefinition {
this.registerDirective(createDirectiveSpecification({
name: FederationDirectiveName.SHAREABLE,
locations: [DirectiveLocation.OBJECT, DirectiveLocation.FIELD_DEFINITION],
repeatable: version >= (new FeatureVersion(2, 2)),
repeatable: version.gte(new FeatureVersion(2, 2)),
}));

this.registerSubFeature(INACCESSIBLE_VERSIONS.getMinimumRequiredVersion(version));
Expand All @@ -130,7 +130,7 @@ export class FederationSpecDefinition extends FeatureDefinition {
args: [{ name: 'from', type: (schema) => new NonNullType(schema.stringType()) }],
}));

if (version >= (new FeatureVersion(2, 1))) {
if (version.gte(new FeatureVersion(2, 1))) {
this.registerDirective(createDirectiveSpecification({
name: FederationDirectiveName.COMPOSE_DIRECTIVE,
locations: [DirectiveLocation.SCHEMA],
Expand All @@ -139,20 +139,20 @@ export class FederationSpecDefinition extends FeatureDefinition {
}));
}

if (version >= (new FeatureVersion(2, 3))) {
if (version.gte(new FeatureVersion(2, 3))) {
this.registerDirective(createDirectiveSpecification({
name: FederationDirectiveName.INTERFACE_OBJECT,
locations: [DirectiveLocation.OBJECT],
}));
this.registerSubFeature(TAG_VERSIONS.find(new FeatureVersion(0, 3))!);
}

if (version >= (new FeatureVersion(2, 5))) {
if (version.gte(new FeatureVersion(2, 5))) {
this.registerSubFeature(AUTHENTICATED_VERSIONS.find(new FeatureVersion(0, 1))!);
this.registerSubFeature(REQUIRES_SCOPES_VERSIONS.find(new FeatureVersion(0, 1))!);
}

if (version >= (new FeatureVersion(2, 6))) {
if (version.gte(new FeatureVersion(2, 6))) {
this.registerSubFeature(POLICY_VERSIONS.find(new FeatureVersion(0, 1))!);
}
}
Expand Down
6 changes: 3 additions & 3 deletions internals-js/src/specs/joinSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class JoinSpecDefinition extends FeatureDefinition {
joinType.addArgument('extension', new NonNullType(schema.booleanType()), false);
joinType.addArgument('resolvable', new NonNullType(schema.booleanType()), true);

if (this.version >= (new FeatureVersion(0, 3))) {
if (this.version.gte(new FeatureVersion(0, 3))) {
joinType.addArgument('isInterfaceObject', new NonNullType(schema.booleanType()), false);
}
}
Expand All @@ -93,7 +93,7 @@ export class JoinSpecDefinition extends FeatureDefinition {
// The `graph` argument used to be non-nullable, but @interfaceObject makes us add some field in
// the supergraph that don't "directly" come from any subgraph (they indirectly are inherited from
// an `@interfaceObject` type), and to indicate that, we use a `@join__field(graph: null)` annotation.
const graphArgType = this.version >= (new FeatureVersion(0, 3))
const graphArgType = this.version.gte(new FeatureVersion(0, 3))
? graphEnum
: new NonNullType(graphEnum);
joinField.addArgument('graph', graphArgType);
Expand All @@ -115,7 +115,7 @@ export class JoinSpecDefinition extends FeatureDefinition {
joinImplements.addArgument('interface', new NonNullType(schema.stringType()));
}

if (this.version >= (new FeatureVersion(0, 3))) {
if (this.version.gte(new FeatureVersion(0, 3))) {
const joinUnionMember = this.addDirective(schema, 'unionMember').addLocations(DirectiveLocation.UNION);
joinUnionMember.repeatable = true;
joinUnionMember.addArgument('graph', new NonNullType(graphEnum));
Expand Down