@financial-times/cp-content-pipeline-schema 2.3.0 → 2.4.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/CHANGELOG.md +19 -0
- package/lib/generated/index.d.ts +2 -0
- package/lib/model/CapiResponse.d.ts +2 -1
- package/lib/model/CapiResponse.js +11 -5
- package/lib/model/CapiResponse.js.map +1 -1
- package/lib/model/Concept.test.js +0 -9
- package/lib/model/Concept.test.js.map +1 -1
- package/lib/model/RichText.d.ts +1 -1
- package/lib/model/RichText.js +20 -34
- package/lib/model/RichText.js.map +1 -1
- package/lib/model/RichText.test.js +0 -9
- package/lib/model/RichText.test.js.map +1 -1
- package/lib/resolvers/content-tree/bodyXMLToTree.d.ts +4 -3
- package/lib/resolvers/content-tree/bodyXMLToTree.js +2 -2
- package/lib/resolvers/content-tree/bodyXMLToTree.js.map +1 -1
- package/lib/resolvers/content-tree/bodyXMLToTree.test.js +20 -32
- package/lib/resolvers/content-tree/bodyXMLToTree.test.js.map +1 -1
- package/lib/resolvers/content-tree/nodePredicates.d.ts +6 -13
- package/lib/resolvers/content-tree/nodePredicates.js +33 -33
- package/lib/resolvers/content-tree/nodePredicates.js.map +1 -1
- package/lib/resolvers/content-tree/references/Reference.d.ts +1 -1
- package/lib/resolvers/content-tree/tagMappings.d.ts +2 -1
- package/lib/resolvers/content-tree/tagMappings.js +60 -60
- package/lib/resolvers/content-tree/tagMappings.js.map +1 -1
- package/lib/resolvers/content.d.ts +1 -0
- package/lib/resolvers/content.js +1 -0
- package/lib/resolvers/content.js.map +1 -1
- package/lib/resolvers/index.d.ts +1 -0
- package/package.json +1 -2
- package/queries/article.graphql +2 -1
- package/src/generated/index.ts +2 -0
- package/src/model/CapiResponse.ts +14 -7
- package/src/model/Concept.test.ts +0 -9
- package/src/model/RichText.test.ts +0 -10
- package/src/model/RichText.ts +27 -39
- package/src/resolvers/content-tree/bodyXMLToTree.test.ts +22 -32
- package/src/resolvers/content-tree/bodyXMLToTree.ts +11 -6
- package/src/resolvers/content-tree/nodePredicates.ts +39 -45
- package/src/resolvers/content-tree/tagMappings.ts +102 -60
- package/src/resolvers/content.ts +1 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/typedefs/content.graphql +2 -1
- package/__mocks__/worker_threads.ts +0 -3
- package/lib/resolvers/content-tree/bodyXMLToTreeWorker.d.ts +0 -9
- package/lib/resolvers/content-tree/bodyXMLToTreeWorker.js +0 -18
- package/lib/resolvers/content-tree/bodyXMLToTreeWorker.js.map +0 -1
- package/src/resolvers/content-tree/bodyXMLToTreeWorker.ts +0 -31
|
@@ -602,7 +602,7 @@ export class CapiResponse {
|
|
|
602
602
|
: null
|
|
603
603
|
}
|
|
604
604
|
|
|
605
|
-
async liveBlogPosts(
|
|
605
|
+
async liveBlogPosts(): Promise<CapiResponse[] | []> {
|
|
606
606
|
const contains = await this.contains()
|
|
607
607
|
|
|
608
608
|
if (contains && contains.length) {
|
|
@@ -614,17 +614,24 @@ export class CapiResponse {
|
|
|
614
614
|
new Date(a.publishedDate()).getTime()
|
|
615
615
|
)
|
|
616
616
|
|
|
617
|
-
|
|
618
|
-
'pinnedPosts' in this.capiData && this.capiData?.pinnedPosts[0]
|
|
619
|
-
|
|
620
|
-
return includePinned || !pinnedPostId
|
|
621
|
-
? liveBlogPosts
|
|
622
|
-
: liveBlogPosts.filter((post) => post.id() !== pinnedPostId)
|
|
617
|
+
return liveBlogPosts
|
|
623
618
|
}
|
|
624
619
|
|
|
625
620
|
return []
|
|
626
621
|
}
|
|
627
622
|
|
|
623
|
+
isPinned() {
|
|
624
|
+
if (
|
|
625
|
+
this.packageContainer &&
|
|
626
|
+
'pinnedPosts' in this.packageContainer.capiData
|
|
627
|
+
) {
|
|
628
|
+
const pinnedPosts = this.packageContainer?.capiData.pinnedPosts || []
|
|
629
|
+
const pinnedPostId = pinnedPosts[0]
|
|
630
|
+
return pinnedPostId === this.id()
|
|
631
|
+
}
|
|
632
|
+
return false
|
|
633
|
+
}
|
|
634
|
+
|
|
628
635
|
async pinnedPost(): Promise<CapiResponse | null> {
|
|
629
636
|
if ('pinnedPosts' in this.capiData) {
|
|
630
637
|
const pinnedPosts = this.capiData.pinnedPosts || []
|
|
@@ -6,15 +6,6 @@ import { URLManagementDataSource } from '../datasources/url-management'
|
|
|
6
6
|
import { CapiDataSource } from '../datasources/capi'
|
|
7
7
|
import { Logger } from '@dotcom-reliability-kit/logger'
|
|
8
8
|
|
|
9
|
-
jest.mock('@dotcom-reliability-kit/logger', () => ({
|
|
10
|
-
Logger: jest.fn(() => ({
|
|
11
|
-
debug: jest.fn(),
|
|
12
|
-
error: jest.fn(),
|
|
13
|
-
fatal: jest.fn(),
|
|
14
|
-
info: jest.fn(),
|
|
15
|
-
warn: jest.fn(),
|
|
16
|
-
})),
|
|
17
|
-
}))
|
|
18
9
|
const vanityMock = jest.fn<URLManagementDataSource['get']>()
|
|
19
10
|
const getPersonMock = jest.fn<CapiDataSource['getPerson']>()
|
|
20
11
|
const context = {
|
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
import { baseCapiObject } from '../fixtures/capiObject'
|
|
2
2
|
import { RichText } from '../model/RichText'
|
|
3
3
|
|
|
4
|
-
jest.mock('@dotcom-reliability-kit/logger', () => ({
|
|
5
|
-
Logger: jest.fn(() => ({
|
|
6
|
-
debug: jest.fn(),
|
|
7
|
-
error: jest.fn(),
|
|
8
|
-
fatal: jest.fn(),
|
|
9
|
-
info: jest.fn(),
|
|
10
|
-
warn: jest.fn(),
|
|
11
|
-
})),
|
|
12
|
-
}))
|
|
13
|
-
|
|
14
4
|
describe('RichText resolver', () => {
|
|
15
5
|
it('should transform bodyXML to an AST', async () => {
|
|
16
6
|
const model = new RichText('bodyXML', baseCapiObject.bodyXML)
|
package/src/model/RichText.ts
CHANGED
|
@@ -1,26 +1,16 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import type { QueryContext } from '..'
|
|
4
|
-
import bodyXMLToTreeWorker from '../resolvers/content-tree/bodyXMLToTreeWorker'
|
|
1
|
+
import bodyXMLToTree from '../resolvers/content-tree/bodyXMLToTree'
|
|
5
2
|
import extractText from '../resolvers/content-tree/extractText'
|
|
6
|
-
import type { PredicateError } from '../resolvers/content-tree/nodePredicates'
|
|
7
3
|
import updateTreeWithReferenceIds from '../resolvers/content-tree/updateTreeWithReferenceIds'
|
|
8
4
|
import { LiteralUnionScalarValues } from '../resolvers/literal-union'
|
|
9
5
|
import { RichTextSource } from '../resolvers/scalars'
|
|
10
6
|
import { CapiResponse } from './CapiResponse'
|
|
11
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
commonTagMappings,
|
|
9
|
+
articleTagMappings,
|
|
10
|
+
liveBlogPostTagMappings,
|
|
11
|
+
} from '../resolvers/content-tree/tagMappings'
|
|
12
12
|
import { ContentTree } from '@financial-times/content-tree'
|
|
13
|
-
|
|
14
|
-
let piscina: Piscina | undefined
|
|
15
|
-
// Don't run the thread pool during tests as it creates loads of threads that
|
|
16
|
-
// aren't properly closed and leaves the process hanging. We do our best to
|
|
17
|
-
// keep the same behaviour when running on the main thread vs a worker thread,
|
|
18
|
-
// with the main difference being the lack of logs on the main thread.
|
|
19
|
-
if (process.env.NODE_ENV !== 'test') {
|
|
20
|
-
piscina = new Piscina({
|
|
21
|
-
filename: require.resolve('../resolvers/content-tree/bodyXMLToTreeWorker'),
|
|
22
|
-
})
|
|
23
|
-
}
|
|
13
|
+
import { QueryContext } from '..'
|
|
24
14
|
|
|
25
15
|
export class RichText {
|
|
26
16
|
constructor(
|
|
@@ -34,29 +24,27 @@ export class RichText {
|
|
|
34
24
|
}
|
|
35
25
|
|
|
36
26
|
async structured(context?: QueryContext) {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
27
|
+
const tree = await new Promise<ContentTree.Body>((resolve, reject) => {
|
|
28
|
+
// bodyXMLToTree is synchronous and slow. scheduling it in a setImmediate
|
|
29
|
+
// prevents it from blocking the event loop, so the app can still handle
|
|
30
|
+
// requests, and it won't skew resolver timing metrics by blocking the
|
|
31
|
+
// timers for quicker, asynchronous resolvers from finishing.
|
|
32
|
+
|
|
33
|
+
const tagMappings =
|
|
34
|
+
this.contentApiData?.type() === 'LiveBlogPost'
|
|
35
|
+
? { ...commonTagMappings, ...liveBlogPostTagMappings }
|
|
36
|
+
: {
|
|
37
|
+
...commonTagMappings,
|
|
38
|
+
...articleTagMappings(this.contentApiData),
|
|
39
|
+
}
|
|
40
|
+
setImmediate(() => {
|
|
41
|
+
try {
|
|
42
|
+
resolve(bodyXMLToTree(this.value ?? '', tagMappings, context))
|
|
43
|
+
} catch (error) {
|
|
44
|
+
reject(error)
|
|
45
|
+
}
|
|
46
|
+
})
|
|
52
47
|
})
|
|
53
|
-
// bodyXMLToTree is synchronous and slow. Offload its processing to a
|
|
54
|
-
// worker thread so that it does not block the event loop. This allows the
|
|
55
|
-
// app to continue to handle other requests whilst processing an expensive
|
|
56
|
-
// one, as well as not skewing timing metrics.
|
|
57
|
-
const tree: ContentTree.Body = piscina
|
|
58
|
-
? await piscina.run(args)
|
|
59
|
-
: bodyXMLToTreeWorker(args)
|
|
60
48
|
|
|
61
49
|
const { tree: treeWithReferences, references } = updateTreeWithReferenceIds(
|
|
62
50
|
tree,
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import { parentPort } from 'worker_threads'
|
|
2
1
|
import { ContentTree } from '@financial-times/content-tree'
|
|
3
2
|
import bodyXMLToTree, { TagMappings } from './bodyXMLToTree'
|
|
4
3
|
import tags from './tagMappings'
|
|
4
|
+
import { Logger } from '@dotcom-reliability-kit/logger'
|
|
5
|
+
import { QueryContext } from '../..'
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const mockLogger = new Logger()
|
|
8
|
+
const mockLogError = jest.spyOn(mockLogger, 'error')
|
|
9
|
+
|
|
10
|
+
const mockContext = {
|
|
11
|
+
logger: mockLogger,
|
|
12
|
+
} as QueryContext
|
|
9
13
|
|
|
10
14
|
describe('bodyXMLToTree', () => {
|
|
11
15
|
it('converts XML to tree', () => {
|
|
12
16
|
const xml = `<body><p>Hello world</p></body>`
|
|
13
|
-
expect(bodyXMLToTree(xml, tags)).toMatchInlineSnapshot(`
|
|
17
|
+
expect(bodyXMLToTree(xml, tags, mockContext)).toMatchInlineSnapshot(`
|
|
14
18
|
Object {
|
|
15
19
|
"children": Array [
|
|
16
20
|
Object {
|
|
@@ -216,14 +220,14 @@ describe('bodyXMLToTree', () => {
|
|
|
216
220
|
"version": 1,
|
|
217
221
|
}
|
|
218
222
|
`)
|
|
219
|
-
expect(
|
|
223
|
+
expect(mockLogError).not.toBeCalled()
|
|
220
224
|
})
|
|
221
225
|
|
|
222
226
|
it('should handle heading and slots', () => {
|
|
223
227
|
const xml =
|
|
224
228
|
'<body><div class="n-content-layout"><h3></h3><div class="n-content-layout__slot"></div><div class="n-content-layout__slot"></div></body>'
|
|
225
229
|
|
|
226
|
-
expect(bodyXMLToTree(xml, tags)).toMatchInlineSnapshot(`
|
|
230
|
+
expect(bodyXMLToTree(xml, tags, mockContext)).toMatchInlineSnapshot(`
|
|
227
231
|
Object {
|
|
228
232
|
"children": Array [
|
|
229
233
|
Object {
|
|
@@ -251,14 +255,14 @@ describe('bodyXMLToTree', () => {
|
|
|
251
255
|
"version": 1,
|
|
252
256
|
}
|
|
253
257
|
`)
|
|
254
|
-
expect(
|
|
258
|
+
expect(mockLogError).not.toBeCalled()
|
|
255
259
|
})
|
|
256
260
|
|
|
257
261
|
it('should log an error on unexpected child after heading', () => {
|
|
258
262
|
const xml =
|
|
259
263
|
'<body><div class="n-content-layout"><h3></h3><div class="n-content-layout__slot"></div><p></p></body>'
|
|
260
264
|
|
|
261
|
-
expect(bodyXMLToTree(xml, tags)).toMatchInlineSnapshot(`
|
|
265
|
+
expect(bodyXMLToTree(xml, tags, mockContext)).toMatchInlineSnapshot(`
|
|
262
266
|
Object {
|
|
263
267
|
"children": Array [
|
|
264
268
|
Object {
|
|
@@ -286,19 +290,12 @@ describe('bodyXMLToTree', () => {
|
|
|
286
290
|
"version": 1,
|
|
287
291
|
}
|
|
288
292
|
`)
|
|
289
|
-
expect(
|
|
290
|
-
expect(
|
|
293
|
+
expect(mockLogError).toBeCalled()
|
|
294
|
+
expect(mockLogError.mock.lastCall).toMatchInlineSnapshot(`
|
|
291
295
|
Array [
|
|
292
296
|
Object {
|
|
293
|
-
"
|
|
294
|
-
"layout-slot",
|
|
295
|
-
"paragraph",
|
|
296
|
-
],
|
|
297
|
-
"code": "BODY_XML_UNEXPECTED_STRUCTURE",
|
|
298
|
-
"error": [Error],
|
|
297
|
+
"error": [OperationalError: Unexpected children types for layout],
|
|
299
298
|
"event": "RECOVERABLE_ERROR",
|
|
300
|
-
"expected": "layout-slot",
|
|
301
|
-
"message": "Unexpected children types for layout",
|
|
302
299
|
},
|
|
303
300
|
]
|
|
304
301
|
`)
|
|
@@ -308,7 +305,7 @@ describe('bodyXMLToTree', () => {
|
|
|
308
305
|
const xml =
|
|
309
306
|
'<body><div class="n-content-layout"><div class="n-content-layout__slot"></div><p></p></body>'
|
|
310
307
|
|
|
311
|
-
expect(bodyXMLToTree(xml, tags)).toMatchInlineSnapshot(`
|
|
308
|
+
expect(bodyXMLToTree(xml, tags, mockContext)).toMatchInlineSnapshot(`
|
|
312
309
|
Object {
|
|
313
310
|
"children": Array [
|
|
314
311
|
Object {
|
|
@@ -331,19 +328,12 @@ describe('bodyXMLToTree', () => {
|
|
|
331
328
|
"version": 1,
|
|
332
329
|
}
|
|
333
330
|
`)
|
|
334
|
-
expect(
|
|
335
|
-
expect(
|
|
331
|
+
expect(mockLogError).toBeCalled()
|
|
332
|
+
expect(mockLogError.mock.lastCall).toMatchInlineSnapshot(`
|
|
336
333
|
Array [
|
|
337
334
|
Object {
|
|
338
|
-
"
|
|
339
|
-
"layout-slot",
|
|
340
|
-
"paragraph",
|
|
341
|
-
],
|
|
342
|
-
"code": "BODY_XML_UNEXPECTED_STRUCTURE",
|
|
343
|
-
"error": [Error],
|
|
335
|
+
"error": [OperationalError: Unexpected children types for layout],
|
|
344
336
|
"event": "RECOVERABLE_ERROR",
|
|
345
|
-
"expected": "layout-slot",
|
|
346
|
-
"message": "Unexpected children types for layout",
|
|
347
337
|
},
|
|
348
338
|
]
|
|
349
339
|
`)
|
|
@@ -355,7 +345,7 @@ describe('bodyXMLToTree', () => {
|
|
|
355
345
|
const xml =
|
|
356
346
|
'<table class="data-table" data-table-collapse-rownum="" data-table-layout-largescreen="auto" data-table-layout-smallscreen="auto" data-table-theme="auto"><caption>Nulla iaculis tempus augue</caption><thead><tr><th data-column-hidden="none" data-column-sortable="false" data-column-type="string">libero mollis</th><th data-column-hidden="none" data-column-sortable="false" data-column-type="string">pretium nunc</th><th data-column-hidden="none" data-column-sortable="false" data-column-type="string">euismod nunc</th></tr></thead><tbody><tr><td>Aenean </td><td>14134</td><td>dfdsfd</td></tr><tr><td>lobortis </td><td>3434</td><td>fdsf dsf </td></tr><tr><td>volutpat </td><td>234234</td><td>sd fsd</td></tr><tr><td>vitae </td><td>2423</td><td>s fsdf</td></tr><tr><td>elementumus</td><td>23423</td><td>f sdf</td></tr></tbody><tfoot><tr><td colspan="1000">Aenean sodales sapien</td></tr></tfoot></table>'
|
|
357
347
|
|
|
358
|
-
expect(bodyXMLToTree(xml, tags)).toMatchInlineSnapshot(`
|
|
348
|
+
expect(bodyXMLToTree(xml, tags, mockContext)).toMatchInlineSnapshot(`
|
|
359
349
|
Object {
|
|
360
350
|
"children": Array [
|
|
361
351
|
Object {
|
|
@@ -614,7 +604,7 @@ describe('bodyXMLToTree', () => {
|
|
|
614
604
|
const xml =
|
|
615
605
|
'<table class="data-table" id="U1140244733565W0C"><caption>Emerging markets outlook for 2017</caption><tbody><tr><td colspan="2"><p>Brazil</p><p>Brazilian shares were the best-performing asset globally over the 12 months to the end of January, returning 121 per cent, according to data from BofA Merrill Lynch. Brazilian stocks did very well through January, but investors including Lazard and Eastspring have scaled back their exposure after strong growth last year.</p></td></tr></tbody></table>'
|
|
616
606
|
|
|
617
|
-
expect(bodyXMLToTree(xml, tags)).toMatchInlineSnapshot(`
|
|
607
|
+
expect(bodyXMLToTree(xml, tags, mockContext)).toMatchInlineSnapshot(`
|
|
618
608
|
Object {
|
|
619
609
|
"children": Array [
|
|
620
610
|
Object {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { ContentTree } from '@financial-times/content-tree'
|
|
2
1
|
import * as cheerio from 'cheerio'
|
|
3
|
-
import { isTag, isText } from 'domhandler'
|
|
4
2
|
|
|
3
|
+
import type { ContentTree } from '@financial-times/content-tree'
|
|
5
4
|
import { AnyNode } from './Workarounds'
|
|
5
|
+
import { QueryContext } from '../..'
|
|
6
|
+
import { isTag, isText } from 'domhandler'
|
|
6
7
|
|
|
7
8
|
function isNode(node: ContentTree.Node | undefined): node is ContentTree.Node {
|
|
8
9
|
return Boolean(node && 'type' in node)
|
|
@@ -10,7 +11,8 @@ function isNode(node: ContentTree.Node | undefined): node is ContentTree.Node {
|
|
|
10
11
|
|
|
11
12
|
type ContentTreeTransform = (
|
|
12
13
|
$el: cheerio.Cheerio<any>,
|
|
13
|
-
traverse: () => AnyNode[]
|
|
14
|
+
traverse: () => AnyNode[],
|
|
15
|
+
context?: QueryContext
|
|
14
16
|
) => AnyNode | AnyNode[]
|
|
15
17
|
|
|
16
18
|
export type TagMappings = Record<string, ContentTreeTransform>
|
|
@@ -21,7 +23,8 @@ export type TagMappings = Record<string, ContentTreeTransform>
|
|
|
21
23
|
*/
|
|
22
24
|
export default function bodyXMLToTree(
|
|
23
25
|
xml: string,
|
|
24
|
-
tagMappings: TagMappings
|
|
26
|
+
tagMappings: TagMappings,
|
|
27
|
+
context?: QueryContext
|
|
25
28
|
): ContentTree.Body {
|
|
26
29
|
const $ = cheerio.load(xml)
|
|
27
30
|
|
|
@@ -41,8 +44,10 @@ export default function bodyXMLToTree(
|
|
|
41
44
|
|
|
42
45
|
if (matchedSelector) {
|
|
43
46
|
const contentTreeTransform = tagMappings[matchedSelector]
|
|
44
|
-
return contentTreeTransform(
|
|
45
|
-
|
|
47
|
+
return contentTreeTransform(
|
|
48
|
+
$(node),
|
|
49
|
+
() => flattenAndTraverseChildren(node.children),
|
|
50
|
+
context
|
|
46
51
|
)
|
|
47
52
|
}
|
|
48
53
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import
|
|
1
|
+
import { OperationalError } from '@dotcom-reliability-kit/errors'
|
|
2
|
+
import { AnyNode } from './Workarounds'
|
|
3
|
+
import { QueryContext } from '../..'
|
|
4
4
|
|
|
5
5
|
type ValuesOfTuple<Tuple extends readonly string[]> = Tuple[number]
|
|
6
6
|
|
|
@@ -19,39 +19,24 @@ export const phrasingTypes = [
|
|
|
19
19
|
'link',
|
|
20
20
|
] as const
|
|
21
21
|
|
|
22
|
-
export interface PredicateError {
|
|
23
|
-
event: string
|
|
24
|
-
code: string
|
|
25
|
-
message: string
|
|
26
|
-
expected: AnyNode['type'] | readonly AnyNode['type'][]
|
|
27
|
-
actual: AnyNode['type'][]
|
|
28
|
-
error: Error
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// reliability-kit's logger creates a worker thread under the hood but we can't
|
|
32
|
-
// access this thread directly from these worker threads. Instead let's send
|
|
33
|
-
// all our recoverable errors to the piscina pool managing us.
|
|
34
|
-
const postLoggerError = (error: Omit<PredicateError, 'error'>) => {
|
|
35
|
-
// stacks will only copy over if they're in an Error object
|
|
36
|
-
const errorWithStack = { ...error, error: new Error() }
|
|
37
|
-
parentPort?.postMessage(errorWithStack)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
22
|
export const findChildOftype = <NodeType extends AnyNode>(
|
|
41
23
|
type: NodeType['type'],
|
|
42
24
|
nodes: AnyNode[],
|
|
43
|
-
parentType: AnyNode['type']
|
|
25
|
+
parentType: AnyNode['type'],
|
|
26
|
+
context?: QueryContext
|
|
44
27
|
): NodeType | undefined => {
|
|
45
28
|
const predicate = (node: AnyNode): node is NodeType => node.type === type
|
|
46
29
|
const child = nodes.find(predicate)
|
|
47
30
|
|
|
48
31
|
if (!child) {
|
|
49
|
-
|
|
32
|
+
context?.logger.error({
|
|
50
33
|
event: 'RECOVERABLE_ERROR',
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
34
|
+
error: new OperationalError({
|
|
35
|
+
code: 'BODY_XML_UNEXPECTED_STRUCTURE',
|
|
36
|
+
message: `Didn't find expected child type in ${parentType}`,
|
|
37
|
+
expected: type,
|
|
38
|
+
actual: nodes.map((node) => node.type),
|
|
39
|
+
}),
|
|
55
40
|
})
|
|
56
41
|
}
|
|
57
42
|
|
|
@@ -61,18 +46,21 @@ export const findChildOftype = <NodeType extends AnyNode>(
|
|
|
61
46
|
export const everyChildIsType = <NodeType extends AnyNode>(
|
|
62
47
|
type: NodeType['type'],
|
|
63
48
|
nodes: AnyNode[],
|
|
64
|
-
parentType: AnyNode['type']
|
|
49
|
+
parentType: AnyNode['type'],
|
|
50
|
+
context?: QueryContext
|
|
65
51
|
): NodeType[] => {
|
|
66
52
|
const predicate = (node: AnyNode): node is NodeType => node.type === type
|
|
67
53
|
const allChildrenAreType = nodes.every(predicate)
|
|
68
54
|
|
|
69
55
|
if (!allChildrenAreType) {
|
|
70
|
-
|
|
56
|
+
context?.logger.error({
|
|
71
57
|
event: 'RECOVERABLE_ERROR',
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
58
|
+
error: new OperationalError({
|
|
59
|
+
code: 'BODY_XML_UNEXPECTED_STRUCTURE',
|
|
60
|
+
message: `Unexpected children types for ${parentType}`,
|
|
61
|
+
expected: type,
|
|
62
|
+
actual: nodes.map((node) => node.type),
|
|
63
|
+
}),
|
|
76
64
|
})
|
|
77
65
|
}
|
|
78
66
|
|
|
@@ -82,19 +70,22 @@ export const everyChildIsType = <NodeType extends AnyNode>(
|
|
|
82
70
|
export const childrenOfTypes = <Types extends readonly AnyNode['type'][]>(
|
|
83
71
|
types: Types,
|
|
84
72
|
nodes: AnyNode[],
|
|
85
|
-
parentType: AnyNode['type']
|
|
73
|
+
parentType: AnyNode['type'],
|
|
74
|
+
context?: QueryContext
|
|
86
75
|
): NodeOfType<ValuesOfTuple<Types>>[] => {
|
|
87
76
|
const predicate = (node: AnyNode): node is NodeOfType<ValuesOfTuple<Types>> =>
|
|
88
77
|
types.includes(node.type)
|
|
89
78
|
const allChildrenAreType = nodes.every(predicate)
|
|
90
79
|
|
|
91
80
|
if (!allChildrenAreType) {
|
|
92
|
-
|
|
81
|
+
context?.logger.error({
|
|
93
82
|
event: 'RECOVERABLE_ERROR',
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
83
|
+
error: new OperationalError({
|
|
84
|
+
code: 'BODY_XML_UNEXPECTED_STRUCTURE',
|
|
85
|
+
message: `Unexpected ordered children types for ${parentType}`,
|
|
86
|
+
expected: types,
|
|
87
|
+
actual: nodes.map((node) => node.type),
|
|
88
|
+
}),
|
|
98
89
|
})
|
|
99
90
|
}
|
|
100
91
|
|
|
@@ -119,18 +110,21 @@ export const childrenOfOrderedTypes = <
|
|
|
119
110
|
>(
|
|
120
111
|
types: Types,
|
|
121
112
|
nodes: AnyNode[],
|
|
122
|
-
parentType: AnyNode['type']
|
|
113
|
+
parentType: AnyNode['type'],
|
|
114
|
+
context?: QueryContext
|
|
123
115
|
): NodesOfTypes<Types> => {
|
|
124
116
|
if (nodesAreOrderedTypes(types, nodes)) {
|
|
125
117
|
return nodes
|
|
126
118
|
}
|
|
127
119
|
|
|
128
|
-
|
|
120
|
+
context?.logger.error({
|
|
129
121
|
event: 'RECOVERABLE_ERROR',
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
122
|
+
error: new OperationalError({
|
|
123
|
+
code: 'BODY_XML_UNEXPECTED_STRUCTURE',
|
|
124
|
+
message: `Unexpected children types for ${parentType}`,
|
|
125
|
+
expected: types,
|
|
126
|
+
actual: nodes.map((node) => node.type),
|
|
127
|
+
}),
|
|
134
128
|
})
|
|
135
129
|
|
|
136
130
|
return nodes as unknown as NodesOfTypes<Types>
|