Skip to content

Commit f87d8d6

Browse files
committed
rename diff to check and respond with a change report
1 parent 609d116 commit f87d8d6

File tree

5 files changed

+72
-42
lines changed

5 files changed

+72
-42
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
- Serves a supergraph schema for the GraphQL gateway
2727
- Validates new schema to be compatible with other running services
2828
- Validates that all client operations are supported by your schema
29-
- Produce a diff between your proposed schema and the current registry state to detect breaking changes and more
29+
- Detect breaking, dangerous and safe changes of schema updates
3030
- Lightweight authorization concept based on JWT.
3131

3232
[**Read more**](https://principledgraphql.com/integrity#3-track-the-schema-in-a-registry)

docs/api.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ POST - `/schema/push` Creates a new graph and schema for a service. If you omit
3939

4040
**Notice:** A schema is normalized before it's stored in the database. Whitespaces are stipped.
4141

42-
**Notice:** The schema isn't validated for breaking-changes. Use [schema diff](#produce-a-diff-from-your-schema) to understand the implications of your update.
42+
**Notice:** The schema isn't validated for breaking-changes. Use a [change report](#creates-a-change-report) to understand the implications of your update.
4343

4444
<details>
4545
<summary>Example Request</summary>
@@ -114,9 +114,9 @@ PUT - `/schema/deactivate` Deactivates a schema by id. The schema will no longer
114114

115115
## Validation
116116

117-
### Produce a diff from your schema
117+
### Creates a change report
118118

119-
POST - `/schema/diff` Returns the schema report between provided and latest schemas.
119+
POST - `/schema/check` Returns the schema report between provided and latest schemas. It can detect breaking, dangerous and safe changes. This should be executed before a new schema is pushed.
120120

121121
<details>
122122
<summary>Example Request</summary>
@@ -135,7 +135,7 @@ POST - `/schema/diff` Returns the schema report between provided and latest sche
135135

136136
### Validate your schema
137137

138-
POST - `/schema/validate` Validate schema between provided and latest schemas.
138+
POST - `/schema/validate` Validate schema between provided and latest schemas. It only verify if the schema can be composed.
139139

140140
<details>
141141
<summary>Example Request</summary>

src/registry/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import basicAuth from '../core/basic-auth'
33
import garbageCollect from './maintanance/garbage-collect'
44
import composeSchema from './federation/compose-schema'
55
import composeSchemaVersions from './federation/compose-schema-versions'
6-
import schemaDiff from './schema-validation/schema-diff'
6+
import schemaCheck from './schema-validation/schema-check'
77
import schemaValidation from './schema-validation/schema-validation'
88
import listGraphs from './federation/list-graphs'
99
import registerSchema from './federation/register-schema'
@@ -42,6 +42,6 @@ export default async function Registry(fastify: FastifyInstance, opts: registryO
4242
composeSchema(fastify)
4343
composeSchemaVersions(fastify)
4444
deactivateSchema(fastify)
45-
schemaDiff(fastify)
45+
schemaCheck(fastify)
4646
schemaValidation(fastify)
4747
}

src/registry/schema-validation/schema-diff.test.ts renamed to src/registry/schema-validation/schema-check.test.ts

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ test.before(createTestContext())
1313
test.beforeEach(createTestPrefix())
1414
test.after.always('cleanup', cleanTest())
1515

16-
test('Should calculate schema diff', async (t) => {
16+
test('Should check the schema for changes with the latest registry state', async (t) => {
1717
const app = build({
1818
databaseConnectionUrl: t.context.connectionUrl,
1919
})
@@ -39,7 +39,7 @@ test('Should calculate schema diff', async (t) => {
3939

4040
res = await app.inject({
4141
method: 'POST',
42-
url: '/schema/diff',
42+
url: '/schema/check',
4343
payload: {
4444
typeDefs: /* GraphQL */ `
4545
type Query {
@@ -58,16 +58,17 @@ test('Should calculate schema diff', async (t) => {
5858
res.json(),
5959
{
6060
success: true,
61-
data: [
62-
{
63-
criticality: {
61+
data: {
62+
isBreaking: false,
63+
report: [
64+
{
6465
level: 'NON_BREAKING',
66+
type: 'FIELD_ADDED',
67+
message: "Field 'world' was added to object type 'Query'",
68+
path: 'Query.world',
6569
},
66-
type: 'FIELD_ADDED',
67-
message: "Field 'world' was added to object type 'Query'",
68-
path: 'Query.world',
69-
},
70-
],
70+
],
71+
},
7172
},
7273
'response payload match',
7374
)
@@ -100,7 +101,7 @@ test('Should detect a breaking change', async (t) => {
100101

101102
res = await app.inject({
102103
method: 'POST',
103-
url: '/schema/diff',
104+
url: '/schema/check',
104105
payload: {
105106
typeDefs: trimDoc/* GraphQL */ `
106107
type Query {
@@ -118,18 +119,19 @@ test('Should detect a breaking change', async (t) => {
118119
res.json(),
119120
{
120121
success: true,
121-
data: [
122-
{
123-
criticality: {
124-
level: 'BREAKING',
122+
data: {
123+
isBreaking: true,
124+
report: [
125+
{
125126
reason:
126127
'Removing a field is a breaking change. It is preferable to deprecate the field before removing it.',
128+
level: 'BREAKING',
129+
type: 'FIELD_REMOVED',
130+
message: "Field 'world' was removed from object type 'Query'",
131+
path: 'Query.world',
127132
},
128-
type: 'FIELD_REMOVED',
129-
message: "Field 'world' was removed from object type 'Query'",
130-
path: 'Query.world',
131-
},
132-
],
133+
],
134+
},
133135
},
134136
'response payload match',
135137
)
@@ -143,7 +145,7 @@ test('Should return 400 because type_def is missing', async (t) => {
143145

144146
let res = await app.inject({
145147
method: 'POST',
146-
url: '/schema/diff',
148+
url: '/schema/check',
147149
payload: {
148150
serviceName: `${t.context.testPrefix}_foo`,
149151
graphName: `${t.context.graphName}`,
@@ -187,7 +189,7 @@ test('Should return an empty diff when no other services exists', async (t) => {
187189

188190
res = await app.inject({
189191
method: 'POST',
190-
url: '/schema/diff',
192+
url: '/schema/check',
191193
payload: {
192194
typeDefs: /* GraphQL */ `
193195
type Query {
@@ -204,7 +206,10 @@ test('Should return an empty diff when no other services exists', async (t) => {
204206
res.json(),
205207
{
206208
success: true,
207-
data: [],
209+
data: {
210+
isBreaking: false,
211+
report: [],
212+
},
208213
},
209214
'message',
210215
)

src/registry/schema-validation/schema-diff.ts renamed to src/registry/schema-validation/schema-check.ts

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import S from 'fluent-json-schema'
22
import { FastifyInstance, FastifySchema } from 'fastify'
3-
import { diff } from '@graphql-inspector/core'
3+
import { CriticalityLevel, diff } from '@graphql-inspector/core'
44
import { composeAndValidateSchema } from '../../core/federation'
55
import { SchemaManager } from '../../core/manager/SchemaManager'
66
import {
@@ -29,14 +29,20 @@ export const schema: FastifySchema = {
2929
.prop('success', S.boolean())
3030
.prop(
3131
'data',
32-
S.array().items(
33-
S.object()
34-
.required(['criticality', 'type', 'message', 'path'])
35-
.prop('criticality', S.object().prop('level', S.string()).prop('reason', S.string()))
36-
.prop('type', S.string())
37-
.prop('message', S.string())
38-
.prop('path', S.string()),
39-
),
32+
S.object()
33+
.prop('isBreaking', S.boolean())
34+
.prop(
35+
'report',
36+
S.array().items(
37+
S.object()
38+
.required(['type', 'message', 'level', 'path'])
39+
.prop('type', S.string())
40+
.prop('message', S.string())
41+
.prop('level', S.string())
42+
.prop('path', S.string())
43+
.prop('reason', S.string()),
44+
),
45+
),
4046
),
4147
},
4248
body: S.object()
@@ -48,7 +54,7 @@ export const schema: FastifySchema = {
4854
}
4955

5056
export default function schemaDiff(fastify: FastifyInstance) {
51-
fastify.post<RequestContext>('/schema/diff', { schema }, async (req, res) => {
57+
fastify.post<RequestContext>('/schema/check', { schema }, async (req, res) => {
5258
const graphRepository = new GraphRepository(fastify.knex)
5359

5460
const graphExists = await graphRepository.exists({
@@ -111,11 +117,30 @@ export default function schemaDiff(fastify: FastifyInstance) {
111117
throw SchemaCompositionError(updated.error)
112118
}
113119

114-
const result = diff(original.schema, updated.schema)
120+
const changes = diff(original.schema, updated.schema)
121+
122+
const changesReport = []
123+
let isBreaking = false
124+
125+
for (const change of changes) {
126+
if (change.criticality.level === CriticalityLevel.Breaking) {
127+
isBreaking = true
128+
}
129+
changesReport.push({
130+
type: change.type,
131+
message: change.message,
132+
level: change.criticality.level,
133+
path: change.path,
134+
reason: change.criticality.reason,
135+
})
136+
}
115137

116138
return {
117139
success: true,
118-
data: result,
140+
data: {
141+
isBreaking,
142+
report: changesReport,
143+
},
119144
}
120145
})
121146
}

0 commit comments

Comments
 (0)