-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: Improve GraphQL Schema Validation to allow Subscription types wh…
…en not using Realtime and ensure schema does not use reserved names (#9005) See: #8988 1. In GraphQL there are many reserved names - `Subscription` being one of them. And Redwood's schema validation checks that a validator directive is applied to queries, mutations -- and subscriptions. However, we have cases where existing RW apps have used `Subscription` as a model and a type. This PR will only check for the directive on Subscriptions if Redwood Realtime is enabled. Note: a workaround is to keep your Prisma model named Subscription, but just rename the type to something like "CustomerSubscription" or "ProductSubscription" and rework your services to have resolver types that match your GraphQL schema. 2. This PR also will raise errors when reserved names are uses as types ,inputs or interfaces -- like if for example you had a Prisma model and a generated type like "Float" because maybe you had a fishing or sailing store and needed to have a collection of floats :) Note to @jtoar - this is a potentially breaking changes because some projects might be using reserved GraphQL names as types but really shouldn't to mitigate issues downstream. --------- Co-authored-by: Dominic Saadi <[email protected]>
- Loading branch information
1 parent
2631a06
commit 0167be7
Showing
5 changed files
with
290 additions
and
5 deletions.
There are no files selected for viewing
19 changes: 19 additions & 0 deletions
19
packages/internal/src/__tests__/__snapshots__/validateSchemaForReservedNames.test.ts.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`SDL with no reserved names used SDL is invalid because uses a reserved name as a type 1`] = ` | ||
"The type named 'Float' is a reserved GraphQL name. | ||
Please rename it to something more specific, like: ApplicationFloat. | ||
" | ||
`; | ||
|
||
exports[`SDL with no reserved names used because uses a reserved name as an input 1`] = ` | ||
"The input type named 'Float' is a reserved GraphQL name. | ||
Please rename it to something more specific, like: ApplicationFloat. | ||
" | ||
`; | ||
|
||
exports[`SDL with no reserved names used because uses a reserved name as an interface 1`] = ` | ||
"The interface named 'Float' is a reserved GraphQL name. | ||
Please rename it to something more specific, like: ApplicationFloat. | ||
" | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
177 changes: 177 additions & 0 deletions
177
packages/internal/src/__tests__/validateSchemaForReservedNames.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
import path from 'path' | ||
|
||
import { DocumentNode } from 'graphql' | ||
import gql from 'graphql-tag' | ||
|
||
import { validateSchema } from '../validateSchema' | ||
|
||
const FIXTURE_PATH = path.resolve( | ||
__dirname, | ||
'../../../../__fixtures__/example-todo-main' | ||
) | ||
|
||
beforeAll(() => { | ||
process.env.RWJS_CWD = FIXTURE_PATH | ||
}) | ||
afterAll(() => { | ||
delete process.env.RWJS_CWD | ||
}) | ||
|
||
const validateSdlFile = async (document: DocumentNode) => { | ||
validateSchema(document) | ||
} | ||
|
||
describe('SDL with no reserved names used', () => { | ||
describe('SDL is valid', () => { | ||
test('with proper type definition names', async () => { | ||
const document = gql` | ||
type Message { | ||
from: String | ||
body: String | ||
} | ||
type Query { | ||
room(id: ID!): [Message!]! @skipAuth | ||
} | ||
input SendMessageInput { | ||
roomId: ID! | ||
from: String! | ||
body: String! | ||
} | ||
type Mutation { | ||
sendMessage(input: SendMessageInput!): Message! @skipAuth | ||
} | ||
` | ||
|
||
await expect(validateSdlFile(document)).resolves.not.toThrowError() | ||
}) | ||
test('with proper interface interface definition names', async () => { | ||
const document = gql` | ||
interface Node { | ||
id: ID! | ||
} | ||
type Message implements Node { | ||
id: ID! | ||
from: String | ||
body: String | ||
} | ||
type Query { | ||
room(id: ID!): [Message!]! @skipAuth | ||
} | ||
` | ||
await expect(validateSdlFile(document)).resolves.not.toThrowError() | ||
}) | ||
test('with proper interface input type definition names', async () => { | ||
const document = gql` | ||
type Message { | ||
from: String | ||
body: String | ||
} | ||
type Query { | ||
room(id: ID!): [Message!]! @skipAuth | ||
} | ||
input SendMessageInput { | ||
roomId: ID! | ||
from: String! | ||
body: String! | ||
} | ||
type Mutation { | ||
sendMessage(input: SendMessageInput!): Message! @skipAuth | ||
} | ||
` | ||
await expect(validateSdlFile(document)).resolves.not.toThrowError() | ||
}) | ||
}) | ||
|
||
describe('SDL is invalid', () => { | ||
test('because uses a reserved name as a type', async () => { | ||
const document = gql` | ||
type Float { | ||
from: String | ||
body: String | ||
} | ||
type Query { | ||
room(id: ID!): [Message!]! @skipAuth | ||
} | ||
input SendMessageInput { | ||
roomId: ID! | ||
from: String! | ||
body: String! | ||
} | ||
type Mutation { | ||
sendMessage(input: SendMessageInput!): Message! @skipAuth | ||
} | ||
` | ||
await expect( | ||
validateSdlFile(document) | ||
).rejects.toThrowErrorMatchingSnapshot() | ||
}) | ||
}) | ||
|
||
test('because uses a reserved name as an input', async () => { | ||
const document = gql` | ||
type Message { | ||
from: String | ||
body: String | ||
} | ||
type Query { | ||
room(id: ID!): [Message!]! @skipAuth | ||
} | ||
input Float { | ||
roomId: ID! | ||
from: String! | ||
body: String! | ||
} | ||
type Mutation { | ||
sendMessage(input: SendMessageInput!): Message! @skipAuth | ||
} | ||
` | ||
await expect( | ||
validateSdlFile(document) | ||
).rejects.toThrowErrorMatchingSnapshot() | ||
}) | ||
|
||
test('because uses a reserved name as an interface', async () => { | ||
const document = gql` | ||
interface Float { | ||
id: ID! | ||
} | ||
type Message implements Float { | ||
id: ID! | ||
from: String | ||
body: String | ||
} | ||
type Query { | ||
room(id: ID!): [Message!]! @skipAuth | ||
} | ||
input SendMessageInput { | ||
roomId: ID! | ||
from: String! | ||
body: String! | ||
} | ||
type Mutation { | ||
sendMessage(input: SendMessageInput!): Message! @skipAuth | ||
} | ||
` | ||
await expect( | ||
validateSdlFile(document) | ||
).rejects.toThrowErrorMatchingSnapshot() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters