@apollo/gateway 0.45.0-alpha.1 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +42 -16
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +28 -18
- package/dist/config.js.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.js +4 -1
- package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
- package/dist/executeQueryPlan.d.ts.map +1 -1
- package/dist/executeQueryPlan.js +1 -1
- package/dist/executeQueryPlan.js.map +1 -1
- package/dist/index.d.ts +35 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +205 -308
- package/dist/index.js.map +1 -1
- package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts +31 -0
- package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts.map +1 -0
- package/dist/supergraphManagers/IntrospectAndCompose/index.js +112 -0
- package/dist/supergraphManagers/IntrospectAndCompose/index.js.map +1 -0
- package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts +12 -0
- package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts.map +1 -0
- package/dist/{loadServicesFromRemoteEndpoint.js → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js} +6 -6
- package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js.map +1 -0
- package/dist/supergraphManagers/LegacyFetcher/index.d.ts +33 -0
- package/dist/supergraphManagers/LegacyFetcher/index.d.ts.map +1 -0
- package/dist/supergraphManagers/LegacyFetcher/index.js +149 -0
- package/dist/supergraphManagers/LegacyFetcher/index.js.map +1 -0
- package/dist/supergraphManagers/LocalCompose/index.d.ts +19 -0
- package/dist/supergraphManagers/LocalCompose/index.d.ts.map +1 -0
- package/dist/supergraphManagers/LocalCompose/index.js +55 -0
- package/dist/supergraphManagers/LocalCompose/index.js.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/index.d.ts +32 -0
- package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/index.js +96 -0
- package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -0
- package/dist/{loadSupergraphSdlFromStorage.d.ts → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts} +1 -1
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -0
- package/dist/{loadSupergraphSdlFromStorage.js → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js} +1 -1
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -0
- package/dist/{outOfBandReporter.d.ts → supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts} +0 -0
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +1 -0
- package/dist/{outOfBandReporter.js → supergraphManagers/UplinkFetcher/outOfBandReporter.js} +2 -2
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js.map +1 -0
- package/dist/supergraphManagers/index.d.ts +5 -0
- package/dist/supergraphManagers/index.d.ts.map +1 -0
- package/dist/supergraphManagers/index.js +12 -0
- package/dist/supergraphManagers/index.js.map +1 -0
- package/dist/utilities/createHash.d.ts +2 -0
- package/dist/utilities/createHash.d.ts.map +1 -0
- package/dist/utilities/createHash.js +15 -0
- package/dist/utilities/createHash.js.map +1 -0
- package/dist/utilities/isNodeLike.d.ts +3 -0
- package/dist/utilities/isNodeLike.d.ts.map +1 -0
- package/dist/utilities/isNodeLike.js +8 -0
- package/dist/utilities/isNodeLike.js.map +1 -0
- package/package.json +10 -8
- package/src/__mocks__/make-fetch-happen-fetcher.ts +3 -1
- package/src/__tests__/executeQueryPlan.test.ts +636 -0
- package/src/__tests__/execution-utils.ts +2 -2
- package/src/__tests__/gateway/buildService.test.ts +3 -3
- package/src/__tests__/gateway/executor.test.ts +1 -1
- package/src/__tests__/gateway/lifecycle-hooks.test.ts +58 -99
- package/src/__tests__/gateway/opentelemetry.test.ts +8 -3
- package/src/__tests__/gateway/queryPlanCache.test.ts +25 -9
- package/src/__tests__/gateway/reporting.test.ts +33 -7
- package/src/__tests__/gateway/supergraphSdl.test.ts +394 -0
- package/src/__tests__/integration/aliases.test.ts +9 -3
- package/src/__tests__/integration/configuration.test.ts +109 -12
- package/src/__tests__/integration/logger.test.ts +1 -1
- package/src/__tests__/integration/networkRequests.test.ts +81 -118
- package/src/__tests__/integration/nockMocks.ts +15 -8
- package/src/config.ts +149 -40
- package/src/datasources/RemoteGraphQLDataSource.ts +8 -2
- package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +4 -4
- package/src/executeQueryPlan.ts +11 -1
- package/src/index.ts +314 -485
- package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +370 -0
- package/src/{__tests__ → supergraphManagers/IntrospectAndCompose/__tests__}/loadServicesFromRemoteEndpoint.test.ts +5 -5
- package/src/supergraphManagers/IntrospectAndCompose/__tests__/tsconfig.json +8 -0
- package/src/supergraphManagers/IntrospectAndCompose/index.ts +163 -0
- package/src/{loadServicesFromRemoteEndpoint.ts → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts} +6 -6
- package/src/supergraphManagers/LegacyFetcher/index.ts +229 -0
- package/src/supergraphManagers/LocalCompose/index.ts +83 -0
- package/src/{__tests__ → supergraphManagers/UplinkFetcher/__tests__}/loadSupergraphSdlFromStorage.test.ts +4 -4
- package/src/supergraphManagers/UplinkFetcher/__tests__/tsconfig.json +8 -0
- package/src/supergraphManagers/UplinkFetcher/index.ts +128 -0
- package/src/{loadSupergraphSdlFromStorage.ts → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts} +3 -3
- package/src/{outOfBandReporter.ts → supergraphManagers/UplinkFetcher/outOfBandReporter.ts} +2 -2
- package/src/supergraphManagers/index.ts +4 -0
- package/src/utilities/createHash.ts +10 -0
- package/src/utilities/isNodeLike.ts +11 -0
- package/CHANGELOG.md +0 -466
- package/dist/loadServicesFromRemoteEndpoint.d.ts +0 -13
- package/dist/loadServicesFromRemoteEndpoint.d.ts.map +0 -1
- package/dist/loadServicesFromRemoteEndpoint.js.map +0 -1
- package/dist/loadSupergraphSdlFromStorage.d.ts.map +0 -1
- package/dist/loadSupergraphSdlFromStorage.js.map +0 -1
- package/dist/outOfBandReporter.d.ts.map +0 -1
- package/dist/outOfBandReporter.js.map +0 -1
- package/src/__tests__/gateway/composedSdl.test.ts +0 -44
|
@@ -1636,4 +1636,640 @@ describe('executeQueryPlan', () => {
|
|
|
1636
1636
|
});
|
|
1637
1637
|
});
|
|
1638
1638
|
});
|
|
1639
|
+
|
|
1640
|
+
describe('@requires', () => {
|
|
1641
|
+
test('handles null in required field correctly (with nullable fields)', async () => {
|
|
1642
|
+
const s1 = {
|
|
1643
|
+
name: 'S1',
|
|
1644
|
+
typeDefs: gql`
|
|
1645
|
+
type T1 @key(fields: "id") {
|
|
1646
|
+
id: Int!
|
|
1647
|
+
f1: String
|
|
1648
|
+
}
|
|
1649
|
+
`
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
const s2 = {
|
|
1653
|
+
name: 'S2',
|
|
1654
|
+
typeDefs: gql`
|
|
1655
|
+
type Query {
|
|
1656
|
+
getT1s: [T1]
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
extend type T1 @key(fields: "id") {
|
|
1660
|
+
id: Int! @external
|
|
1661
|
+
f1: String @external
|
|
1662
|
+
f2: T2 @requires(fields: "f1")
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
type T2 {
|
|
1666
|
+
a: String
|
|
1667
|
+
}
|
|
1668
|
+
`
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2 ]);
|
|
1672
|
+
|
|
1673
|
+
const s1_data = [
|
|
1674
|
+
{ id: 0, f1: "foo" },
|
|
1675
|
+
{ id: 1, f1: null },
|
|
1676
|
+
{ id: 2, f1: "bar" },
|
|
1677
|
+
];
|
|
1678
|
+
|
|
1679
|
+
addResolversToSchema(serviceMap['S1'].schema, {
|
|
1680
|
+
T1: {
|
|
1681
|
+
__resolveReference(ref) {
|
|
1682
|
+
return s1_data[ref.id];
|
|
1683
|
+
},
|
|
1684
|
+
},
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
addResolversToSchema(serviceMap['S2'].schema, {
|
|
1688
|
+
Query: {
|
|
1689
|
+
getT1s() {
|
|
1690
|
+
return [{id: 0}, {id: 1}, {id: 2}];
|
|
1691
|
+
},
|
|
1692
|
+
},
|
|
1693
|
+
T1: {
|
|
1694
|
+
__resolveReference(ref) {
|
|
1695
|
+
// the ref has already the id and f1 is a require is triggered, and we resolve f2 below
|
|
1696
|
+
return ref;
|
|
1697
|
+
},
|
|
1698
|
+
f2(o) {
|
|
1699
|
+
return o.f1 === null ? null : { a: `t1:${o.f1}` };
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
});
|
|
1703
|
+
|
|
1704
|
+
const operationDocument = gql`
|
|
1705
|
+
query {
|
|
1706
|
+
getT1s {
|
|
1707
|
+
id
|
|
1708
|
+
f1
|
|
1709
|
+
f2 {
|
|
1710
|
+
a
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
`;
|
|
1715
|
+
|
|
1716
|
+
const operationContext = buildOperationContext({
|
|
1717
|
+
schema,
|
|
1718
|
+
operationDocument,
|
|
1719
|
+
});
|
|
1720
|
+
|
|
1721
|
+
const queryPlan = queryPlanner.buildQueryPlan(operationContext);
|
|
1722
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
1723
|
+
QueryPlan {
|
|
1724
|
+
Sequence {
|
|
1725
|
+
Fetch(service: "S2") {
|
|
1726
|
+
{
|
|
1727
|
+
getT1s {
|
|
1728
|
+
id
|
|
1729
|
+
__typename
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
},
|
|
1733
|
+
Flatten(path: "getT1s.@") {
|
|
1734
|
+
Fetch(service: "S1") {
|
|
1735
|
+
{
|
|
1736
|
+
... on T1 {
|
|
1737
|
+
__typename
|
|
1738
|
+
id
|
|
1739
|
+
}
|
|
1740
|
+
} =>
|
|
1741
|
+
{
|
|
1742
|
+
... on T1 {
|
|
1743
|
+
f1
|
|
1744
|
+
__typename
|
|
1745
|
+
id
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
},
|
|
1749
|
+
},
|
|
1750
|
+
Flatten(path: "getT1s.@") {
|
|
1751
|
+
Fetch(service: "S2") {
|
|
1752
|
+
{
|
|
1753
|
+
... on T1 {
|
|
1754
|
+
__typename
|
|
1755
|
+
id
|
|
1756
|
+
f1
|
|
1757
|
+
}
|
|
1758
|
+
} =>
|
|
1759
|
+
{
|
|
1760
|
+
... on T1 {
|
|
1761
|
+
f2 {
|
|
1762
|
+
a
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
},
|
|
1767
|
+
},
|
|
1768
|
+
},
|
|
1769
|
+
}
|
|
1770
|
+
`);
|
|
1771
|
+
const response = await executeQueryPlan(queryPlan, serviceMap, buildRequestContext(), operationContext);
|
|
1772
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
1773
|
+
Object {
|
|
1774
|
+
"getT1s": Array [
|
|
1775
|
+
Object {
|
|
1776
|
+
"f1": "foo",
|
|
1777
|
+
"f2": Object {
|
|
1778
|
+
"a": "t1:foo",
|
|
1779
|
+
},
|
|
1780
|
+
"id": 0,
|
|
1781
|
+
},
|
|
1782
|
+
Object {
|
|
1783
|
+
"f1": null,
|
|
1784
|
+
"f2": null,
|
|
1785
|
+
"id": 1,
|
|
1786
|
+
},
|
|
1787
|
+
Object {
|
|
1788
|
+
"f1": "bar",
|
|
1789
|
+
"f2": Object {
|
|
1790
|
+
"a": "t1:bar",
|
|
1791
|
+
},
|
|
1792
|
+
"id": 2,
|
|
1793
|
+
},
|
|
1794
|
+
],
|
|
1795
|
+
}
|
|
1796
|
+
`);
|
|
1797
|
+
expect(response.errors).toBeUndefined();
|
|
1798
|
+
});
|
|
1799
|
+
|
|
1800
|
+
test('handles null in required field correctly (with @require field non-nullable)', async () => {
|
|
1801
|
+
const s1 = {
|
|
1802
|
+
name: 'S1',
|
|
1803
|
+
typeDefs: gql`
|
|
1804
|
+
type T1 @key(fields: "id") {
|
|
1805
|
+
id: Int!
|
|
1806
|
+
f1: String
|
|
1807
|
+
}
|
|
1808
|
+
`
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
const s2 = {
|
|
1812
|
+
name: 'S2',
|
|
1813
|
+
typeDefs: gql`
|
|
1814
|
+
type Query {
|
|
1815
|
+
getT1s: [T1]
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
extend type T1 @key(fields: "id") {
|
|
1819
|
+
id: Int! @external
|
|
1820
|
+
f1: String @external
|
|
1821
|
+
f2: T2! @requires(fields: "f1")
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
type T2 {
|
|
1825
|
+
a: String
|
|
1826
|
+
}
|
|
1827
|
+
`
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2 ]);
|
|
1831
|
+
|
|
1832
|
+
const s1_data = [
|
|
1833
|
+
{ id: 0, f1: "foo" },
|
|
1834
|
+
{ id: 1, f1: null },
|
|
1835
|
+
{ id: 2, f1: "bar" },
|
|
1836
|
+
];
|
|
1837
|
+
|
|
1838
|
+
addResolversToSchema(serviceMap['S1'].schema, {
|
|
1839
|
+
T1: {
|
|
1840
|
+
__resolveReference(ref) {
|
|
1841
|
+
return s1_data[ref.id];
|
|
1842
|
+
},
|
|
1843
|
+
},
|
|
1844
|
+
});
|
|
1845
|
+
|
|
1846
|
+
addResolversToSchema(serviceMap['S2'].schema, {
|
|
1847
|
+
Query: {
|
|
1848
|
+
getT1s() {
|
|
1849
|
+
return [{id: 0}, {id: 1}, {id: 2}];
|
|
1850
|
+
},
|
|
1851
|
+
},
|
|
1852
|
+
T1: {
|
|
1853
|
+
__resolveReference(ref) {
|
|
1854
|
+
// the ref has already the id and f1 is a require is triggered, and we resolve f2 below
|
|
1855
|
+
return ref;
|
|
1856
|
+
},
|
|
1857
|
+
f2(o) {
|
|
1858
|
+
return o.f1 === null ? null : { a: `t1:${o.f1}` };
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
});
|
|
1862
|
+
|
|
1863
|
+
const operationDocument = gql`
|
|
1864
|
+
query {
|
|
1865
|
+
getT1s {
|
|
1866
|
+
id
|
|
1867
|
+
f1
|
|
1868
|
+
f2 {
|
|
1869
|
+
a
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
`;
|
|
1874
|
+
|
|
1875
|
+
const operationContext = buildOperationContext({
|
|
1876
|
+
schema,
|
|
1877
|
+
operationDocument,
|
|
1878
|
+
});
|
|
1879
|
+
|
|
1880
|
+
const queryPlan = queryPlanner.buildQueryPlan(operationContext);
|
|
1881
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
1882
|
+
QueryPlan {
|
|
1883
|
+
Sequence {
|
|
1884
|
+
Fetch(service: "S2") {
|
|
1885
|
+
{
|
|
1886
|
+
getT1s {
|
|
1887
|
+
id
|
|
1888
|
+
__typename
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
},
|
|
1892
|
+
Flatten(path: "getT1s.@") {
|
|
1893
|
+
Fetch(service: "S1") {
|
|
1894
|
+
{
|
|
1895
|
+
... on T1 {
|
|
1896
|
+
__typename
|
|
1897
|
+
id
|
|
1898
|
+
}
|
|
1899
|
+
} =>
|
|
1900
|
+
{
|
|
1901
|
+
... on T1 {
|
|
1902
|
+
f1
|
|
1903
|
+
__typename
|
|
1904
|
+
id
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
},
|
|
1908
|
+
},
|
|
1909
|
+
Flatten(path: "getT1s.@") {
|
|
1910
|
+
Fetch(service: "S2") {
|
|
1911
|
+
{
|
|
1912
|
+
... on T1 {
|
|
1913
|
+
__typename
|
|
1914
|
+
id
|
|
1915
|
+
f1
|
|
1916
|
+
}
|
|
1917
|
+
} =>
|
|
1918
|
+
{
|
|
1919
|
+
... on T1 {
|
|
1920
|
+
f2 {
|
|
1921
|
+
a
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
},
|
|
1926
|
+
},
|
|
1927
|
+
},
|
|
1928
|
+
}
|
|
1929
|
+
`);
|
|
1930
|
+
|
|
1931
|
+
const response = await executeQueryPlan(queryPlan, serviceMap, buildRequestContext(), operationContext);
|
|
1932
|
+
// `null` should bubble up since `f2` is now non-nullable. But we should still get the `id: 0` response.
|
|
1933
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
1934
|
+
Object {
|
|
1935
|
+
"getT1s": Array [
|
|
1936
|
+
Object {
|
|
1937
|
+
"f1": "foo",
|
|
1938
|
+
"f2": Object {
|
|
1939
|
+
"a": "t1:foo",
|
|
1940
|
+
},
|
|
1941
|
+
"id": 0,
|
|
1942
|
+
},
|
|
1943
|
+
null,
|
|
1944
|
+
Object {
|
|
1945
|
+
"f1": "bar",
|
|
1946
|
+
"f2": Object {
|
|
1947
|
+
"a": "t1:bar",
|
|
1948
|
+
},
|
|
1949
|
+
"id": 2,
|
|
1950
|
+
},
|
|
1951
|
+
],
|
|
1952
|
+
}
|
|
1953
|
+
`);
|
|
1954
|
+
|
|
1955
|
+
// We returning `null` for f2 which isn't nullable, so it bubbled up and we should have an error
|
|
1956
|
+
expect(response.errors?.map((e) => e.message)).toStrictEqual(['Cannot return null for non-nullable field T1.f2.']);
|
|
1957
|
+
});
|
|
1958
|
+
|
|
1959
|
+
test('handles null in required field correctly (with non-nullable required field)', async () => {
|
|
1960
|
+
const s1 = {
|
|
1961
|
+
name: 'S1',
|
|
1962
|
+
typeDefs: gql`
|
|
1963
|
+
type T1 @key(fields: "id") {
|
|
1964
|
+
id: Int!
|
|
1965
|
+
f1: String!
|
|
1966
|
+
}
|
|
1967
|
+
`
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
const s2 = {
|
|
1971
|
+
name: 'S2',
|
|
1972
|
+
typeDefs: gql`
|
|
1973
|
+
type Query {
|
|
1974
|
+
getT1s: [T1]
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
extend type T1 @key(fields: "id") {
|
|
1978
|
+
id: Int! @external
|
|
1979
|
+
f1: String! @external
|
|
1980
|
+
f2: T2 @requires(fields: "f1")
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
type T2 {
|
|
1984
|
+
a: String
|
|
1985
|
+
}
|
|
1986
|
+
`
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2 ]);
|
|
1990
|
+
|
|
1991
|
+
const s1_data = [
|
|
1992
|
+
{ id: 0, f1: "foo" },
|
|
1993
|
+
{ id: 1, f1: null },
|
|
1994
|
+
{ id: 2, f1: "bar" },
|
|
1995
|
+
];
|
|
1996
|
+
|
|
1997
|
+
addResolversToSchema(serviceMap['S1'].schema, {
|
|
1998
|
+
T1: {
|
|
1999
|
+
__resolveReference(ref) {
|
|
2000
|
+
return s1_data[ref.id];
|
|
2001
|
+
},
|
|
2002
|
+
},
|
|
2003
|
+
});
|
|
2004
|
+
|
|
2005
|
+
addResolversToSchema(serviceMap['S2'].schema, {
|
|
2006
|
+
Query: {
|
|
2007
|
+
getT1s() {
|
|
2008
|
+
return [{id: 0}, {id: 1}, {id: 2}];
|
|
2009
|
+
},
|
|
2010
|
+
},
|
|
2011
|
+
T1: {
|
|
2012
|
+
__resolveReference(ref) {
|
|
2013
|
+
// the ref has already the id and f1 is a require is triggered, and we resolve f2 below
|
|
2014
|
+
return ref;
|
|
2015
|
+
},
|
|
2016
|
+
f2(o) {
|
|
2017
|
+
return o.f1 === null ? null : { a: `t1:${o.f1}` };
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
});
|
|
2021
|
+
|
|
2022
|
+
const operationDocument = gql`
|
|
2023
|
+
query {
|
|
2024
|
+
getT1s {
|
|
2025
|
+
id
|
|
2026
|
+
f1
|
|
2027
|
+
f2 {
|
|
2028
|
+
a
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
`;
|
|
2033
|
+
const operationContext = buildOperationContext({
|
|
2034
|
+
schema,
|
|
2035
|
+
operationDocument,
|
|
2036
|
+
});
|
|
2037
|
+
|
|
2038
|
+
const queryPlan = queryPlanner.buildQueryPlan(operationContext);
|
|
2039
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
2040
|
+
QueryPlan {
|
|
2041
|
+
Sequence {
|
|
2042
|
+
Fetch(service: "S2") {
|
|
2043
|
+
{
|
|
2044
|
+
getT1s {
|
|
2045
|
+
id
|
|
2046
|
+
__typename
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
},
|
|
2050
|
+
Flatten(path: "getT1s.@") {
|
|
2051
|
+
Fetch(service: "S1") {
|
|
2052
|
+
{
|
|
2053
|
+
... on T1 {
|
|
2054
|
+
__typename
|
|
2055
|
+
id
|
|
2056
|
+
}
|
|
2057
|
+
} =>
|
|
2058
|
+
{
|
|
2059
|
+
... on T1 {
|
|
2060
|
+
f1
|
|
2061
|
+
__typename
|
|
2062
|
+
id
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
},
|
|
2066
|
+
},
|
|
2067
|
+
Flatten(path: "getT1s.@") {
|
|
2068
|
+
Fetch(service: "S2") {
|
|
2069
|
+
{
|
|
2070
|
+
... on T1 {
|
|
2071
|
+
__typename
|
|
2072
|
+
id
|
|
2073
|
+
f1
|
|
2074
|
+
}
|
|
2075
|
+
} =>
|
|
2076
|
+
{
|
|
2077
|
+
... on T1 {
|
|
2078
|
+
f2 {
|
|
2079
|
+
a
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
},
|
|
2084
|
+
},
|
|
2085
|
+
},
|
|
2086
|
+
}
|
|
2087
|
+
`);
|
|
2088
|
+
|
|
2089
|
+
const response = await executeQueryPlan(queryPlan, serviceMap, buildRequestContext(), operationContext);
|
|
2090
|
+
// `null` should bubble up since `f2` is now non-nullable. But we should still get the `id: 0` response.
|
|
2091
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
2092
|
+
Object {
|
|
2093
|
+
"getT1s": Array [
|
|
2094
|
+
Object {
|
|
2095
|
+
"f1": "foo",
|
|
2096
|
+
"f2": Object {
|
|
2097
|
+
"a": "t1:foo",
|
|
2098
|
+
},
|
|
2099
|
+
"id": 0,
|
|
2100
|
+
},
|
|
2101
|
+
null,
|
|
2102
|
+
Object {
|
|
2103
|
+
"f1": "bar",
|
|
2104
|
+
"f2": Object {
|
|
2105
|
+
"a": "t1:bar",
|
|
2106
|
+
},
|
|
2107
|
+
"id": 2,
|
|
2108
|
+
},
|
|
2109
|
+
],
|
|
2110
|
+
}
|
|
2111
|
+
`);
|
|
2112
|
+
expect(response.errors?.map((e) => e.message)).toStrictEqual(['Cannot return null for non-nullable field T1.f1.']);
|
|
2113
|
+
});
|
|
2114
|
+
|
|
2115
|
+
test('handles errors in required field correctly (with nullable fields)', async () => {
|
|
2116
|
+
const s1 = {
|
|
2117
|
+
name: 'S1',
|
|
2118
|
+
typeDefs: gql`
|
|
2119
|
+
type T1 @key(fields: "id") {
|
|
2120
|
+
id: Int!
|
|
2121
|
+
f1: String
|
|
2122
|
+
}
|
|
2123
|
+
`
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
const s2 = {
|
|
2127
|
+
name: 'S2',
|
|
2128
|
+
typeDefs: gql`
|
|
2129
|
+
type Query {
|
|
2130
|
+
getT1s: [T1]
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
extend type T1 @key(fields: "id") {
|
|
2134
|
+
id: Int! @external
|
|
2135
|
+
f1: String @external
|
|
2136
|
+
f2: T2 @requires(fields: "f1")
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
type T2 {
|
|
2140
|
+
a: String
|
|
2141
|
+
}
|
|
2142
|
+
`
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2 ]);
|
|
2146
|
+
|
|
2147
|
+
addResolversToSchema(serviceMap['S1'].schema, {
|
|
2148
|
+
T1: {
|
|
2149
|
+
__resolveReference(ref) {
|
|
2150
|
+
return ref;
|
|
2151
|
+
},
|
|
2152
|
+
f1(o) {
|
|
2153
|
+
switch (o.id) {
|
|
2154
|
+
case 0: return "foo";
|
|
2155
|
+
case 1: return [ "invalid" ]; // This will effectively throw
|
|
2156
|
+
case 2: return "bar";
|
|
2157
|
+
default: throw new Error('Not handled');
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
},
|
|
2161
|
+
});
|
|
2162
|
+
|
|
2163
|
+
addResolversToSchema(serviceMap['S2'].schema, {
|
|
2164
|
+
Query: {
|
|
2165
|
+
getT1s() {
|
|
2166
|
+
return [{id: 0}, {id: 1}, {id: 2}];
|
|
2167
|
+
},
|
|
2168
|
+
},
|
|
2169
|
+
T1: {
|
|
2170
|
+
__resolveReference(ref) {
|
|
2171
|
+
// the ref has already the id and f1 is a require is triggered, and we resolve f2 below
|
|
2172
|
+
return ref;
|
|
2173
|
+
},
|
|
2174
|
+
f2(o) {
|
|
2175
|
+
return o.f1 === null ? null : { a: `t1:${o.f1}` };
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
});
|
|
2179
|
+
|
|
2180
|
+
const operationDocument = gql`
|
|
2181
|
+
query {
|
|
2182
|
+
getT1s {
|
|
2183
|
+
id
|
|
2184
|
+
f1
|
|
2185
|
+
f2 {
|
|
2186
|
+
a
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
`;
|
|
2191
|
+
const operationContext = buildOperationContext({
|
|
2192
|
+
schema,
|
|
2193
|
+
operationDocument,
|
|
2194
|
+
});
|
|
2195
|
+
|
|
2196
|
+
const queryPlan = queryPlanner.buildQueryPlan(operationContext);
|
|
2197
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
2198
|
+
QueryPlan {
|
|
2199
|
+
Sequence {
|
|
2200
|
+
Fetch(service: "S2") {
|
|
2201
|
+
{
|
|
2202
|
+
getT1s {
|
|
2203
|
+
id
|
|
2204
|
+
__typename
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
},
|
|
2208
|
+
Flatten(path: "getT1s.@") {
|
|
2209
|
+
Fetch(service: "S1") {
|
|
2210
|
+
{
|
|
2211
|
+
... on T1 {
|
|
2212
|
+
__typename
|
|
2213
|
+
id
|
|
2214
|
+
}
|
|
2215
|
+
} =>
|
|
2216
|
+
{
|
|
2217
|
+
... on T1 {
|
|
2218
|
+
f1
|
|
2219
|
+
__typename
|
|
2220
|
+
id
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
},
|
|
2224
|
+
},
|
|
2225
|
+
Flatten(path: "getT1s.@") {
|
|
2226
|
+
Fetch(service: "S2") {
|
|
2227
|
+
{
|
|
2228
|
+
... on T1 {
|
|
2229
|
+
__typename
|
|
2230
|
+
id
|
|
2231
|
+
f1
|
|
2232
|
+
}
|
|
2233
|
+
} =>
|
|
2234
|
+
{
|
|
2235
|
+
... on T1 {
|
|
2236
|
+
f2 {
|
|
2237
|
+
a
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
},
|
|
2242
|
+
},
|
|
2243
|
+
},
|
|
2244
|
+
}
|
|
2245
|
+
`);
|
|
2246
|
+
const response = await executeQueryPlan(queryPlan, serviceMap, buildRequestContext(), operationContext);
|
|
2247
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
2248
|
+
Object {
|
|
2249
|
+
"getT1s": Array [
|
|
2250
|
+
Object {
|
|
2251
|
+
"f1": "foo",
|
|
2252
|
+
"f2": Object {
|
|
2253
|
+
"a": "t1:foo",
|
|
2254
|
+
},
|
|
2255
|
+
"id": 0,
|
|
2256
|
+
},
|
|
2257
|
+
Object {
|
|
2258
|
+
"f1": null,
|
|
2259
|
+
"f2": null,
|
|
2260
|
+
"id": 1,
|
|
2261
|
+
},
|
|
2262
|
+
Object {
|
|
2263
|
+
"f1": "bar",
|
|
2264
|
+
"f2": Object {
|
|
2265
|
+
"a": "t1:bar",
|
|
2266
|
+
},
|
|
2267
|
+
"id": 2,
|
|
2268
|
+
},
|
|
2269
|
+
],
|
|
2270
|
+
}
|
|
2271
|
+
`);
|
|
2272
|
+
expect(response.errors?.map((e) => e.message)).toStrictEqual(['String cannot represent value: ["invalid"]']);
|
|
2273
|
+
});
|
|
2274
|
+
});
|
|
1639
2275
|
});
|
|
@@ -117,8 +117,8 @@ export function getTestingSupergraphSdl(services: typeof fixtures = fixtures) {
|
|
|
117
117
|
);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
export function wait(ms: number) {
|
|
121
|
-
return new Promise(r => setTimeout(r, ms));
|
|
120
|
+
export function wait(ms: number, toResolveTo?: any) {
|
|
121
|
+
return new Promise((r) => setTimeout(() => r(toResolveTo), ms));
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
export function printPlan(queryPlan: QueryPlan): string {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fetch } from '../../__mocks__/
|
|
1
|
+
import { fetch } from '../../__mocks__/make-fetch-happen-fetcher';
|
|
2
2
|
import { ApolloServerBase as ApolloServer } from 'apollo-server-core';
|
|
3
3
|
|
|
4
4
|
import { RemoteGraphQLDataSource } from '../../datasources/RemoteGraphQLDataSource';
|
|
@@ -218,7 +218,7 @@ it('does not share service definition cache between gateways', async () => {
|
|
|
218
218
|
url: 'https://api.example.com/repeat',
|
|
219
219
|
},
|
|
220
220
|
],
|
|
221
|
-
|
|
221
|
+
experimental_didUpdateSupergraph: updateObserver,
|
|
222
222
|
});
|
|
223
223
|
|
|
224
224
|
await gateway.load();
|
|
@@ -237,7 +237,7 @@ it('does not share service definition cache between gateways', async () => {
|
|
|
237
237
|
url: 'https://api.example.com/repeat',
|
|
238
238
|
},
|
|
239
239
|
],
|
|
240
|
-
|
|
240
|
+
experimental_didUpdateSupergraph: updateObserver,
|
|
241
241
|
});
|
|
242
242
|
|
|
243
243
|
await gateway.load();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { fetch } from '../../__mocks__/make-fetch-happen-fetcher';
|
|
1
2
|
import gql from 'graphql-tag';
|
|
2
3
|
import { ApolloGateway } from '../../';
|
|
3
4
|
import { fixtures } from 'apollo-federation-integration-testsuite';
|
|
4
5
|
import { Logger } from 'apollo-server-types';
|
|
5
|
-
import { fetch } from '../../__mocks__/apollo-server-env';
|
|
6
6
|
|
|
7
7
|
let logger: {
|
|
8
8
|
warn: jest.MockedFunction<Logger['warn']>,
|