@apollo/federation-internals 2.0.0-preview.8 → 2.0.1
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/CHANGELOG.md +35 -0
- package/dist/buildSchema.js +1 -1
- package/dist/buildSchema.js.map +1 -1
- package/dist/coreSpec.d.ts +13 -6
- package/dist/coreSpec.d.ts.map +1 -1
- package/dist/coreSpec.js +105 -38
- package/dist/coreSpec.js.map +1 -1
- package/dist/definitions.d.ts +25 -11
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +115 -63
- package/dist/definitions.js.map +1 -1
- package/dist/directiveAndTypeSpecification.d.ts +11 -1
- package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
- package/dist/directiveAndTypeSpecification.js +77 -20
- package/dist/directiveAndTypeSpecification.js.map +1 -1
- package/dist/error.d.ts +13 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +28 -2
- package/dist/error.js.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +7 -1
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +19 -6
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +107 -80
- package/dist/federation.js.map +1 -1
- package/dist/federationSpec.d.ts +3 -3
- package/dist/federationSpec.d.ts.map +1 -1
- package/dist/federationSpec.js +34 -26
- package/dist/federationSpec.js.map +1 -1
- package/dist/inaccessibleSpec.d.ts +11 -5
- package/dist/inaccessibleSpec.d.ts.map +1 -1
- package/dist/inaccessibleSpec.js +631 -29
- package/dist/inaccessibleSpec.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/introspection.d.ts.map +1 -1
- package/dist/introspection.js +8 -3
- package/dist/introspection.js.map +1 -1
- package/dist/joinSpec.d.ts +6 -2
- package/dist/joinSpec.d.ts.map +1 -1
- package/dist/joinSpec.js +6 -0
- package/dist/joinSpec.js.map +1 -1
- package/dist/operations.d.ts +1 -0
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +16 -1
- package/dist/operations.js.map +1 -1
- package/dist/{sharing.d.ts → precompute.d.ts} +1 -1
- package/dist/precompute.d.ts.map +1 -0
- package/dist/{sharing.js → precompute.js} +3 -3
- package/dist/precompute.js.map +1 -0
- package/dist/schemaUpgrader.d.ts.map +1 -1
- package/dist/schemaUpgrader.js +17 -7
- package/dist/schemaUpgrader.js.map +1 -1
- package/dist/suggestions.d.ts +1 -1
- package/dist/suggestions.d.ts.map +1 -1
- package/dist/suggestions.js.map +1 -1
- package/dist/supergraphs.d.ts.map +1 -1
- package/dist/supergraphs.js +2 -0
- package/dist/supergraphs.js.map +1 -1
- package/dist/tagSpec.d.ts +6 -2
- package/dist/tagSpec.d.ts.map +1 -1
- package/dist/tagSpec.js +30 -14
- package/dist/tagSpec.js.map +1 -1
- package/dist/validate.js +13 -7
- package/dist/validate.js.map +1 -1
- package/dist/values.d.ts +2 -2
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +13 -11
- package/dist/values.js.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/coreSpec.test.ts +112 -0
- package/src/__tests__/removeInaccessibleElements.test.ts +2229 -137
- package/src/__tests__/subgraphValidation.test.ts +357 -1
- package/src/__tests__/values.test.ts +315 -3
- package/src/buildSchema.ts +1 -1
- package/src/coreSpec.ts +161 -48
- package/src/definitions.ts +223 -90
- package/src/directiveAndTypeSpecification.ts +98 -21
- package/src/error.ts +80 -2
- package/src/extractSubgraphsFromSupergraph.ts +7 -1
- package/src/federation.ts +124 -97
- package/src/federationSpec.ts +37 -30
- package/src/inaccessibleSpec.ts +983 -49
- package/src/index.ts +1 -0
- package/src/introspection.ts +8 -3
- package/src/joinSpec.ts +19 -4
- package/src/operations.ts +15 -0
- package/src/{sharing.ts → precompute.ts} +3 -6
- package/src/schemaUpgrader.ts +29 -13
- package/src/suggestions.ts +1 -1
- package/src/supergraphs.ts +2 -0
- package/src/tagSpec.ts +42 -16
- package/src/validate.ts +20 -9
- package/src/values.ts +39 -12
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/sharing.d.ts.map +0 -1
- package/dist/sharing.js.map +0 -1
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { DocumentNode } from 'graphql';
|
|
2
2
|
import gql from 'graphql-tag';
|
|
3
|
+
import { Subgraph } from '..';
|
|
3
4
|
import { errorCauses } from '../definitions';
|
|
4
5
|
import { asFed2SubgraphDocument, buildSubgraph } from "../federation"
|
|
6
|
+
import { defaultPrintOptions, printSchema } from '../print';
|
|
7
|
+
import './matchers';
|
|
5
8
|
|
|
6
9
|
// Builds the provided subgraph (using name 'S' for the subgraph) and, if the
|
|
7
10
|
// subgraph is invalid/has errors, return those errors as a list of [code, message].
|
|
8
11
|
// If the subgraph is valid, return undefined.
|
|
9
|
-
function buildForErrors(
|
|
12
|
+
export function buildForErrors(
|
|
10
13
|
subgraphDefs: DocumentNode,
|
|
11
14
|
options?: {
|
|
12
15
|
subgraphName?: string,
|
|
@@ -523,3 +526,356 @@ describe('custom error message for misnamed directives', () => {
|
|
|
523
526
|
]);
|
|
524
527
|
});
|
|
525
528
|
});
|
|
529
|
+
|
|
530
|
+
function buildAndValidate(doc: DocumentNode): Subgraph {
|
|
531
|
+
const name = 'S';
|
|
532
|
+
return buildSubgraph(name, `http://${name}`, doc).validate();
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
describe('@core/@link handling', () => {
|
|
536
|
+
const expectedFullSchema = `
|
|
537
|
+
schema
|
|
538
|
+
@link(url: "https://specs.apollo.dev/link/v1.0")
|
|
539
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
540
|
+
{
|
|
541
|
+
query: Query
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
|
|
545
|
+
|
|
546
|
+
directive @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
|
|
547
|
+
|
|
548
|
+
directive @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION
|
|
549
|
+
|
|
550
|
+
directive @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION
|
|
551
|
+
|
|
552
|
+
directive @federation__external(reason: String) on OBJECT | FIELD_DEFINITION
|
|
553
|
+
|
|
554
|
+
directive @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
|
|
555
|
+
|
|
556
|
+
directive @federation__extends on OBJECT | INTERFACE
|
|
557
|
+
|
|
558
|
+
directive @federation__shareable on OBJECT | FIELD_DEFINITION
|
|
559
|
+
|
|
560
|
+
directive @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
|
|
561
|
+
|
|
562
|
+
directive @federation__override(from: String!) on FIELD_DEFINITION
|
|
563
|
+
|
|
564
|
+
type T
|
|
565
|
+
@key(fields: "k")
|
|
566
|
+
{
|
|
567
|
+
k: ID!
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
enum link__Purpose {
|
|
571
|
+
"""
|
|
572
|
+
\`SECURITY\` features provide metadata necessary to securely resolve fields.
|
|
573
|
+
"""
|
|
574
|
+
SECURITY
|
|
575
|
+
|
|
576
|
+
"""
|
|
577
|
+
\`EXECUTION\` features provide metadata necessary for operation execution.
|
|
578
|
+
"""
|
|
579
|
+
EXECUTION
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
scalar link__Import
|
|
583
|
+
|
|
584
|
+
scalar federation__FieldSet
|
|
585
|
+
|
|
586
|
+
scalar _Any
|
|
587
|
+
|
|
588
|
+
type _Service {
|
|
589
|
+
sdl: String
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
union _Entity = T
|
|
593
|
+
|
|
594
|
+
type Query {
|
|
595
|
+
_entities(representations: [_Any!]!): [_Entity]!
|
|
596
|
+
_service: _Service!
|
|
597
|
+
}
|
|
598
|
+
`
|
|
599
|
+
const validateFullSchema = (subgraph: Subgraph) => {
|
|
600
|
+
// Note: we merge types and extensions to avoid having to care whether the @link are on a schema definition or schema extension
|
|
601
|
+
// as 1) this will vary (we add them to extensions in our test, but when auto-added, they are added to the schema definition)
|
|
602
|
+
// and 2) it doesn't matter in practice, it's valid in all cases.
|
|
603
|
+
expect(printSchema(subgraph.schema, { ...defaultPrintOptions, mergeTypesAndExtensions: true})).toMatchString(expectedFullSchema);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
it('expands everything if only the federation spec is linked', () => {
|
|
607
|
+
const doc = gql`
|
|
608
|
+
extend schema
|
|
609
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
610
|
+
|
|
611
|
+
type T @key(fields: "k") {
|
|
612
|
+
k: ID!
|
|
613
|
+
}
|
|
614
|
+
`;
|
|
615
|
+
|
|
616
|
+
validateFullSchema(buildAndValidate(doc));
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
it('expands definitions if both the federation spec and link spec are linked', () => {
|
|
620
|
+
const doc = gql`
|
|
621
|
+
extend schema
|
|
622
|
+
@link(url: "https://specs.apollo.dev/link/v1.0")
|
|
623
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
624
|
+
|
|
625
|
+
type T @key(fields: "k") {
|
|
626
|
+
k: ID!
|
|
627
|
+
}
|
|
628
|
+
`;
|
|
629
|
+
|
|
630
|
+
validateFullSchema(buildAndValidate(doc));
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it('is valid if a schema is complete from the get-go', () => {
|
|
634
|
+
validateFullSchema(buildAndValidate(gql(expectedFullSchema)));
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
it('expands missing definitions when some are partially provided', () => {
|
|
638
|
+
const docs = [
|
|
639
|
+
gql`
|
|
640
|
+
extend schema
|
|
641
|
+
@link(url: "https://specs.apollo.dev/link/v1.0")
|
|
642
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
643
|
+
|
|
644
|
+
type T @key(fields: "k") {
|
|
645
|
+
k: ID!
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
directive @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
|
|
649
|
+
|
|
650
|
+
scalar federation__FieldSet
|
|
651
|
+
|
|
652
|
+
scalar link__Import
|
|
653
|
+
`,
|
|
654
|
+
gql`
|
|
655
|
+
extend schema
|
|
656
|
+
@link(url: "https://specs.apollo.dev/link/v1.0")
|
|
657
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
658
|
+
|
|
659
|
+
type T @key(fields: "k") {
|
|
660
|
+
k: ID!
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
scalar link__Import
|
|
664
|
+
`,
|
|
665
|
+
gql`
|
|
666
|
+
extend schema
|
|
667
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
668
|
+
|
|
669
|
+
type T @key(fields: "k") {
|
|
670
|
+
k: ID!
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
scalar link__Import
|
|
674
|
+
`,
|
|
675
|
+
gql`
|
|
676
|
+
extend schema
|
|
677
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
678
|
+
|
|
679
|
+
type T @key(fields: "k") {
|
|
680
|
+
k: ID!
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
directive @federation__external(reason: String) on OBJECT | FIELD_DEFINITION
|
|
684
|
+
`,
|
|
685
|
+
];
|
|
686
|
+
|
|
687
|
+
// Note that we cannot use `validateFullSchema` as-is for those examples because the order or directive is going
|
|
688
|
+
// to be different. But that's ok, we mostly care that the validation doesn't throw since validation ultimately
|
|
689
|
+
// calls the graphQL-js validation, so we can be somewhat sure that if something necessary wasn't expanded
|
|
690
|
+
// properly, we would have an issue. The main reason we did validate the full schema in prior tests is
|
|
691
|
+
// so we had at least one full example of a subgraph expansion in the tests.
|
|
692
|
+
docs.forEach((doc) => buildAndValidate(doc));
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
it('allows known directives with incomplete but compatible definitions', () => {
|
|
696
|
+
const docs = [
|
|
697
|
+
// @key has a `resolvable` argument in its full definition, but it is optional.
|
|
698
|
+
gql`
|
|
699
|
+
extend schema
|
|
700
|
+
@link(url: "https://specs.apollo.dev/link/v1.0")
|
|
701
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
702
|
+
|
|
703
|
+
type T @key(fields: "k") {
|
|
704
|
+
k: ID!
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
directive @key(fields: federation__FieldSet!) repeatable on OBJECT | INTERFACE
|
|
708
|
+
|
|
709
|
+
scalar federation__FieldSet
|
|
710
|
+
`,
|
|
711
|
+
// @inacessible can be put in a bunch of locations, but you're welcome to restrict yourself to just fields.
|
|
712
|
+
gql`
|
|
713
|
+
extend schema
|
|
714
|
+
@link(url: "https://specs.apollo.dev/link/v1.0")
|
|
715
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@inaccessible"])
|
|
716
|
+
|
|
717
|
+
type T {
|
|
718
|
+
k: ID! @inaccessible
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
directive @inaccessible on FIELD_DEFINITION
|
|
722
|
+
`,
|
|
723
|
+
// @key is repeatable, but you're welcome to restrict yourself to never repeating it.
|
|
724
|
+
gql`
|
|
725
|
+
extend schema
|
|
726
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
727
|
+
|
|
728
|
+
type T @key(fields: "k") {
|
|
729
|
+
k: ID!
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
directive @key(fields: federation__FieldSet!, resolvable: Boolean = true) on OBJECT | INTERFACE
|
|
733
|
+
|
|
734
|
+
scalar federation__FieldSet
|
|
735
|
+
`,
|
|
736
|
+
// @key `resolvable` argument is optional, but you're welcome to force users to always provide it.
|
|
737
|
+
gql`
|
|
738
|
+
extend schema
|
|
739
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
740
|
+
|
|
741
|
+
type T @key(fields: "k", resolvable: true) {
|
|
742
|
+
k: ID!
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
directive @key(fields: federation__FieldSet!, resolvable: Boolean!) repeatable on OBJECT | INTERFACE
|
|
746
|
+
|
|
747
|
+
scalar federation__FieldSet
|
|
748
|
+
`,
|
|
749
|
+
// @link `url` argument is allowed to be `null` now, but it used not too, so making sure we still
|
|
750
|
+
// accept definition where it's mandatory.
|
|
751
|
+
gql`
|
|
752
|
+
extend schema
|
|
753
|
+
@link(url: "https://specs.apollo.dev/link/v1.0")
|
|
754
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
755
|
+
|
|
756
|
+
type T @key(fields: "k") {
|
|
757
|
+
k: ID!
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
directive @link(url: String!, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
|
|
761
|
+
|
|
762
|
+
scalar link__Import
|
|
763
|
+
scalar link__Purpose
|
|
764
|
+
`,
|
|
765
|
+
];
|
|
766
|
+
|
|
767
|
+
// Like above, we really only care that the examples validate.
|
|
768
|
+
docs.forEach((doc) => buildAndValidate(doc));
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
it('errors on invalid known directive location', () => {
|
|
772
|
+
const doc = gql`
|
|
773
|
+
extend schema
|
|
774
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
775
|
+
|
|
776
|
+
type T @key(fields: "k") {
|
|
777
|
+
k: ID!
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
directive @federation__external(reason: String) on OBJECT | FIELD_DEFINITION | SCHEMA
|
|
781
|
+
`;
|
|
782
|
+
|
|
783
|
+
// @external is not allowed on 'schema' and likely never will.
|
|
784
|
+
expect(buildForErrors(doc, { asFed2: false })).toStrictEqual([[
|
|
785
|
+
'DIRECTIVE_DEFINITION_INVALID',
|
|
786
|
+
'[S] Invalid definition for directive "@federation__external": "@federation__external" should have locations OBJECT, FIELD_DEFINITION, but found (non-subset) OBJECT, FIELD_DEFINITION, SCHEMA',
|
|
787
|
+
]]);
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
it('errors on invalid non-repeatable directive marked repeateable', () => {
|
|
791
|
+
const doc = gql`
|
|
792
|
+
extend schema
|
|
793
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
794
|
+
|
|
795
|
+
type T @key(fields: "k") {
|
|
796
|
+
k: ID!
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
directive @federation__external repeatable on OBJECT | FIELD_DEFINITION
|
|
800
|
+
`;
|
|
801
|
+
|
|
802
|
+
// @external is not repeatable (and has no reason to be since it has no arguments).
|
|
803
|
+
expect(buildForErrors(doc, { asFed2: false })).toStrictEqual([[
|
|
804
|
+
'DIRECTIVE_DEFINITION_INVALID',
|
|
805
|
+
'[S] Invalid definition for directive "@federation__external": "@federation__external" should not be repeatable',
|
|
806
|
+
]]);
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
it('errors on unknown argument of known directive', () => {
|
|
810
|
+
const doc = gql`
|
|
811
|
+
extend schema
|
|
812
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
813
|
+
|
|
814
|
+
type T @key(fields: "k") {
|
|
815
|
+
k: ID!
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
directive @federation__external(foo: Int) on OBJECT | FIELD_DEFINITION
|
|
819
|
+
`;
|
|
820
|
+
|
|
821
|
+
expect(buildForErrors(doc, { asFed2: false })).toStrictEqual([[
|
|
822
|
+
'DIRECTIVE_DEFINITION_INVALID',
|
|
823
|
+
'[S] Invalid definition for directive "@federation__external": unknown/unsupported argument "foo"',
|
|
824
|
+
]]);
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
it('errors on invalid type for a known argument', () => {
|
|
828
|
+
const doc = gql`
|
|
829
|
+
extend schema
|
|
830
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
831
|
+
|
|
832
|
+
type T @key(fields: "k") {
|
|
833
|
+
k: ID!
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
directive @key(fields: String!, resolvable: String) repeatable on OBJECT | INTERFACE
|
|
837
|
+
`;
|
|
838
|
+
|
|
839
|
+
expect(buildForErrors(doc, { asFed2: false })).toStrictEqual([[
|
|
840
|
+
'DIRECTIVE_DEFINITION_INVALID',
|
|
841
|
+
'[S] Invalid definition for directive "@key": argument "resolvable" should have type "Boolean" but found type "String"',
|
|
842
|
+
]]);
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
it('errors on a required argument defined as optional', () => {
|
|
846
|
+
const doc = gql`
|
|
847
|
+
extend schema
|
|
848
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
849
|
+
|
|
850
|
+
type T @key(fields: "k") {
|
|
851
|
+
k: ID!
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
directive @key(fields: federation__FieldSet, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
|
|
855
|
+
|
|
856
|
+
scalar federation__FieldSet
|
|
857
|
+
`;
|
|
858
|
+
|
|
859
|
+
expect(buildForErrors(doc, { asFed2: false })).toStrictEqual([[
|
|
860
|
+
'DIRECTIVE_DEFINITION_INVALID',
|
|
861
|
+
'[S] Invalid definition for directive "@key": argument "fields" should have type "federation__FieldSet!" but found type "federation__FieldSet"',
|
|
862
|
+
]]);
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
it('allows any (non-scalar) type in redefinition when expected type is a scalar', () => {
|
|
866
|
+
const doc = gql`
|
|
867
|
+
extend schema
|
|
868
|
+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
|
|
869
|
+
|
|
870
|
+
type T @key(fields: "k") {
|
|
871
|
+
k: ID!
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
# 'fields' should be of type 'federation_FieldSet!', but ensure we allow 'String!' alternatively.
|
|
875
|
+
directive @key(fields: String!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
|
|
876
|
+
`;
|
|
877
|
+
|
|
878
|
+
// Just making sure this don't error out.
|
|
879
|
+
buildAndValidate(doc);
|
|
880
|
+
});
|
|
881
|
+
});
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Schema,
|
|
3
|
-
} from '
|
|
4
|
-
import { buildSchema } from '
|
|
5
|
-
import { parseOperation } from '
|
|
3
|
+
} from '../definitions';
|
|
4
|
+
import { buildSchema } from '../buildSchema';
|
|
5
|
+
import { parseOperation } from '../operations';
|
|
6
|
+
import { buildForErrors } from './subgraphValidation.test';
|
|
7
|
+
import gql from 'graphql-tag';
|
|
8
|
+
import { printSchema } from '../print';
|
|
6
9
|
|
|
7
10
|
function parseSchema(schema: string): Schema {
|
|
8
11
|
try {
|
|
@@ -66,3 +69,312 @@ test('handles non-list value for list argument (as singleton)', () => {
|
|
|
66
69
|
}
|
|
67
70
|
`);
|
|
68
71
|
});
|
|
72
|
+
|
|
73
|
+
describe('default value validation', () => {
|
|
74
|
+
it('errors on invalid default value in field argument', () => {
|
|
75
|
+
const doc = gql`
|
|
76
|
+
type Query {
|
|
77
|
+
f(a: Int = "foo"): Int
|
|
78
|
+
}
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
82
|
+
'INVALID_GRAPHQL',
|
|
83
|
+
'[S] Invalid default value (got: "foo") provided for argument Query.f(a:) of type Int.'
|
|
84
|
+
]]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('errors on invalid default value in directive argument', () => {
|
|
88
|
+
const doc = gql`
|
|
89
|
+
type Query {
|
|
90
|
+
f: Int
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
directive @myDirective(a: Int = "foo") on FIELD
|
|
94
|
+
`;
|
|
95
|
+
|
|
96
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
97
|
+
'INVALID_GRAPHQL',
|
|
98
|
+
'[S] Invalid default value (got: "foo") provided for argument @myDirective(a:) of type Int.'
|
|
99
|
+
]]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('errors on invalid default value in input field', () => {
|
|
103
|
+
const doc = gql`
|
|
104
|
+
input I {
|
|
105
|
+
x: Int = "foo"
|
|
106
|
+
}
|
|
107
|
+
`;
|
|
108
|
+
|
|
109
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
110
|
+
'INVALID_GRAPHQL',
|
|
111
|
+
'[S] Invalid default value (got: "foo") provided for input field I.x of type Int.'
|
|
112
|
+
]]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('errors on invalid default value for existing input field', () => {
|
|
116
|
+
const doc = gql`
|
|
117
|
+
type Query {
|
|
118
|
+
f(i: I = { x: 2, y: "3" }): Int
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
input I {
|
|
122
|
+
x: Int
|
|
123
|
+
y: Int
|
|
124
|
+
}
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
128
|
+
'INVALID_GRAPHQL',
|
|
129
|
+
'[S] Invalid default value (got: {x: 2, y: "3"}) provided for argument Query.f(i:) of type I.'
|
|
130
|
+
]]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('errors on default value containing unexpected input fields', () => {
|
|
134
|
+
const doc = gql`
|
|
135
|
+
type Query {
|
|
136
|
+
f(i: I = { x: 1, y: 2, z: 3 }): Int
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
input I {
|
|
140
|
+
x: Int
|
|
141
|
+
y: Int
|
|
142
|
+
}
|
|
143
|
+
`;
|
|
144
|
+
|
|
145
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
146
|
+
'INVALID_GRAPHQL',
|
|
147
|
+
'[S] Invalid default value (got: {x: 1, y: 2, z: 3}) provided for argument Query.f(i:) of type I.'
|
|
148
|
+
]]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('errors on default value being unknown enum value', () => {
|
|
152
|
+
const doc = gql`
|
|
153
|
+
type Query {
|
|
154
|
+
f(e: E = THREE): Int
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
enum E {
|
|
158
|
+
ONE
|
|
159
|
+
TWO
|
|
160
|
+
}
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
// Note that it is slightly imperfect that the error shows the value as a "string" but is the result
|
|
164
|
+
// of enum values being encoded by string value internally (and while, when a value type-check correctly,
|
|
165
|
+
// we can use the type to display enum values properly, this is exactly a case where the value is not
|
|
166
|
+
// correctly type-checked, so we currently don't have a good way to figure out it's an enum when we display
|
|
167
|
+
// it in the error message). We could fix this someday if we change to using a specific class/object for
|
|
168
|
+
// enum values internally (though this might have backward compatbility constraints), but in the meantime,
|
|
169
|
+
// it's unlikely to trip users too much.
|
|
170
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
171
|
+
'INVALID_GRAPHQL',
|
|
172
|
+
'[S] Invalid default value (got: "THREE") provided for argument Query.f(e:) of type E.'
|
|
173
|
+
]]);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('errors on default value being unknown enum value (as string)', () => {
|
|
177
|
+
const doc = gql`
|
|
178
|
+
type Query {
|
|
179
|
+
f(e: E = "TWOO"): Int
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
enum E {
|
|
183
|
+
ONE
|
|
184
|
+
TWO
|
|
185
|
+
}
|
|
186
|
+
`;
|
|
187
|
+
|
|
188
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
189
|
+
'INVALID_GRAPHQL',
|
|
190
|
+
'[S] Invalid default value (got: "TWOO") provided for argument Query.f(e:) of type E.'
|
|
191
|
+
]]);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('accepts default value enum value as string, if a valid enum value', () => {
|
|
195
|
+
// Please note that this test show we accept strings for enum even though the GraphQL spec kind
|
|
196
|
+
// of say we shouldn't. But the graphQL spec also doesn't really do default value validation,
|
|
197
|
+
// which be believe is just wrong, and as a consequence we've seen customer schema with string-for-enum
|
|
198
|
+
// in default values, and it doesn't sound very harmfull to allow it (the spec even admits that some
|
|
199
|
+
// transport may have to deal with enums as string anyway), so we prefer having that allowance in
|
|
200
|
+
// federation (if this ever become a huge issue for some users, we could imagine to add a "strict"
|
|
201
|
+
// more that start refusing this).
|
|
202
|
+
const doc = gql`
|
|
203
|
+
type Query {
|
|
204
|
+
f(e: E = "TWO"): Int
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
enum E {
|
|
208
|
+
ONE
|
|
209
|
+
TWO
|
|
210
|
+
}
|
|
211
|
+
`;
|
|
212
|
+
|
|
213
|
+
expect(buildForErrors(doc)).toBeUndefined();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('accepts any value for a custom scalar in field agument', () => {
|
|
217
|
+
const doc = gql`
|
|
218
|
+
type Query {
|
|
219
|
+
f(i: Scalar = { x: 2, y: "3" }): Int
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
scalar Scalar
|
|
223
|
+
`;
|
|
224
|
+
|
|
225
|
+
expect(buildForErrors(doc)).toBeUndefined();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('accepts any value for a custom scalar in directive agument', () => {
|
|
229
|
+
const doc = gql`
|
|
230
|
+
type Query {
|
|
231
|
+
f: Int
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
directive @myDirective(i: Scalar = { x: 2, y: "3" }) on FIELD
|
|
235
|
+
scalar Scalar
|
|
236
|
+
`;
|
|
237
|
+
|
|
238
|
+
expect(buildForErrors(doc)).toBeUndefined();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('accepts any value for a custom scalar in an input field', () => {
|
|
242
|
+
const doc = gql`
|
|
243
|
+
input I {
|
|
244
|
+
x: Scalar = { z: { a: 4} }
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
scalar Scalar
|
|
248
|
+
`;
|
|
249
|
+
|
|
250
|
+
expect(buildForErrors(doc)).toBeUndefined();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('accepts default value coercible to list for a list type', () => {
|
|
254
|
+
const doc = gql`
|
|
255
|
+
type Query {
|
|
256
|
+
f(x: [String] = "foo"): Int
|
|
257
|
+
}
|
|
258
|
+
`;
|
|
259
|
+
|
|
260
|
+
expect(buildForErrors(doc)).toBeUndefined();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('accepts default value coercible to list for a list type through multiple coercions', () => {
|
|
264
|
+
const doc = gql`
|
|
265
|
+
type Query {
|
|
266
|
+
f(x: [[[String]!]]! = "foo"): Int
|
|
267
|
+
}
|
|
268
|
+
`;
|
|
269
|
+
|
|
270
|
+
expect(buildForErrors(doc)).toBeUndefined();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('errors on default value no coercible to list for a list type through multiple coercions', () => {
|
|
274
|
+
const doc = gql`
|
|
275
|
+
type Query {
|
|
276
|
+
f(x: [[[String]!]]! = 2): Int
|
|
277
|
+
}
|
|
278
|
+
`;
|
|
279
|
+
|
|
280
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
281
|
+
'INVALID_GRAPHQL',
|
|
282
|
+
'[S] Invalid default value (got: 2) provided for argument Query.f(x:) of type [[[String]!]]!.'
|
|
283
|
+
]]);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('accepts default value coercible to its type but needing multiple/nested coercions', () => {
|
|
287
|
+
const doc = gql`
|
|
288
|
+
type Query {
|
|
289
|
+
f(x: I = { j: {x: 1, z: "Foo"} }): Int
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
input I {
|
|
293
|
+
j: [J]
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
input J {
|
|
297
|
+
x: ID
|
|
298
|
+
y: ID
|
|
299
|
+
z: ID
|
|
300
|
+
}
|
|
301
|
+
`;
|
|
302
|
+
|
|
303
|
+
expect(buildForErrors(doc)).toBeUndefined();
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('accepts default values that, if actually coerced, woudl result in infinite loops', () => {
|
|
307
|
+
// This example is stolen from this comment: https://github.com/graphql/graphql-spec/pull/793#issuecomment-738736539
|
|
308
|
+
// It essentially show that while, as the other tests of this file show, we 1) validate default value against
|
|
309
|
+
// their type and 2) ensures default values coercible to said type don't fail such validation, we also do
|
|
310
|
+
// _not_ do the actual coercion of those values, which in this example would lead to an infinite loop.
|
|
311
|
+
const doc = gql`
|
|
312
|
+
input A {
|
|
313
|
+
b: B = {}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
input B {
|
|
317
|
+
a: A = {}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
type Query {
|
|
321
|
+
q(a: A = {}): Int
|
|
322
|
+
}
|
|
323
|
+
`;
|
|
324
|
+
|
|
325
|
+
expect(buildForErrors(doc)).toBeUndefined();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('errors on null default value for non-nullable input', () => {
|
|
329
|
+
const doc = gql`
|
|
330
|
+
type Query {
|
|
331
|
+
f(i: Int! = null): Int
|
|
332
|
+
}
|
|
333
|
+
`;
|
|
334
|
+
|
|
335
|
+
expect(buildForErrors(doc)).toStrictEqual([[
|
|
336
|
+
'INVALID_GRAPHQL',
|
|
337
|
+
'[S] Invalid default value (got: null) provided for argument Query.f(i:) of type Int!.'
|
|
338
|
+
]]);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('Accepts null default value for nullable input', () => {
|
|
342
|
+
const doc = gql`
|
|
343
|
+
type Query {
|
|
344
|
+
f(i: Int = null): Int
|
|
345
|
+
}
|
|
346
|
+
`;
|
|
347
|
+
|
|
348
|
+
expect(buildForErrors(doc)).toBeUndefined();
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
describe('values printing', () => {
|
|
353
|
+
it('prints enums value correctly within multiple lists', () => {
|
|
354
|
+
const sdl = `
|
|
355
|
+
type Query {
|
|
356
|
+
f(a: [[[E]!]!] = [[[FOO], [BAR]]]): Int
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
enum E {
|
|
360
|
+
FOO
|
|
361
|
+
BAR
|
|
362
|
+
}
|
|
363
|
+
`
|
|
364
|
+
expect(printSchema(parseSchema(sdl))).toMatchString(sdl);
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
it('prints enums value when its coercible to list through multiple coercions', () => {
|
|
368
|
+
const sdl = `
|
|
369
|
+
type Query {
|
|
370
|
+
f(a: [[[E]!]!] = FOO): Int
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
enum E {
|
|
374
|
+
FOO
|
|
375
|
+
BAR
|
|
376
|
+
}
|
|
377
|
+
`
|
|
378
|
+
expect(printSchema(parseSchema(sdl))).toMatchString(sdl);
|
|
379
|
+
})
|
|
380
|
+
});
|
package/src/buildSchema.ts
CHANGED
|
@@ -89,7 +89,7 @@ export function buildSchemaFromAST(
|
|
|
89
89
|
buildSchemaDefinitionInner(schemaExtension, schema.schemaDefinition, errors, schema.schemaDefinition.newExtension());
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
schema.blueprint.onDirectiveDefinitionAndSchemaParsed(schema);
|
|
92
|
+
errors.push(...schema.blueprint.onDirectiveDefinitionAndSchemaParsed(schema));
|
|
93
93
|
|
|
94
94
|
for (const definitionNode of documentNode.definitions) {
|
|
95
95
|
switch (definitionNode.kind) {
|