Validation
타입 시스템을 사용하여 GraphQL 쿼리가 유효한지 여부를 미리 결정할 수 있습니다. 이를 통해 서버와 클라이언트는 런타임 검사에 의존하지 않고도 유효하지 않은 쿼리가 생성되었을 때 개발자에게 효과적으로 알릴 수 있습니다.1)
Star Wars 예제의 경우 starWarsValidation-test.ts 파일에는 다양한 무효성을 보여주는 여러 쿼리가 포함되어 있으며 참조 구현의 유효성 검사기를 실행하기 위해 실행할 수 있는 테스트 파일입니다.2)
시작으로 복잡한 유효한 쿼리를 사용하겠습니다. 이것은 이전 섹션의 예와 유사하지만 중복된 필드가 조각으로 포함된 중첩 쿼리입니다.3)
{ hero { ...NameAndAppearances friends { ...NameAndAppearances friends { ...NameAndAppearances } } } } fragment NameAndAppearances on Character { name appearsIn }
{ "data": { "hero": { "name": "R2-D2", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ], "friends": [ { "name": "Luke Skywalker", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ], "friends": [ { "name": "Han Solo", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ] }, { "name": "Leia Organa", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ] }, { "name": "C-3PO", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ] }, { "name": "R2-D2", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ] } ] }, { "name": "Han Solo", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ], "friends": [ { "name": "Luke Skywalker", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ] }, { "name": "Leia Organa", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ] }, { "name": "R2-D2", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ] } ] }, { "name": "Leia Organa", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ], "friends": [ { "name": "Luke Skywalker", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ] }, { "name": "Han Solo", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ] }, { "name": "C-3PO", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ] }, { "name": "R2-D2", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ] } ] } ] } } }
그리고 이 쿼리는 유효합니다. 잘못된 쿼리를 살펴보겠습니다.4)
프래그먼트는 자신을 참조하거나 순환을 생성할 수 없습니다. 결과가 제한되지 않을 수 있기 때문입니다! 다음은 위의 동일한 쿼리이지만 명시적인 3단계 중첩이 없습니다.5)
{ hero { ...NameAndAppearancesAndFriends } } fragment NameAndAppearancesAndFriends on Character { name appearsIn friends { ...NameAndAppearancesAndFriends } }
{ "errors": [ { "message": "Cannot spread fragment \"NameAndAppearancesAndFriends\" within itself.", "locations": [ { "line": 11, "column": 5 } ] } ] }
필드를 쿼리할 때 주어진 유형에 존재하는 필드를 쿼리해야 합니다. 따라서 hero
가 Character
를 반환하면 Character
에 대한 필드를 쿼리해야 합니다. 해당 유형에는 favoriteSpaceship
필드가 없으므로 이 쿼리는 유효하지 않습니다.6)
# INVALID: favoriteSpaceship does not exist on Character { hero { favoriteSpaceship } }
{ "errors": [ { "message": "Cannot query field \"favoriteSpaceship\" on type \"Character\".", "locations": [ { "line": 4, "column": 5 } ] } ] }
필드를 쿼리하고 스칼라 또는 열거형이 아닌 다른 것을 반환할 때마다 필드에서 반환하려는 데이터를 지정해야 합니다. hero
는 Character
를 반환하고 우리는 name
및 appearsIn
과 같은 필드를 요청했습니다. 이를 생략하면 쿼리가 유효하지 않습니다.
# INVALID: hero is not a scalar, so fields are needed { hero }
{ "errors": [ { "message": "Field \"hero\" of type \"Character\" must have a selection of subfields. Did you mean \"hero { ... }\"?", "locations": [ { "line": 3, "column": 3 } ] } ] }
마찬가지로 필드가 스칼라인 경우 추가 필드를 쿼리하는 것은 의미가 없으며 그렇게 하면 쿼리가 무효화됩니다.7)
# INVALID: name is a scalar, so fields are not permitted { hero { name { firstCharacterOfName } } }
{ "errors": [ { "message": "Field \"name\" must not have a selection since type \"String!\" has no subfields.", "locations": [ { "line": 4, "column": 10 } ] } ] }
없는 필드
이전에 쿼리는 해당 유형의 필드만 쿼리할 수 있다는 점에 유의했습니다. Character를 반환하는 hero
를 쿼리할 때 Character에 존재하는 필드만 쿼리할 수 있습니다. 하지만 R2-D2의 기본 기능을 쿼리하려는 경우 어떻게 됩니까?8)
# INVALID: primaryFunction does not exist on Character { hero { name primaryFunction } }
{ "errors": [ { "message": "Cannot query field \"primaryFunction\" on type \"Character\". Did you mean to use an inline fragment on \"Droid\"?", "locations": [ { "line": 5, "column": 5 } ] } ] }
Using Fragment
primaryFunction이 Character의 필드가 아니기 때문에 해당 쿼리는 유효하지 않습니다. Character가 Droid이면 primaryFunction을 가져오고 그렇지 않으면 해당 필드를 무시한다는 것을 나타내는 방법이 필요합니다. 이를 위해 앞서 소개한 조각을 사용할 수 있습니다. Droid에 정의된 프래그먼트를 설정하고 포함함으로써 정의된 primaryFunction만 쿼리하도록 합니다.9)
{ hero { name ...DroidFields } } fragment DroidFields on Droid { primaryFunction }
{ "data": { "hero": { "name": "R2-D2", "primaryFunction": "Astromech" } } }
이 쿼리는 유효하지만 약간 장황합니다. 명명된 조각은 위에서 여러 번 사용할 때 유용했지만 이 조각은 한 번만 사용합니다. 명명된 조각을 사용하는 대신 인라인 조각을 사용할 수 있습니다. 이것은 여전히 우리가 쿼리하는 유형을 나타낼 수 있지만 별도의 프래그먼트 이름을 지정하지 않습니다.10)
{ hero { name ... on Droid { primaryFunction } } }
{ "data": { "hero": { "name": "R2-D2", "primaryFunction": "Astromech" } } }
이것은 검증 시스템의 표면을 긁은 것뿐입니다. GraphQL 쿼리가 의미적으로 의미가 있는지 확인하기 위한 여러 유효성 검사 규칙이 있습니다. 사양은 “검증” 섹션에서 이 주제에 대해 더 자세히 설명하고 GraphQL.js의 유효성 검사 디렉터리에는 사양을 준수하는 GraphQL 유효성 검사기를 구현하는 코드가 포함되어 있습니다.11)