@scalar/json-magic 0.6.0 → 0.7.0
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/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +22 -0
- package/dist/bundle/bundle.d.ts +1 -0
- package/dist/bundle/bundle.d.ts.map +1 -1
- package/dist/bundle/bundle.js +3 -3
- package/dist/bundle/bundle.js.map +2 -2
- package/dist/bundle/plugins/fetch-urls/index.d.ts.map +1 -1
- package/dist/bundle/plugins/fetch-urls/index.js +8 -1
- package/dist/bundle/plugins/fetch-urls/index.js.map +2 -2
- package/dist/bundle/plugins/parse-json/index.d.ts.map +1 -1
- package/dist/bundle/plugins/parse-json/index.js +7 -6
- package/dist/bundle/plugins/parse-json/index.js.map +2 -2
- package/dist/bundle/plugins/parse-yaml/index.d.ts.map +1 -1
- package/dist/bundle/plugins/parse-yaml/index.js +7 -6
- package/dist/bundle/plugins/parse-yaml/index.js.map +2 -2
- package/dist/bundle/plugins/read-files/index.d.ts.map +1 -1
- package/dist/bundle/plugins/read-files/index.js +2 -1
- package/dist/bundle/plugins/read-files/index.js.map +2 -2
- package/dist/diff/diff.d.ts.map +1 -1
- package/dist/diff/diff.js.map +2 -2
- package/dist/diff/utils.d.ts.map +1 -1
- package/dist/diff/utils.js.map +2 -2
- package/dist/magic-proxy/proxy.d.ts +7 -6
- package/dist/magic-proxy/proxy.d.ts.map +1 -1
- package/dist/magic-proxy/proxy.js +21 -11
- package/dist/magic-proxy/proxy.js.map +2 -2
- package/package.json +4 -4
- package/src/bundle/bundle.test.ts +278 -259
- package/src/bundle/bundle.ts +4 -4
- package/src/bundle/plugins/fetch-urls/index.test.ts +21 -24
- package/src/bundle/plugins/fetch-urls/index.ts +10 -0
- package/src/bundle/plugins/parse-json/index.test.ts +3 -1
- package/src/bundle/plugins/parse-json/index.ts +7 -6
- package/src/bundle/plugins/parse-yaml/index.test.ts +3 -1
- package/src/bundle/plugins/parse-yaml/index.ts +7 -6
- package/src/bundle/plugins/read-files/index.test.ts +4 -3
- package/src/bundle/plugins/read-files/index.ts +1 -0
- package/src/bundle/value-generator.test.ts +7 -8
- package/src/dereference/dereference.test.ts +6 -6
- package/src/diff/diff.ts +0 -1
- package/src/diff/utils.test.ts +2 -2
- package/src/diff/utils.ts +0 -2
- package/src/helpers/escape-json-pointer.test.ts +1 -1
- package/src/helpers/unescape-json-pointer.test.ts +1 -1
- package/src/magic-proxy/proxy.test.ts +108 -76
- package/src/magic-proxy/proxy.ts +34 -20
package/src/bundle/bundle.ts
CHANGED
|
@@ -65,7 +65,7 @@ export function isLocalRef(value: string): boolean {
|
|
|
65
65
|
return value.startsWith('#')
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
export type ResolveResult = { ok: true; data: unknown } | { ok: false }
|
|
68
|
+
export type ResolveResult = { ok: true; data: unknown; raw: string } | { ok: false }
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
71
|
* Resolves a string by finding and executing the appropriate plugin.
|
|
@@ -80,16 +80,16 @@ export type ResolveResult = { ok: true; data: unknown } | { ok: false }
|
|
|
80
80
|
* // No matching plugin returns { ok: false }
|
|
81
81
|
* await resolveContents('#/components/schemas/User', [urlPlugin, filePlugin])
|
|
82
82
|
*/
|
|
83
|
-
|
|
83
|
+
function resolveContents(value: string, plugins: LoaderPlugin[]): Promise<ResolveResult> {
|
|
84
84
|
const plugin = plugins.find((p) => p.validate(value))
|
|
85
85
|
|
|
86
86
|
if (plugin) {
|
|
87
87
|
return plugin.exec(value)
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
return {
|
|
90
|
+
return Promise.resolve({
|
|
91
91
|
ok: false,
|
|
92
|
-
}
|
|
92
|
+
})
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/**
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { fastify, type FastifyInstance } from 'fastify'
|
|
2
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
-
import { fetchUrl } from '.'
|
|
4
|
-
import assert from 'node:assert'
|
|
5
1
|
import { setTimeout } from 'node:timers/promises'
|
|
6
2
|
|
|
3
|
+
import { type FastifyInstance, fastify } from 'fastify'
|
|
4
|
+
import { assert, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
|
|
6
|
+
import { fetchUrl } from '.'
|
|
7
|
+
|
|
7
8
|
describe('fetchUrl', () => {
|
|
8
9
|
const noLimit = <T>(fn: () => Promise<T>) => fn()
|
|
9
10
|
|
|
@@ -12,11 +13,11 @@ describe('fetchUrl', () => {
|
|
|
12
13
|
|
|
13
14
|
beforeEach(() => {
|
|
14
15
|
server = fastify({ logger: false })
|
|
15
|
-
})
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
return async () => {
|
|
18
|
+
await server.close()
|
|
19
|
+
await setTimeout(100)
|
|
20
|
+
}
|
|
20
21
|
})
|
|
21
22
|
|
|
22
23
|
it('reads json response', async () => {
|
|
@@ -71,14 +72,14 @@ describe('fetchUrl', () => {
|
|
|
71
72
|
|
|
72
73
|
it('send headers to the specified domain', async () => {
|
|
73
74
|
const url = `http://localhost:${PORT}`
|
|
74
|
-
const
|
|
75
|
+
const headersSpy = vi.fn()
|
|
75
76
|
|
|
76
77
|
const response = {
|
|
77
78
|
message: '200OK',
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
server.get('/', (request, reply) => {
|
|
81
|
-
|
|
82
|
+
headersSpy(request.headers)
|
|
82
83
|
reply.send(response)
|
|
83
84
|
})
|
|
84
85
|
|
|
@@ -87,8 +88,8 @@ describe('fetchUrl', () => {
|
|
|
87
88
|
headers: [{ headers: { 'Authorization': 'Bearer <TOKEN>' }, domains: [`localhost:${PORT}`] }],
|
|
88
89
|
})
|
|
89
90
|
|
|
90
|
-
expect(
|
|
91
|
-
expect(
|
|
91
|
+
expect(headersSpy).toHaveBeenCalledOnce()
|
|
92
|
+
expect(headersSpy).toHaveBeenCalledWith({
|
|
92
93
|
'accept': '*/*',
|
|
93
94
|
'accept-encoding': 'gzip, deflate',
|
|
94
95
|
'accept-language': '*',
|
|
@@ -102,14 +103,14 @@ describe('fetchUrl', () => {
|
|
|
102
103
|
|
|
103
104
|
it('does not send headers to other domains', async () => {
|
|
104
105
|
const url = `http://localhost:${PORT}`
|
|
105
|
-
const
|
|
106
|
+
const headersSpy = vi.fn()
|
|
106
107
|
|
|
107
108
|
const response = {
|
|
108
109
|
message: '200OK',
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
server.get('/', (request, reply) => {
|
|
112
|
-
|
|
113
|
+
headersSpy(request.headers)
|
|
113
114
|
reply.send(response)
|
|
114
115
|
})
|
|
115
116
|
|
|
@@ -118,8 +119,8 @@ describe('fetchUrl', () => {
|
|
|
118
119
|
headers: [{ headers: { 'Authorization': 'Bearer <TOKEN>' }, domains: ['localhost:9932', 'localhost'] }],
|
|
119
120
|
})
|
|
120
121
|
|
|
121
|
-
expect(
|
|
122
|
-
expect(
|
|
122
|
+
expect(headersSpy).toHaveBeenCalledOnce()
|
|
123
|
+
expect(headersSpy).toHaveBeenNthCalledWith(1, {
|
|
123
124
|
'accept': '*/*',
|
|
124
125
|
'accept-encoding': 'gzip, deflate',
|
|
125
126
|
'accept-language': '*',
|
|
@@ -131,17 +132,13 @@ describe('fetchUrl', () => {
|
|
|
131
132
|
})
|
|
132
133
|
|
|
133
134
|
it('runs custom fetcher', async () => {
|
|
134
|
-
const
|
|
135
|
+
const customFetch = vi.fn().mockResolvedValue(new Response('{}', { status: 200 }))
|
|
135
136
|
|
|
136
137
|
await fetchUrl('https://example.com', (fn) => fn(), {
|
|
137
|
-
fetch:
|
|
138
|
-
fn(input, init)
|
|
139
|
-
|
|
140
|
-
return new Response('{}', { status: 200 })
|
|
141
|
-
},
|
|
138
|
+
fetch: customFetch,
|
|
142
139
|
})
|
|
143
140
|
|
|
144
|
-
expect(
|
|
145
|
-
expect(
|
|
141
|
+
expect(customFetch).toHaveBeenCalledOnce()
|
|
142
|
+
expect(customFetch).toHaveBeenCalledWith('https://example.com', { headers: undefined })
|
|
146
143
|
})
|
|
147
144
|
})
|
|
@@ -59,13 +59,23 @@ export async function fetchUrl(
|
|
|
59
59
|
return {
|
|
60
60
|
ok: true,
|
|
61
61
|
data: normalize(body),
|
|
62
|
+
raw: body,
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
const contentType = result.headers.get('Content-Type') ?? ''
|
|
67
|
+
|
|
68
|
+
// Warn if the content type is HTML or XML as we only support JSON/YAML
|
|
69
|
+
if (['text/html', 'application/xml'].includes(contentType)) {
|
|
70
|
+
console.warn(`[WARN] We only support JSON/YAML formats, received ${contentType}`)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.warn(`[WARN] Fetch failed with status ${result.status} ${result.statusText} for URL: ${url}`)
|
|
65
74
|
return {
|
|
66
75
|
ok: false,
|
|
67
76
|
}
|
|
68
77
|
} catch {
|
|
78
|
+
console.warn(`[WARN] Failed to parse JSON/YAML from URL: ${url}`)
|
|
69
79
|
return {
|
|
70
80
|
ok: false,
|
|
71
81
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { parseJson } from '@/bundle/plugins/parse-json'
|
|
2
1
|
import { describe, expect, it } from 'vitest'
|
|
3
2
|
|
|
3
|
+
import { parseJson } from '@/bundle/plugins/parse-json'
|
|
4
|
+
|
|
4
5
|
describe('parse-json', () => {
|
|
5
6
|
it.each([
|
|
6
7
|
['{}', true],
|
|
@@ -17,6 +18,7 @@ describe('parse-json', () => {
|
|
|
17
18
|
expect(await parseJson().exec('{ "message": "Hello World" }')).toEqual({
|
|
18
19
|
ok: true,
|
|
19
20
|
data: { message: 'Hello World' },
|
|
21
|
+
'raw': '{ "message": "Hello World" }',
|
|
20
22
|
})
|
|
21
23
|
})
|
|
22
24
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LoaderPlugin
|
|
1
|
+
import type { LoaderPlugin } from '@/bundle'
|
|
2
2
|
import { isJsonObject } from '@/helpers/is-json-object'
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -15,16 +15,17 @@ export function parseJson(): LoaderPlugin {
|
|
|
15
15
|
return {
|
|
16
16
|
type: 'loader',
|
|
17
17
|
validate: isJsonObject,
|
|
18
|
-
exec:
|
|
18
|
+
exec: (value) => {
|
|
19
19
|
try {
|
|
20
|
-
return {
|
|
20
|
+
return Promise.resolve({
|
|
21
21
|
ok: true,
|
|
22
22
|
data: JSON.parse(value),
|
|
23
|
-
|
|
23
|
+
raw: value,
|
|
24
|
+
})
|
|
24
25
|
} catch {
|
|
25
|
-
return {
|
|
26
|
+
return Promise.resolve({
|
|
26
27
|
ok: false,
|
|
27
|
-
}
|
|
28
|
+
})
|
|
28
29
|
}
|
|
29
30
|
},
|
|
30
31
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { parseYaml } from '@/bundle/plugins/parse-yaml'
|
|
2
1
|
import { describe, expect, it } from 'vitest'
|
|
3
2
|
|
|
3
|
+
import { parseYaml } from '@/bundle/plugins/parse-yaml'
|
|
4
|
+
|
|
4
5
|
describe('parse-yaml', () => {
|
|
5
6
|
it.each([
|
|
6
7
|
['hi: hello\n', true],
|
|
@@ -19,6 +20,7 @@ describe('parse-yaml', () => {
|
|
|
19
20
|
expect(await parseYaml().exec('{ "message": "Hello World" }')).toEqual({
|
|
20
21
|
ok: true,
|
|
21
22
|
data: { message: 'Hello World' },
|
|
23
|
+
'raw': '{ "message": "Hello World" }',
|
|
22
24
|
})
|
|
23
25
|
})
|
|
24
26
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import YAML from 'yaml'
|
|
2
2
|
|
|
3
|
-
import type { LoaderPlugin
|
|
3
|
+
import type { LoaderPlugin } from '@/bundle'
|
|
4
4
|
import { isYaml } from '@/helpers/is-yaml'
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -17,16 +17,17 @@ export function parseYaml(): LoaderPlugin {
|
|
|
17
17
|
return {
|
|
18
18
|
type: 'loader',
|
|
19
19
|
validate: isYaml,
|
|
20
|
-
exec:
|
|
20
|
+
exec: (value) => {
|
|
21
21
|
try {
|
|
22
|
-
return {
|
|
22
|
+
return Promise.resolve({
|
|
23
23
|
ok: true,
|
|
24
24
|
data: YAML.parse(value, { merge: true, maxAliasCount: 10000 }),
|
|
25
|
-
|
|
25
|
+
raw: value,
|
|
26
|
+
})
|
|
26
27
|
} catch {
|
|
27
|
-
return {
|
|
28
|
+
return Promise.resolve({
|
|
28
29
|
ok: false,
|
|
29
|
-
}
|
|
30
|
+
})
|
|
30
31
|
}
|
|
31
32
|
},
|
|
32
33
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import fs from 'node:fs/promises'
|
|
2
1
|
import { randomUUID } from 'node:crypto'
|
|
3
|
-
import
|
|
2
|
+
import fs from 'node:fs/promises'
|
|
3
|
+
|
|
4
|
+
import { assert, describe, expect, it } from 'vitest'
|
|
5
|
+
|
|
4
6
|
import { readFile } from '.'
|
|
5
|
-
import assert from 'node:assert'
|
|
6
7
|
|
|
7
8
|
describe('readFile', () => {
|
|
8
9
|
it('reads json contents of a file', async () => {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { generateUniqueValue, uniqueValueGeneratorFactory } from './value-generator'
|
|
2
1
|
import { describe, expect, it } from 'vitest'
|
|
3
2
|
|
|
3
|
+
import { generateUniqueValue, uniqueValueGeneratorFactory } from './value-generator'
|
|
4
|
+
|
|
4
5
|
describe('generateUniqueHash', () => {
|
|
5
6
|
it('should generate hash values from the function we pass in', async () => {
|
|
6
|
-
const hashFunction =
|
|
7
|
+
const hashFunction = (value: string) => {
|
|
7
8
|
if (value === 'a') {
|
|
8
9
|
return 'a'
|
|
9
10
|
}
|
|
@@ -16,7 +17,7 @@ describe('generateUniqueHash', () => {
|
|
|
16
17
|
})
|
|
17
18
|
|
|
18
19
|
it('should return same value for the same input', async () => {
|
|
19
|
-
const hashFunction =
|
|
20
|
+
const hashFunction = (value: string) => {
|
|
20
21
|
if (value === 'a') {
|
|
21
22
|
return value
|
|
22
23
|
}
|
|
@@ -33,7 +34,7 @@ describe('generateUniqueHash', () => {
|
|
|
33
34
|
})
|
|
34
35
|
|
|
35
36
|
it('should handle hash collisions', async () => {
|
|
36
|
-
const hashFunction =
|
|
37
|
+
const hashFunction = (value: string) => {
|
|
37
38
|
// Hash a, b and c will produce collisions
|
|
38
39
|
if (value === 'a' || value === 'b' || value === 'c') {
|
|
39
40
|
return 'd'
|
|
@@ -83,9 +84,7 @@ describe('generateUniqueHash', () => {
|
|
|
83
84
|
})
|
|
84
85
|
|
|
85
86
|
it('should throw when it reaches max depth', async () => {
|
|
86
|
-
const hashFunction =
|
|
87
|
-
return 'a'
|
|
88
|
-
}
|
|
87
|
+
const hashFunction = () => 'a'
|
|
89
88
|
|
|
90
89
|
const map = {}
|
|
91
90
|
const result1 = await generateUniqueValue(hashFunction, 'a', map)
|
|
@@ -94,7 +93,7 @@ describe('generateUniqueHash', () => {
|
|
|
94
93
|
const result2 = await generateUniqueValue(hashFunction, 'a', map)
|
|
95
94
|
expect(result2).toBe('a')
|
|
96
95
|
|
|
97
|
-
expect(() => generateUniqueValue(hashFunction, 'b', map)).rejects.toThrowError()
|
|
96
|
+
await expect(() => generateUniqueValue(hashFunction, 'b', map)).rejects.toThrowError()
|
|
98
97
|
})
|
|
99
98
|
})
|
|
100
99
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { dereference } from '@/dereference/dereference'
|
|
2
|
-
import { fastify, type FastifyInstance } from 'fastify'
|
|
3
1
|
import { setTimeout } from 'node:timers/promises'
|
|
2
|
+
|
|
3
|
+
import { type FastifyInstance, fastify } from 'fastify'
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
5
5
|
|
|
6
|
+
import { dereference } from '@/dereference/dereference'
|
|
7
|
+
|
|
6
8
|
describe('dereference', () => {
|
|
7
9
|
describe('sync', () => {
|
|
8
|
-
it('should dereference JSON pointers',
|
|
10
|
+
it('should dereference JSON pointers', () => {
|
|
9
11
|
const data = {
|
|
10
12
|
users: {
|
|
11
13
|
name: 'John Doe',
|
|
@@ -82,9 +84,7 @@ describe('dereference', () => {
|
|
|
82
84
|
street: 'Sunset Boulevard',
|
|
83
85
|
},
|
|
84
86
|
}
|
|
85
|
-
server.get('/users',
|
|
86
|
-
return userProfile
|
|
87
|
-
})
|
|
87
|
+
server.get('/users', () => userProfile)
|
|
88
88
|
|
|
89
89
|
await server.listen({ port: port })
|
|
90
90
|
|
package/src/diff/diff.ts
CHANGED
package/src/diff/utils.test.ts
CHANGED
|
@@ -148,7 +148,7 @@ describe('isArrayEqual', () => {
|
|
|
148
148
|
[1, 2, 3],
|
|
149
149
|
[1, 2, 3],
|
|
150
150
|
],
|
|
151
|
-
// @ts-
|
|
151
|
+
// @ts-expect-error
|
|
152
152
|
])('should return true', (a, b) => expect(isArrayEqual(a, b)).toEqual(true))
|
|
153
153
|
|
|
154
154
|
test.each([
|
|
@@ -164,6 +164,6 @@ describe('isArrayEqual', () => {
|
|
|
164
164
|
[2, 2, 4],
|
|
165
165
|
[1, 2, 3],
|
|
166
166
|
],
|
|
167
|
-
// @ts-
|
|
167
|
+
// @ts-expect-error
|
|
168
168
|
])('should return false', (a, b) => expect(isArrayEqual(a, b)).toEqual(false))
|
|
169
169
|
})
|
package/src/diff/utils.ts
CHANGED
|
@@ -28,9 +28,7 @@ export const isKeyCollisions = (a: unknown, b: unknown) => {
|
|
|
28
28
|
const keys = new Set([...Object.keys(a), ...Object.keys(b)])
|
|
29
29
|
|
|
30
30
|
for (const key of keys) {
|
|
31
|
-
// @ts-ignore
|
|
32
31
|
if (a[key] !== undefined && b[key] !== undefined) {
|
|
33
|
-
// @ts-ignore
|
|
34
32
|
if (isKeyCollisions(a[key], b[key])) {
|
|
35
33
|
return true
|
|
36
34
|
}
|
|
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'
|
|
|
2
2
|
|
|
3
3
|
import { escapeJsonPointer } from './escape-json-pointer'
|
|
4
4
|
|
|
5
|
-
describe('escapeJsonPointer',
|
|
5
|
+
describe('escapeJsonPointer', () => {
|
|
6
6
|
it('should escape a slash', () => {
|
|
7
7
|
expect(escapeJsonPointer('application/json')).toBe('application~1json')
|
|
8
8
|
})
|
|
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'
|
|
|
2
2
|
|
|
3
3
|
import { unescapeJsonPointer } from './unescape-json-pointer'
|
|
4
4
|
|
|
5
|
-
describe('unescapeJsonPointer',
|
|
5
|
+
describe('unescapeJsonPointer', () => {
|
|
6
6
|
it('unescapes a slash', () => {
|
|
7
7
|
expect(unescapeJsonPointer('/foo~1bar~1baz')).toBe('/foo/bar/baz')
|
|
8
8
|
})
|