Skip to content

Commit 9a96022

Browse files
eokoneyoAlan-Cha
authored andcommitted
add setup to generate a schema following jaydenseric's file upload proposal
Signed-off-by: Eyo O. Eyo <[email protected]> add more test cases for generated schema Signed-off-by: Eyo Okon Eyo <[email protected]>
1 parent 1bc9eac commit 9a96022

File tree

8 files changed

+285
-2
lines changed

8 files changed

+285
-2
lines changed

packages/openapi-to-graphql/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
"form-urlencoded": "^6.0.4",
8888
"graphql-scalars": "^1.10.0",
8989
"graphql-subscriptions": "^1.1.0",
90+
"graphql-upload": "^13.0.0",
9091
"json-ptr": "^2.2.0",
9192
"jsonpath-plus": "^6.0.1",
9293
"oas-validator": "^5.0.2",

packages/openapi-to-graphql/src/oas_3_tools.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,9 @@ export function getSchemaTargetGraphQLType<TSource, TContext, TArgs>(
473473
if (typeof schema.format === 'string') {
474474
if (schema.type === 'integer' && schema.format === 'int64') {
475475
return TargetGraphQLType.bigint
476-
476+
// CASE: file upload
477+
} else if (schema.type === 'string' && schema.format === 'binary') {
478+
return TargetGraphQLType.upload
477479
// CASE: id
478480
} else if (
479481
schema.type === 'string' &&
@@ -840,7 +842,8 @@ export function getRequestSchemaAndNames(
840842
if (
841843
payloadContentType === 'application/json' ||
842844
payloadContentType === '*/*' ||
843-
payloadContentType === 'application/x-www-form-urlencoded'
845+
payloadContentType === 'application/x-www-form-urlencoded' ||
846+
payloadContentType === 'multipart/form-data'
844847
) {
845848
// Name extracted from a reference, if applicable
846849
let fromRef: string

packages/openapi-to-graphql/src/schema_builder.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
GraphQLInputType,
3737
GraphQLInputFieldConfigMap
3838
} from 'graphql'
39+
import { GraphQLUpload } from 'graphql-upload'
3940

4041
// Imports:
4142
import { GraphQLBigInt, GraphQLJSON } from 'graphql-scalars'
@@ -222,6 +223,10 @@ export function getGraphQLType<TSource, TContext, TArgs>({
222223
case TargetGraphQLType.bigint:
223224
def.graphQLType = GraphQLBigInt
224225
return def.graphQLType
226+
227+
case TargetGraphQLType.upload:
228+
def.graphQLType = GraphQLUpload
229+
return def.graphQLType
225230
}
226231
}
227232

packages/openapi-to-graphql/src/types/operation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export enum TargetGraphQLType {
3838
boolean = 'boolean',
3939
id = 'id',
4040
bigint = 'bigint',
41+
upload = 'upload',
4142

4243
// JSON
4344
json = 'json',
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
'use strict'
2+
3+
import { afterAll, beforeAll, expect, test } from '@jest/globals'
4+
import * as openAPIToGraphQL from '../src/index'
5+
import * as Oas3Tools from '../src/oas_3_tools'
6+
7+
import { startServer, stopServer } from './file_upload_server'
8+
import { graphql } from 'graphql'
9+
10+
/**
11+
* Set up the schema first
12+
*/
13+
const oas = require('./fixtures/file_upload.json')
14+
const PORT = 3010
15+
16+
// Update PORT for this test case:
17+
oas.servers[0].variables.port.default = String(PORT)
18+
19+
let createdSchema
20+
21+
beforeAll(async () => {
22+
const [{ schema }] = await Promise.all([
23+
openAPIToGraphQL.createGraphQLSchema(oas),
24+
startServer(PORT)
25+
])
26+
27+
createdSchema = schema
28+
})
29+
30+
afterAll(async () => {
31+
await stopServer()
32+
})
33+
34+
test('All mutation endpoints are found to be present', () => {
35+
let oasMutCount = 0
36+
for (let path in oas.paths) {
37+
for (let method in oas.paths[path]) {
38+
if (Oas3Tools.isHttpMethod(method) && method !== 'get') oasMutCount++
39+
}
40+
}
41+
const gqlTypes = Object.keys(createdSchema._typeMap.Mutation.getFields())
42+
.length
43+
expect(gqlTypes).toEqual(oasMutCount)
44+
})
45+
46+
test('registers the graphql-upload Upload scalar type', async () => {
47+
const query = `{
48+
__type(name: "Upload") {
49+
name
50+
kind
51+
}
52+
}`
53+
54+
const result = await graphql(createdSchema, query)
55+
expect(result).toEqual({
56+
data: {
57+
__type: {
58+
name: 'Upload',
59+
kind: 'SCALAR'
60+
}
61+
}
62+
})
63+
})
64+
65+
test('introspection for mutations returns a mutation matching the custom field specified for the multipart API definition', async () => {
66+
const query = `{
67+
__schema {
68+
mutationType {
69+
fields {
70+
name
71+
type {
72+
name
73+
kind
74+
}
75+
}
76+
}
77+
}
78+
}`
79+
80+
const result = await graphql(createdSchema, query)
81+
expect(result).toEqual({
82+
data: {
83+
__schema: {
84+
mutationType: {
85+
fields: expect.arrayContaining([
86+
expect.objectContaining({
87+
name: 'fileUploadTest'
88+
})
89+
])
90+
}
91+
}
92+
}
93+
})
94+
})
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
'use strict'
2+
3+
const express = require('express')
4+
5+
let server // holds server object for shutdown
6+
7+
/**
8+
* Starts the server at the given port
9+
*/
10+
function startServer (PORT) {
11+
const app = express()
12+
13+
const bodyParser = require('body-parser')
14+
app.use(bodyParser.json())
15+
16+
app.get('/api/upload', (req, res) => {
17+
res.send({
18+
id: '1234567098',
19+
url: 'https://some-random-url.domain/assets/upload-file.ext'
20+
})
21+
})
22+
23+
return new Promise(resolve => {
24+
server = app.listen(PORT, () => {
25+
console.log(`Example API accessible on port ${PORT}`)
26+
resolve()
27+
})
28+
})
29+
}
30+
31+
/**
32+
* Stops server.
33+
*/
34+
function stopServer () {
35+
return new Promise(resolve => {
36+
server.close(() => {
37+
console.log(`Stopped API server`)
38+
resolve()
39+
})
40+
})
41+
}
42+
43+
// If run from command line, start server:
44+
if (require.main === module) {
45+
void (async () => startServer(3002))()
46+
}
47+
48+
module.exports = {
49+
startServer,
50+
stopServer
51+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
{
2+
"openapi": "3.0.0",
3+
"info": {
4+
"title": "File Upload API",
5+
"version": "1.0.0"
6+
},
7+
"servers": [
8+
{
9+
"url": "http://localhost:{port}/{basePath}",
10+
"description": "The location of the local test server.",
11+
"variables": {
12+
"port": {
13+
"default": "3010"
14+
},
15+
"basePath": {
16+
"default": "api"
17+
}
18+
}
19+
}
20+
],
21+
"paths": {
22+
"/upload": {
23+
"post": {
24+
"requestBody": {
25+
"content": {
26+
"multipart/form-data": {
27+
"schema": {
28+
"required": [
29+
"file"
30+
],
31+
"type": "object",
32+
"properties": {
33+
"file": {
34+
"maxLength": 255,
35+
"pattern": "^[0-9a-zA-Z_./ -]*$",
36+
"type": "string",
37+
"format": "binary"
38+
}
39+
}
40+
}
41+
}
42+
}
43+
},
44+
"responses": {
45+
"200": {
46+
"description": "OK",
47+
"content": {
48+
"application/json": {
49+
"schema": {
50+
"required": [
51+
"id",
52+
"url"
53+
],
54+
"type": "object",
55+
"properties": {
56+
"id": {
57+
"type": "integer",
58+
"format": "int64",
59+
"example": 10
60+
},
61+
"url": {
62+
"type": "string"
63+
}
64+
}
65+
}
66+
}
67+
}
68+
}
69+
}
70+
}
71+
}
72+
}
73+
}

packages/openapi-to-graphql/yarn.lock

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,6 +1124,13 @@ bulk-write-stream@^2.0.1:
11241124
inherits "^2.0.3"
11251125
readable-stream "^3.1.1"
11261126

1127+
busboy@^0.3.1:
1128+
version "0.3.1"
1129+
resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b"
1130+
integrity sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==
1131+
dependencies:
1132+
dicer "0.3.0"
1133+
11271134
11281135
version "3.1.0"
11291136
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
@@ -1598,6 +1605,13 @@ detect-newline@^3.0.0:
15981605
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
15991606
integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
16001607

1608+
1609+
version "0.3.0"
1610+
resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872"
1611+
integrity sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==
1612+
dependencies:
1613+
streamsearch "0.1.2"
1614+
16011615
diff-sequences@^26.6.2:
16021616
version "26.6.2"
16031617
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1"
@@ -2365,6 +2379,11 @@ from2@^2.3.0:
23652379
inherits "^2.0.1"
23662380
readable-stream "^2.0.0"
23672381

2382+
fs-capacitor@^6.2.0:
2383+
version "6.2.0"
2384+
resolved "https://registry.yarnpkg.com/fs-capacitor/-/fs-capacitor-6.2.0.tgz#fa79ac6576629163cb84561995602d8999afb7f5"
2385+
integrity sha512-nKcE1UduoSKX27NSZlg879LdQc94OtbOsEmKMN2MBNudXREvijRKx2GEBsTMTfws+BrbkJoEuynbGSVRSpauvw==
2386+
23682387
fs.realpath@^1.0.0:
23692388
version "1.0.0"
23702389
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -2565,6 +2584,16 @@ graphql-subscriptions@^1.1.0:
25652584
dependencies:
25662585
iterall "^1.3.0"
25672586

2587+
graphql-upload@^13.0.0:
2588+
version "13.0.0"
2589+
resolved "https://registry.yarnpkg.com/graphql-upload/-/graphql-upload-13.0.0.tgz#1a255b64d3cbf3c9f9171fa62a8fb0b9b59bb1d9"
2590+
integrity sha512-YKhx8m/uOtKu4Y1UzBFJhbBGJTlk7k4CydlUUiNrtxnwZv0WigbRHP+DVhRNKt7u7DXOtcKZeYJlGtnMXvreXA==
2591+
dependencies:
2592+
busboy "^0.3.1"
2593+
fs-capacitor "^6.2.0"
2594+
http-errors "^1.8.1"
2595+
object-path "^0.11.8"
2596+
25682597
graphql@*, graphql@^15.3.0:
25692598
version "15.5.1"
25702599
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.5.1.tgz#f2f84415d8985e7b84731e7f3536f8bb9d383aad"
@@ -2675,6 +2704,17 @@ [email protected]:
26752704
statuses ">= 1.5.0 < 2"
26762705
toidentifier "1.0.0"
26772706

2707+
http-errors@^1.8.1:
2708+
version "1.8.1"
2709+
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c"
2710+
integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==
2711+
dependencies:
2712+
depd "~1.1.2"
2713+
inherits "2.0.4"
2714+
setprototypeof "1.2.0"
2715+
statuses ">= 1.5.0 < 2"
2716+
toidentifier "1.0.1"
2717+
26782718
http-proxy-agent@^4.0.1:
26792719
version "4.0.1"
26802720
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a"
@@ -4203,6 +4243,11 @@ object-keys@^1.0.12, object-keys@^1.1.1:
42034243
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
42044244
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
42054245

4246+
object-path@^0.11.8:
4247+
version "0.11.8"
4248+
resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.8.tgz#ed002c02bbdd0070b78a27455e8ae01fc14d4742"
4249+
integrity sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA==
4250+
42064251
object.assign@^4.1.2:
42074252
version "4.1.2"
42084253
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
@@ -5166,6 +5211,11 @@ stream-shift@^1.0.0:
51665211
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"
51675212
integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==
51685213

5214+
5215+
version "0.1.2"
5216+
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
5217+
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
5218+
51695219
string-length@^4.0.1:
51705220
version "4.0.2"
51715221
resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"
@@ -5435,6 +5485,11 @@ [email protected]:
54355485
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
54365486
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
54375487

5488+
5489+
version "1.0.1"
5490+
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
5491+
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
5492+
54385493
touch@^3.1.0:
54395494
version "3.1.0"
54405495
resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b"

0 commit comments

Comments
 (0)