-
Notifications
You must be signed in to change notification settings - Fork 0
/
json-schema.ts
197 lines (173 loc) · 5.65 KB
/
json-schema.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
import { createErrorHandler, SchemaError } from "./errors";
import { File } from "./file";
import { getCWD, locationToURL } from "./isomorphic.node";
import { iterate } from "./iterate";
import { Reference } from "./reference";
import { Resolution } from "./resolution";
import { Resource } from "./resource";
import { compareURIs, createURL } from "./url";
import { _internal } from "./utils";
import { jsonSchema, JsonSchemaVersionNumber } from "./versions";
/**
* The arguments that can be passed to the `JsonSchema` constructor
*/
export interface JsonSchemaArgs {
cwd?: string;
url?: string | URL;
path?: string | URL;
mediaType?: string;
metadata?: object;
data?: unknown;
}
/**
* A JSON Schema that has been read from one or more files, URLs, or other sources
*/
export class JsonSchema {
/**
* These fields are for internal use only
* @internal
*/
public [_internal]: {
cwd: string;
};
/**
* The files in the schema, including the root file and any files referenced by `$ref` pointers
*/
public files: File[] = [];
/**
* The root file of the schema. This is the first file that was read. All other files are
* referenced either directly or indirectly by this file.
*/
public rootFile!: File;
public constructor(args: JsonSchemaArgs = {}) {
// Determine the CWD, which we'll use to resolve relative paths/URLs
let cwd = args.cwd || getCWD();
this[_internal] = { cwd };
if (args.url || args.path) {
this.rootFile = new File({
schema: this,
url: args.url,
path: args.path,
mediaType: args.mediaType || "application/schema+json",
metadata: args.metadata,
data: args.data,
});
this.files.push(this.rootFile);
}
}
/**
* The root resource of the root file
*/
public get rootResource(): Resource {
return this.rootFile.rootResource;
}
/**
* All of the JSON Schema resources that have URIs
*/
public get resources(): Iterable<Resource> {
return iterate(this.files, "resources");
}
/**
* Indicates whether there are any errors in any file in the schema
*/
public get hasErrors(): boolean {
let result = this.errors[Symbol.iterator]().next();
return !result.done;
}
/**
* All errors in all files in the schema
*/
public get errors(): Iterable<SchemaError> {
return iterate(this.files, "errors");
}
/**
* Determines whether the specified file is in the schema
*
* @param location - The absolute or relative path/URL of the file to check for
*/
public hasFile(location: string | URL): boolean {
return Boolean(this.getFile(location));
}
/**
* Returns the specified file in the schema
*
* @param location - The absolute or relative path/URL of the file to return
*/
public getFile(location: string | URL): File | undefined {
// Resolve relative URLs against the CWD, just like the File constructor does.
// This ensures that relative URLs always behave consistently.
// NOTE: To find a file relative to the schema root, use resolve() instead
let url = locationToURL(location, this[_internal].cwd);
return this.files.find((file) => compareURIs(file.url, url));
}
/**
* Determines whether the specified JSON Schema resource URI is in the schema
*
* @param uri - The canonical URI of the resource to check for
*/
public hasResource(uri: string | URL): boolean {
return Boolean(this.getResource(uri));
}
/**
* Returns the JSON Schema resource with the specified URI
*
* @param uri - The canonical URI of the resource to return
* @returns The specified Resource, if found
*/
public getResource(uri: string | URL): Resource | undefined {
uri = uri instanceof URL ? uri : createURL(uri);
for (let resource of this.resources) {
if (compareURIs(resource.uri, uri)) {
return resource;
}
}
}
/**
* Indexes the schema's contents. That is, it scans all files and records all JSON Schema resources,
* anchors, and references in them.
*
* @param versionNumber - The JSON Schema version to use. Defaults to auto-detecting.
* @returns The same `JsonSchema` instance, to allow for chaining
*/
public index(versionNumber?: JsonSchemaVersionNumber): this {
for (let file of this.files) {
file.index(versionNumber);
}
return this;
}
/**
* Resolve a URI to a value in the schema.
*
* @param uri - The URI to find in the schema
* @param versionNumber - The JSON Schema version to use. Defaults to auto-detecting.
* @returns The resolved value, along with information about how it was resolved
*/
public resolve(uri: string | URL, versionNumber?: JsonSchemaVersionNumber): Resolution {
// Create a Reference that resolves the URI from the root of the schema
let href = uri instanceof URL ? uri.href : uri;
let ref = new Reference({
resource: this.rootResource,
locationInFile: [],
value: href,
data: { $ref: href }
});
let version = versionNumber ? jsonSchema[versionNumber] : jsonSchema;
let handleError = createErrorHandler({ file: this.rootFile, code: "ERR_RESOLVE" });
return version.resolveRef(ref, { handleError });
}
/**
* Determines whether a value is a `JsonSchema` instance
*/
public static isJsonSchema(value: unknown): value is JsonSchema {
if (value instanceof JsonSchema) {
return true;
}
// Use duck typing to support interoperability between multiple versions
// of the @apidevtools/json-schema package
let schema = value as JsonSchema;
return schema &&
typeof schema === "object" &&
Array.isArray(schema.files) &&
File.isFile(schema.rootFile);
}
}