@financial-times/cp-content-pipeline-schema 0.4.2 → 0.4.3
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 +15 -0
- package/lib/datasources/capi.d.ts +1 -0
- package/lib/datasources/capi.js +33 -1
- package/lib/datasources/capi.js.map +1 -1
- package/lib/datasources/instrumented.d.ts +3 -0
- package/lib/datasources/instrumented.js +13 -0
- package/lib/datasources/instrumented.js.map +1 -1
- package/lib/datasources/origami-image.d.ts +1 -0
- package/lib/datasources/origami-image.js +1 -0
- package/lib/datasources/origami-image.js.map +1 -1
- package/lib/datasources/schemas/capi.d.ts +1284 -0
- package/lib/datasources/schemas/capi.js +130 -0
- package/lib/datasources/schemas/capi.js.map +1 -0
- package/lib/datasources/twitter.d.ts +1 -0
- package/lib/datasources/twitter.js +1 -0
- package/lib/datasources/twitter.js.map +1 -1
- package/lib/model/CapiResponse.d.ts +160 -5
- package/package.json +4 -2
- package/src/datasources/capi.ts +42 -3
- package/src/datasources/instrumented.ts +15 -0
- package/src/datasources/origami-image.ts +2 -0
- package/src/datasources/schemas/capi.ts +140 -0
- package/src/datasources/twitter.ts +1 -0
- package/src/types/internal-content.d.ts +19 -108
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.3](https://github.com/Financial-Times/cp-content-pipeline/compare/cp-content-pipeline-schema-v0.4.2...cp-content-pipeline-schema-v0.4.3) (2022-11-17)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add relatesToSystems to datasource errors ([07c4735](https://github.com/Financial-Times/cp-content-pipeline/commit/07c47359718a6f69b7f5b2f24b813d8f18724dbd))
|
|
9
|
+
* replace CAPI types with types inferred from zod schema ([9f08a78](https://github.com/Financial-Times/cp-content-pipeline/commit/9f08a7817a466fd0104005a9a8bd35901aceac77))
|
|
10
|
+
* track incoming data validation failures/success ([916a5ed](https://github.com/Financial-Times/cp-content-pipeline/commit/916a5ed677092a1a8ac6360ea1f50d37582e2af2))
|
|
11
|
+
* validate incoming CAPI data against schema ([6730205](https://github.com/Financial-Times/cp-content-pipeline/commit/67302057661e55b44f93b75054769d1058ce9c68))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* update error log to use 'cause' ([d08c739](https://github.com/Financial-Times/cp-content-pipeline/commit/d08c739913219009f631a9930ef337d93a6f4a84))
|
|
17
|
+
|
|
3
18
|
## [0.4.2](https://github.com/Financial-Times/cp-content-pipeline/compare/cp-content-pipeline-schema-v0.4.1...cp-content-pipeline-schema-v0.4.2) (2022-11-10)
|
|
4
19
|
|
|
5
20
|
|
|
@@ -8,6 +8,7 @@ export declare class CapiDataSource extends InstrumentedRESTDataSource {
|
|
|
8
8
|
capiKey: string;
|
|
9
9
|
articleCacheTTL: number;
|
|
10
10
|
peopleCacheTTL: number;
|
|
11
|
+
backendSystemCodes: string[];
|
|
11
12
|
willSendRequest(request: WillSendRequestOptions): void;
|
|
12
13
|
protected cacheOptionsFor(url: string): CacheOptions | undefined;
|
|
13
14
|
getContent(uuid: string): Promise<CapiResponse>;
|
package/lib/datasources/capi.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { CapiResponse } from '../model/CapiResponse.js';
|
|
2
2
|
import { InstrumentedRESTDataSource } from './instrumented.js';
|
|
3
|
+
import { InternalContentSchema } from './schemas/capi.js';
|
|
4
|
+
import { OperationalError } from '@dotcom-reliability-kit/errors';
|
|
5
|
+
import { logRecoverableError } from '@dotcom-reliability-kit/log-error';
|
|
3
6
|
export class CapiDataSource extends InstrumentedRESTDataSource {
|
|
4
7
|
constructor() {
|
|
5
8
|
super(...arguments);
|
|
@@ -11,6 +14,7 @@ export class CapiDataSource extends InstrumentedRESTDataSource {
|
|
|
11
14
|
this.peopleCacheTTL = process.env.PEOPLE_CACHE_TTL
|
|
12
15
|
? parseInt(process.env.PEOPLE_CACHE_TTL)
|
|
13
16
|
: 600; // 10 minutes
|
|
17
|
+
this.backendSystemCodes = ['up-ica'];
|
|
14
18
|
}
|
|
15
19
|
willSendRequest(request) {
|
|
16
20
|
request.headers['x-api-key'] = this.capiKey;
|
|
@@ -24,7 +28,35 @@ export class CapiDataSource extends InstrumentedRESTDataSource {
|
|
|
24
28
|
}
|
|
25
29
|
}
|
|
26
30
|
async getContent(uuid) {
|
|
27
|
-
return this.get(`internalcontent/${uuid}`).then((response) =>
|
|
31
|
+
return this.get(`internalcontent/${uuid}`).then((response) => {
|
|
32
|
+
var _a, _b;
|
|
33
|
+
/*
|
|
34
|
+
* Check if the incoming data matches the types defined in our data source schema
|
|
35
|
+
* Our data source schema should handle all possible response from CAPI
|
|
36
|
+
* As there is no agreed schema provided by CAPI there is a chance it will out of date / incorrect at times
|
|
37
|
+
* Manual updates will be required at times to keep it in sync with the responses we receive
|
|
38
|
+
*/
|
|
39
|
+
const schemaResponse = InternalContentSchema.safeParse(response);
|
|
40
|
+
/*
|
|
41
|
+
* Log an error if the response fails validation
|
|
42
|
+
* Logs should be used to warn us of issues or response changes
|
|
43
|
+
* Don't let this failure block this system from continuing to try and handle the request
|
|
44
|
+
*/
|
|
45
|
+
if (!schemaResponse.success) {
|
|
46
|
+
logRecoverableError({
|
|
47
|
+
error: new OperationalError({
|
|
48
|
+
message: 'The data received from the CAPI data source does not match our data source schema. It is likely that our schema will require updating to handle all possible responses from CAPI.',
|
|
49
|
+
code: 'CAPI_SCHEMA_VALIDATION_FAILURE',
|
|
50
|
+
cause: schemaResponse.error,
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
(_a = this.context.metrics) === null || _a === void 0 ? void 0 : _a.count(`graphql.datasource.${this.constructor.name}.validation.failure.count`, 1);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
(_b = this.context.metrics) === null || _b === void 0 ? void 0 : _b.count(`graphql.datasource.${this.constructor.name}.validation.success.count`, 1);
|
|
57
|
+
}
|
|
58
|
+
return new CapiResponse(response, this.context);
|
|
59
|
+
});
|
|
28
60
|
}
|
|
29
61
|
async getPerson(uuid) {
|
|
30
62
|
return this.get(`people/${uuid}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capi.js","sourceRoot":"","sources":["../../src/datasources/capi.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"capi.js","sourceRoot":"","sources":["../../src/datasources/capi.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAA;AAE9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAA;AAEvE,MAAM,OAAO,cAAe,SAAQ,0BAA0B;IAA9D;;QACE,YAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,uBAAuB,CAAA;QACzD,YAAO,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE,CAAA;QACxC,oBAAe,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB;YAC7C,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;YACzC,CAAC,CAAC,EAAE,CAAA,CAAC,aAAa;QACpB,mBAAc,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB;YAC3C,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;YACxC,CAAC,CAAC,GAAG,CAAA,CAAC,aAAa;QAErB,uBAAkB,GAAG,CAAC,QAAQ,CAAC,CAAA;IAyDjC,CAAC;IAvDU,eAAe,CAAC,OAA+B;QACtD,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC,OAAO,CAAA;IAC7C,CAAC;IAES,eAAe,CAAC,GAAW;QACnC,IAAI,GAAG,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE;YACtC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,CAAA;SACrC;aAAM,IAAI,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE;YACpC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,CAAA;SACpC;IACH,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,IAAY;QAC3B,OAAO,IAAI,CAAC,GAAG,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;;YAC3D;;;;;eAKG;YACH,MAAM,cAAc,GAAG,qBAAqB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;YAEhE;;;;eAIG;YACH,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;gBAC3B,mBAAmB,CAAC;oBAClB,KAAK,EAAE,IAAI,gBAAgB,CAAC;wBAC1B,OAAO,EACL,mLAAmL;wBACrL,IAAI,EAAE,gCAAgC;wBACtC,KAAK,EAAE,cAAc,CAAC,KAAK;qBAC5B,CAAC;iBACH,CAAC,CAAA;gBAEF,MAAA,IAAI,CAAC,OAAO,CAAC,OAAO,0CAAE,KAAK,CACzB,sBAAsB,IAAI,CAAC,WAAW,CAAC,IAAI,2BAA2B,EACtE,CAAC,CACF,CAAA;aACF;iBAAM;gBACL,MAAA,IAAI,CAAC,OAAO,CAAC,OAAO,0CAAE,KAAK,CACzB,sBAAsB,IAAI,CAAC,WAAW,CAAC,IAAI,2BAA2B,EACtE,CAAC,CACF,CAAA;aACF;YAED,OAAO,IAAI,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAA;QACjD,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,IAAY;QAC1B,OAAO,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAA;IACnC,CAAC;CACF"}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { WillSendRequestOptions, RESTDataSource, RequestOptions } from '@apollo/datasource-rest';
|
|
2
2
|
import type { FetcherResponse } from '@apollo/utils.fetcher';
|
|
3
|
+
import { GraphQLError } from 'graphql';
|
|
3
4
|
import { QueryContext } from '../index.js';
|
|
4
5
|
import { BaseDataSource, BaseDataSourceOptions } from './base.js';
|
|
5
6
|
export declare class InstrumentedRESTDataSource extends RESTDataSource implements BaseDataSource {
|
|
6
7
|
startTime?: bigint;
|
|
7
8
|
includeHeadersInLogs: string[];
|
|
9
|
+
backendSystemCodes: string[];
|
|
8
10
|
context: QueryContext;
|
|
9
11
|
constructor({ cache, context }: BaseDataSourceOptions);
|
|
10
12
|
get cachePrefix(): string;
|
|
11
13
|
trace<TResult>(url: URL, request: WillSendRequestOptions, fn: () => Promise<TResult>): Promise<TResult>;
|
|
12
14
|
didReceiveResponse<TResult>(response: FetcherResponse, request: RequestOptions): Promise<TResult>;
|
|
15
|
+
errorFromResponse(response: FetcherResponse): Promise<GraphQLError>;
|
|
13
16
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { RESTDataSource, } from '@apollo/datasource-rest';
|
|
2
2
|
import { PrefixingKeyValueCache } from '@apollo/utils.keyvaluecache';
|
|
3
|
+
import { HttpError } from '@dotcom-reliability-kit/errors';
|
|
3
4
|
import { isContextableCache } from '../types/cache.js';
|
|
4
5
|
export class InstrumentedRESTDataSource extends RESTDataSource {
|
|
5
6
|
constructor({ cache, context }) {
|
|
@@ -9,6 +10,7 @@ export class InstrumentedRESTDataSource extends RESTDataSource {
|
|
|
9
10
|
cache: wrappedCache,
|
|
10
11
|
});
|
|
11
12
|
this.includeHeadersInLogs = [];
|
|
13
|
+
this.backendSystemCodes = [];
|
|
12
14
|
// okay _now_ we can use `this`
|
|
13
15
|
// also that's a private property so use the [] escape hatch
|
|
14
16
|
// sorry
|
|
@@ -46,5 +48,16 @@ export class InstrumentedRESTDataSource extends RESTDataSource {
|
|
|
46
48
|
(_b = this.context.metrics) === null || _b === void 0 ? void 0 : _b.count(`graphql.datasource.${this.constructor.name}.response.${response.status}.time`, Number(duration));
|
|
47
49
|
return super.didReceiveResponse(response, request);
|
|
48
50
|
}
|
|
51
|
+
// HACK:KB:20221115 this needs to return a GraphQLError, but i want to return
|
|
52
|
+
// a different kind of error, so i'm telling Typescript we return one but just
|
|
53
|
+
// throwing the error I want instead. it just gets thrown by the method that
|
|
54
|
+
// calls this anyway
|
|
55
|
+
async errorFromResponse(response) {
|
|
56
|
+
throw new HttpError({
|
|
57
|
+
message: `${response.status}: ${response.statusText} from ${this.constructor.name}`,
|
|
58
|
+
statusCode: response.status,
|
|
59
|
+
relatesToSystems: this.backendSystemCodes,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
49
62
|
}
|
|
50
63
|
//# sourceMappingURL=instrumented.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumented.js","sourceRoot":"","sources":["../../src/datasources/instrumented.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,cAAc,GAEf,MAAM,yBAAyB,CAAA;AAEhC,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;
|
|
1
|
+
{"version":3,"file":"instrumented.js","sourceRoot":"","sources":["../../src/datasources/instrumented.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,cAAc,GAEf,MAAM,yBAAyB,CAAA;AAEhC,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AAEpE,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAA;AAG1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAGtD,MAAM,OAAO,0BACX,SAAQ,cAAc;IAQtB,YAAY,EAAE,KAAK,EAAE,OAAO,EAAyB;QACnD,MAAM,YAAY,GAAG,IAAI,sBAAsB,CAC7C,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,EAC9D,sEAAsE,CAAC,yCAAyC;SACjH,CAAA;QAED,KAAK,CAAC;YACJ,KAAK,EAAE,YAAY;SACpB,CAAC,CAAA;QAZJ,yBAAoB,GAAa,EAAE,CAAA;QACnC,uBAAkB,GAAa,EAAE,CAAA;QAa/B,+BAA+B;QAC/B,4DAA4D;QAC5D,QAAQ;QACR,YAAY,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,WAAW,GAAG,GAAG,CAAA;QAC/C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IACxB,CAAC;IAED,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;IACvE,CAAC;IAEQ,KAAK,CAAC,KAAK,CAClB,GAAQ,EACR,OAA+B,EAC/B,EAA0B;;QAE1B,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAA;QAExC,MAAA,IAAI,CAAC,OAAO,CAAC,OAAO,0CAAE,KAAK,CACzB,sBAAsB,IAAI,CAAC,WAAW,CAAC,IAAI,gBAAgB,EAC3D,CAAC,CACF,CAAA;QACD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;YACvB,KAAK,EAAE,oBAAoB;YAC3B,UAAU,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI;YACjC,OAAO,EAAE;gBACP,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS;gBAC1B,GAAG,EAAE,GAAG,CAAC,IAAI;gBACb,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,OAAO,EAAE,OAAO,CAAC,OAAO;aACJ;SACvB,CAAC,CAAA;QAEF,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE;YAC1B,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAA;SACzD;QAED,OAAO,EAAE,EAAE,CAAA;IACb,CAAC;IAED,KAAK,CAAC,kBAAkB,CACtB,QAAyB,EACzB,OAAuB;;QAEvB,wGAAwG;QACxG,oEAAoE;QACpE,MAAM,QAAQ,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,SAAU,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;QAE1E,MAAA,IAAI,CAAC,OAAO,CAAC,OAAO,0CAAE,KAAK,CACzB,sBAAsB,IAAI,CAAC,WAAW,CAAC,IAAI,aAAa,QAAQ,CAAC,MAAM,QAAQ,EAC/E,CAAC,CACF,CAAA;QACD,MAAA,IAAI,CAAC,OAAO,CAAC,OAAO,0CAAE,KAAK,CACzB,sBAAsB,IAAI,CAAC,WAAW,CAAC,IAAI,aAAa,QAAQ,CAAC,MAAM,OAAO,EAC9E,MAAM,CAAC,QAAQ,CAAC,CACjB,CAAA;QAED,OAAO,KAAK,CAAC,kBAAkB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IACpD,CAAC;IAED,6EAA6E;IAC7E,8EAA8E;IAC9E,4EAA4E;IAC5E,oBAAoB;IACpB,KAAK,CAAC,iBAAiB,CAAC,QAAyB;QAC/C,MAAM,IAAI,SAAS,CAAC;YAClB,OAAO,EAAE,GAAG,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,UAAU,SAAS,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE;YACnF,UAAU,EAAE,QAAQ,CAAC,MAAM;YAC3B,gBAAgB,EAAE,IAAI,CAAC,kBAAkB;SAC1C,CAAC,CAAA;IACJ,CAAC;CACF"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { InstrumentedRESTDataSource } from './instrumented.js';
|
|
2
2
|
export declare class OrigamiImageDataSource extends InstrumentedRESTDataSource {
|
|
3
3
|
baseURL: string;
|
|
4
|
+
backendSystemCodes: string[];
|
|
4
5
|
getImageMetadata(url: string): Promise<{
|
|
5
6
|
width: number;
|
|
6
7
|
height: number;
|
|
@@ -3,6 +3,7 @@ export class OrigamiImageDataSource extends InstrumentedRESTDataSource {
|
|
|
3
3
|
constructor() {
|
|
4
4
|
super(...arguments);
|
|
5
5
|
this.baseURL = 'https://www.ft.com/__origami/service/image/v2';
|
|
6
|
+
this.backendSystemCodes = ['origami-image-service-v2'];
|
|
6
7
|
}
|
|
7
8
|
async getImageMetadata(url) {
|
|
8
9
|
return this.get(`/images/metadata/${encodeURIComponent(url)}?source=next`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"origami-image.js","sourceRoot":"","sources":["../../src/datasources/origami-image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAA;AAE9D,MAAM,OAAO,sBAAuB,SAAQ,0BAA0B;IAAtE;;QACE,YAAO,GAAG,+CAA+C,CAAA;
|
|
1
|
+
{"version":3,"file":"origami-image.js","sourceRoot":"","sources":["../../src/datasources/origami-image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAA;AAE9D,MAAM,OAAO,sBAAuB,SAAQ,0BAA0B;IAAtE;;QACE,YAAO,GAAG,+CAA+C,CAAA;QACzD,uBAAkB,GAAG,CAAC,0BAA0B,CAAC,CAAA;IAOnD,CAAC;IALC,KAAK,CAAC,gBAAgB,CACpB,GAAW;QAEX,OAAO,IAAI,CAAC,GAAG,CAAC,oBAAoB,kBAAkB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;IAC5E,CAAC;CACF"}
|