Skip to content

Commit e659f42

Browse files
committed
feat: this brings schema-utils into parity with
the refactoring of the dereferr and meta-schema additionally the refactor will help support the allowing custom dereferences from different protocol. In this pr that functionality is not exposed, but in subsequent it will be.
1 parent b3e6711 commit e659f42

10 files changed

+9448
-91
lines changed

package-lock.json

Lines changed: 9293 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@
3030
},
3131
"homepage": "https://github.com/open-rpc/schema-utils-js#readme",
3232
"dependencies": {
33-
"@json-schema-tools/dereferencer": "^1.4.0",
34-
"@json-schema-tools/meta-schema": "^1.5.10",
35-
"@json-schema-tools/reference-resolver": "^1.1.1",
36-
"@open-rpc/meta-schema": "^1.14.0",
33+
"@json-schema-tools/dereferencer": "1.5.1",
34+
"@json-schema-tools/meta-schema": "^1.6.10",
35+
"@json-schema-tools/reference-resolver": "^1.2.1",
36+
"@open-rpc/meta-schema": "1.14.2",
3737
"ajv": "^6.10.0",
3838
"detect-node": "^2.0.4",
3939
"fast-safe-stringify": "^2.0.7",

src/dereference-document.test.ts

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as _fs from "fs-extra";
22
import dereferenceDocument, { OpenRPCDocumentDereferencingError } from "./dereference-document";
3-
import { OpenrpcDocument, ContentDescriptorObject, JSONSchema } from "@open-rpc/meta-schema";
3+
import defaultResolver from "@json-schema-tools/reference-resolver";
4+
import { OpenrpcDocument, ContentDescriptorObject, JSONSchema, MethodObject } from "@open-rpc/meta-schema";
45
import { JSONSchemaObject } from "@json-schema-tools/meta-schema";
56

67

@@ -17,7 +18,7 @@ describe("dereferenceDocument", () => {
1718

1819
it("doesnt explode", async () => {
1920
expect.assertions(1);
20-
const document = await dereferenceDocument(workingDocument);
21+
const document = await dereferenceDocument(workingDocument, defaultResolver);
2122
expect(document.methods).toBeDefined();
2223
});
2324

@@ -32,14 +33,24 @@ describe("dereferenceDocument", () => {
3233
{ name: "abc", params: [], result: { name: "cba", schema: { type: "number" } } }
3334
],
3435
openrpc: "1.0.0-rc1",
35-
});
36+
}, defaultResolver);
3637
expect(document.methods).toBeDefined();
3738
});
3839

3940
it("derefs simple stuff", async () => {
4041
expect.assertions(7);
4142
const testDoc = {
4243
...workingDocument,
44+
"x-methods": {
45+
foobar: {
46+
name: "foobar",
47+
params: [],
48+
result: {
49+
name: "abcfoo",
50+
schema: { type: "number" }
51+
}
52+
}
53+
},
4354
components: {
4455
schemas: {
4556
bigOlBaz: { $ref: "#/components/schemas/bigOlFoo" },
@@ -91,15 +102,17 @@ describe("dereferenceDocument", () => {
91102
schema: { $ref: "#/components/schemas/bigOlFoo" }
92103
}
93104
});
94-
95-
const document = await dereferenceDocument(testDoc);
96-
expect(document.methods).toBeDefined();
97-
expect(document.methods[0]).toBeDefined();
98-
expect(document.methods[0].params[0]).toBeDefined();
99-
expect((document.methods[0].params[0] as ContentDescriptorObject).name).toBe("bazerino");
100-
expect(document.methods[0].result).toBeDefined();
101-
expect(((document.methods[0].result as ContentDescriptorObject).schema as JSONSchemaObject).title).toBe("bigOlFoo");
102-
expect(((document.methods[0].params as ContentDescriptorObject[])[1].schema as JSONSchemaObject).title).toBe("bigOlFoo");
105+
testDoc.methods.push({"$ref":"#/x-methods/foobar"})
106+
107+
const document = await dereferenceDocument(testDoc, defaultResolver);
108+
const docMethods = document.methods as MethodObject[];
109+
expect(docMethods).toBeDefined();
110+
expect(docMethods[0]).toBeDefined();
111+
expect(docMethods[0].params[0]).toBeDefined();
112+
expect((docMethods[0].params[0] as ContentDescriptorObject).name).toBe("bazerino");
113+
expect(docMethods[0].result).toBeDefined();
114+
expect(((docMethods[0].result as ContentDescriptorObject).schema as JSONSchemaObject).title).toBe("bigOlFoo");
115+
expect(((docMethods[0].params as ContentDescriptorObject[])[1].schema as JSONSchemaObject).title).toBe("bigOlFoo");
103116
});
104117

105118
it("interdependent refs", async () => {
@@ -145,12 +158,12 @@ describe("dereferenceDocument", () => {
145158
}
146159
} as OpenrpcDocument;
147160

148-
const document = await dereferenceDocument(testDoc);
161+
const document = await dereferenceDocument(testDoc, defaultResolver);
149162
expect(document.methods).toBeDefined();
150163
expect(document.methods[0]).toBeDefined();
151164

152-
const params = document.methods[0].params as ContentDescriptorObject[];
153-
const result = document.methods[0].result as ContentDescriptorObject;
165+
const params = (document.methods[0] as MethodObject).params as ContentDescriptorObject[];
166+
const result = (document.methods[0] as MethodObject).result as ContentDescriptorObject;
154167
expect(params).toBeDefined();
155168
expect(result).toBeDefined();
156169

@@ -191,7 +204,7 @@ describe("dereferenceDocument", () => {
191204
};
192205

193206
try {
194-
await dereferenceDocument(testDoc as OpenrpcDocument)
207+
await dereferenceDocument(testDoc as OpenrpcDocument, defaultResolver)
195208
} catch (e) {
196209
expect(e).toBeInstanceOf(OpenRPCDocumentDereferencingError);
197210
}
@@ -218,7 +231,7 @@ describe("dereferenceDocument", () => {
218231
};
219232

220233
try {
221-
await dereferenceDocument(testDoc as OpenrpcDocument)
234+
await dereferenceDocument(testDoc as OpenrpcDocument, defaultResolver)
222235
} catch (e) {
223236
expect(e).toBeInstanceOf(OpenRPCDocumentDereferencingError);
224237
}
@@ -248,7 +261,7 @@ describe("dereferenceDocument", () => {
248261
};
249262

250263
try {
251-
await dereferenceDocument(testDoc as OpenrpcDocument)
264+
await dereferenceDocument(testDoc as OpenrpcDocument, defaultResolver)
252265
} catch (e) {
253266
expect(e).toBeInstanceOf(OpenRPCDocumentDereferencingError);
254267
}
@@ -286,7 +299,7 @@ describe("dereferenceDocument", () => {
286299
}
287300
};
288301

289-
const result = await dereferenceDocument(testDoc as OpenrpcDocument) as any;
302+
const result = await dereferenceDocument(testDoc as OpenrpcDocument, defaultResolver) as any;
290303

291304
expect(result.methods[0].links[0]).toBe(testDoc.components.links.fooLink)
292305
});
@@ -312,7 +325,7 @@ describe("dereferenceDocument", () => {
312325
]
313326
};
314327

315-
const result = await dereferenceDocument(testDoc as OpenrpcDocument) as any;
328+
const result = await dereferenceDocument(testDoc as OpenrpcDocument, defaultResolver) as any;
316329

317330
expect(result.methods[0].result.schema.type).toBe("string")
318331
});
@@ -348,7 +361,7 @@ describe("dereferenceDocument", () => {
348361
}
349362
};
350363

351-
const result = await dereferenceDocument(testDoc as OpenrpcDocument) as any;
364+
const result = await dereferenceDocument(testDoc as OpenrpcDocument, defaultResolver) as any;
352365

353366
expect(result.methods[0].result.schema.properties.foo).toBe(result.components.schemas.foo);
354367
});
@@ -386,7 +399,7 @@ describe("dereferenceDocument", () => {
386399
};
387400

388401
try {
389-
await dereferenceDocument(testDoc as OpenrpcDocument) as any;
402+
await dereferenceDocument(testDoc as OpenrpcDocument, defaultResolver) as any;
390403
} catch (e) {
391404
expect(e).toBeInstanceOf(OpenRPCDocumentDereferencingError);
392405
}

src/dereference-document.ts

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import Dereferencer from "@json-schema-tools/dereferencer";
2-
import { OpenrpcDocument as OpenRPC, ReferenceObject, ExamplePairingObject, JSONSchema, SchemaComponents, ContentDescriptorComponents, ContentDescriptorObject, OpenrpcDocument, MethodObject } from "@open-rpc/meta-schema";
2+
import { OpenrpcDocument as OpenRPC, ReferenceObject, ExamplePairingObject, JSONSchema, SchemaComponents, ContentDescriptorComponents, ContentDescriptorObject, OpenrpcDocument, MethodObject, MethodOrReference } from "@open-rpc/meta-schema";
33
import referenceResolver from "@json-schema-tools/reference-resolver";
44
import safeStringify from "fast-safe-stringify";
55

6+
export type ReferenceResolver = typeof referenceResolver
67
/**
78
* Provides an error interface for OpenRPC Document dereferencing problems
89
*
@@ -20,12 +21,13 @@ export class OpenRPCDocumentDereferencingError implements Error {
2021
}
2122
}
2223

23-
const derefItem = async (item: ReferenceObject, doc: OpenRPC) => {
24+
const derefItem = async (item: ReferenceObject, doc: OpenRPC, resolver: ReferenceResolver) => {
2425
const { $ref } = item;
2526
if ($ref === undefined) { return item; }
2627

2728
try {
28-
return await referenceResolver($ref, doc);
29+
// returns resolved value of the reference
30+
return (await resolver.resolve($ref, doc) as any);
2931
} catch (err) {
3032
throw new OpenRPCDocumentDereferencingError([
3133
`unable to eval pointer against OpenRPC Document.`,
@@ -38,10 +40,10 @@ const derefItem = async (item: ReferenceObject, doc: OpenRPC) => {
3840
}
3941
};
4042

41-
const derefItems = async (items: ReferenceObject[], doc: OpenRPC) => {
43+
const derefItems = async (items: ReferenceObject[], doc: OpenRPC, resolver: ReferenceResolver) => {
4244
const dereffed = [];
4345
for (const i of items) {
44-
dereffed.push(await derefItem(i, doc))
46+
dereffed.push(await derefItem(i, doc, resolver))
4547
}
4648
return dereffed;
4749
};
@@ -113,29 +115,35 @@ const handleSchemasInsideContentDescriptorComponents = async (doc: OpenrpcDocume
113115
return doc;
114116
};
115117

116-
const handleMethod = async (method: MethodObject, doc: OpenrpcDocument): Promise<MethodObject> => {
118+
const handleMethod = async (methodOrRef: MethodOrReference, doc: OpenrpcDocument, resolver: ReferenceResolver): Promise<MethodObject> => {
119+
let method = methodOrRef as MethodObject;
120+
121+
if(methodOrRef.$ref !== undefined){
122+
method = await derefItem({$ref: methodOrRef.$ref}, doc, resolver)
123+
}
124+
117125
if (method.tags !== undefined) {
118-
method.tags = await derefItems(method.tags as ReferenceObject[], doc);
126+
method.tags = await derefItems(method.tags as ReferenceObject[], doc, resolver);
119127
}
120128

121129
if (method.errors !== undefined) {
122-
method.errors = await derefItems(method.errors as ReferenceObject[], doc);
130+
method.errors = await derefItems(method.errors as ReferenceObject[], doc, resolver);
123131
}
124132

125133
if (method.links !== undefined) {
126-
method.links = await derefItems(method.links as ReferenceObject[], doc);
134+
method.links = await derefItems(method.links as ReferenceObject[], doc, resolver);
127135
}
128136

129137
if (method.examples !== undefined) {
130-
method.examples = await derefItems(method.examples as ReferenceObject[], doc);
138+
method.examples = await derefItems(method.examples as ReferenceObject[], doc, resolver);
131139
for (const exPairing of method.examples as ExamplePairingObject[]) {
132-
exPairing.params = await derefItems(exPairing.params as ReferenceObject[], doc);
133-
exPairing.result = await derefItem(exPairing.result as ReferenceObject, doc);
140+
exPairing.params = await derefItems(exPairing.params as ReferenceObject[], doc, resolver);
141+
exPairing.result = await derefItem(exPairing.result as ReferenceObject, doc, resolver);
134142
}
135143
}
136144

137-
method.params = await derefItems(method.params as ReferenceObject[], doc);
138-
method.result = await derefItem(method.result as ReferenceObject, doc);
145+
method.params = await derefItems(method.params as ReferenceObject[], doc, resolver);
146+
method.result = await derefItem(method.result as ReferenceObject, doc, resolver);
139147

140148

141149
let componentSchemas: SchemaComponents = {};
@@ -179,14 +187,14 @@ const handleMethod = async (method: MethodObject, doc: OpenrpcDocument): Promise
179187
* ```
180188
*
181189
*/
182-
export default async function dereferenceDocument(openrpcDocument: OpenRPC): Promise<OpenRPC> {
190+
export default async function dereferenceDocument(openrpcDocument: OpenRPC, resolver: ReferenceResolver): Promise<OpenRPC> {
183191
let derefDoc = { ...openrpcDocument };
184-
192+
185193
derefDoc = await handleSchemaComponents(derefDoc);
186194
derefDoc = await handleSchemasInsideContentDescriptorComponents(derefDoc);
187195
const methods = [] as any;
188196
for (const method of derefDoc.methods) {
189-
methods.push(await handleMethod(method, derefDoc));
197+
methods.push(await handleMethod(method, derefDoc, resolver));
190198
}
191199

192200
derefDoc.methods = methods;

src/method-call-validator/method-call-validator.test.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import MethodCallValidator from "./method-call-validator";
2-
import { OpenrpcDocument as OpenRPC, OpenrpcDocument } from "@open-rpc/meta-schema";
2+
import { MethodObject, OpenrpcDocument as OpenRPC, OpenrpcDocument } from "@open-rpc/meta-schema";
33
import MethodCallParameterValidationError from "./parameter-validation-error";
44
import MethodCallMethodNotFoundError from "./method-not-found-error";
55
import MethodNotFoundError from "./method-not-found-error";
6+
import MethodRefUnexpectedError from "./method-ref-unexpected-error";
67

78
const getExampleSchema = (): OpenRPC => ({
89
info: { title: "123", version: "1" },
@@ -29,9 +30,9 @@ describe("MethodCallValidator", () => {
2930
expect(result).toEqual([]);
3031
});
3132

32-
it("can handle having params undefined", () => {
33+
it("can handle having params empty", () => {
3334
const example = getExampleSchema();
34-
delete example.methods[0].params;
35+
(example.methods[0] as MethodObject).params = [];
3536
const methodCallValidator = new MethodCallValidator(example);
3637
const result = methodCallValidator.validate("foo", ["foobar"]);
3738
expect(result).toEqual([]);
@@ -144,4 +145,13 @@ describe("MethodCallValidator", () => {
144145
const result0 = methodCallValidator.validate("rawr", { barbar: "123" });
145146
expect(result0).toBeInstanceOf(MethodNotFoundError);
146147
});
148+
149+
it("unexpected reference error when the document has unresolved method reference passed", () => {
150+
const example = getExampleSchema() as any;
151+
example['x-methods']={ foobar: { name: "foobar", params: [],
152+
result: { name: "abcfoo", schema: { type: "number" } } } }
153+
example.methods.push({"$ref":"#/x-methods/foobar"})
154+
expect(()=>new MethodCallValidator(example)).toThrowError(MethodRefUnexpectedError);
155+
});
156+
147157
});

src/method-call-validator/method-call-validator.ts

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import Ajv, { ErrorObject, Ajv as IAjv } from "ajv";
22
import { generateMethodParamId } from "../generate-method-id";
33
import ParameterValidationError from "./parameter-validation-error";
4-
import { OpenrpcDocument as OpenRPC, MethodObject, ContentDescriptorObject } from "@open-rpc/meta-schema";
4+
import { OpenrpcDocument as OpenRPC, MethodObject, ContentDescriptorObject, MethodOrReference } from "@open-rpc/meta-schema";
55
import MethodNotFoundError from "./method-not-found-error";
66
import { find, compact } from "../helper-functions";
7+
import MethodRefUnexpectedError from "./method-ref-unexpected-error";
78

89
/**
910
* A class to assist in validating method calls to an OpenRPC-based service. Generated Clients,
@@ -28,9 +29,13 @@ export default class MethodCallValidator {
2829
constructor(private document: OpenRPC) {
2930
this.ajvValidator = new Ajv();
3031

31-
document.methods.forEach((method: MethodObject) => {
32+
// Validate that the methods are dereferenced
33+
document.methods.forEach((method: MethodOrReference) => {
34+
if (method.$ref) throw new MethodRefUnexpectedError(method.$ref, document);
35+
});
36+
37+
(document.methods as MethodObject[]).forEach((method: MethodObject) => {
3238
const params = method.params as ContentDescriptorObject[];
33-
if (method.params === undefined) { return; }
3439

3540
params.forEach((param: ContentDescriptorObject) => {
3641
if (param.schema === undefined) { return; }
@@ -70,37 +75,33 @@ export default class MethodCallValidator {
7075
return new MethodNotFoundError(methodName, this.document, params);
7176
}
7277

73-
if (method.params) {
74-
const paramMap = (method.params as ContentDescriptorObject[]);
75-
return compact(paramMap.map((param: ContentDescriptorObject, index: number): ParameterValidationError | undefined => {
76-
let id: string | number;
77-
if (method.paramStructure === "by-position") {
78+
const paramMap = (method.params as ContentDescriptorObject[]);
79+
return compact(paramMap.map((param: ContentDescriptorObject, index: number): ParameterValidationError | undefined => {
80+
let id: string | number;
81+
if (method.paramStructure === "by-position") {
82+
id = index;
83+
} else if (method.paramStructure === "by-name") {
84+
id = param.name;
85+
} else {
86+
if (params[index] !== undefined) {
7887
id = index;
79-
} else if (method.paramStructure === "by-name") {
80-
id = param.name;
8188
} else {
82-
if (params[index] !== undefined) {
83-
id = index;
84-
} else {
85-
id = param.name;
86-
}
89+
id = param.name;
8790
}
88-
const input = params[id];
91+
}
92+
const input = params[id];
8993

90-
if (input === undefined && !param.required) { return; }
94+
if (input === undefined && !param.required) { return; }
9195

92-
if (param.schema !== undefined) {
93-
const idForMethod = generateMethodParamId(method, param);
94-
const isValid = this.ajvValidator.validate(idForMethod, input);
95-
const errors = this.ajvValidator.errors as ErrorObject[];
96+
if (param.schema !== undefined) {
97+
const idForMethod = generateMethodParamId(method, param);
98+
const isValid = this.ajvValidator.validate(idForMethod, input);
99+
const errors = this.ajvValidator.errors as ErrorObject[];
96100

97-
if (!isValid) {
98-
return new ParameterValidationError(id, param.schema, input, errors);
99-
}
101+
if (!isValid) {
102+
return new ParameterValidationError(id, param.schema, input, errors);
100103
}
101-
})) as ParameterValidationError[];
102-
} else {
103-
return [] as ParameterValidationError[];
104-
}
104+
}
105+
})) as ParameterValidationError[];
105106
}
106107
}

0 commit comments

Comments
 (0)