-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Handle Windows absolute paths for schema inputs #2886
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
schani
wants to merge
1
commit into
master
Choose a base branch
from
fix/windows-schema-paths
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,28 @@ function parseHeaders(httpHeaders?: string[]): HttpHeaders { | |
| }, {} as HttpHeaders); | ||
| } | ||
|
|
||
| // "file:" URIs come from JSONSchemaInput, which converts Windows absolute | ||
| // schema paths to them because they are not valid URIs (issue #2869). We | ||
| // don't use `url.fileURLToPath` because its result is platform-dependent: | ||
| // drive-letter URIs must also be readable on POSIX (as paths relative to the | ||
| // working directory), which is how the tests exercise this, and Node's fs | ||
| // accepts forward slashes on Windows anyway. The addresses were URI-decoded | ||
| // when they were normalized, so there's no percent-decoding to do here. | ||
| function filePathFromFileURI(fileURI: string): string { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Put that in a utility file and add comprehensive unit tests. |
||
| const path = fileURI.slice("file://".length); | ||
| if (/^\/[A-Za-z]:\//.test(path)) { | ||
| // Drive-letter path, e.g. "file:///C:/dir/x.json" -> "C:/dir/x.json" | ||
| return path.slice(1); | ||
| } | ||
|
|
||
| if (!path.startsWith("/")) { | ||
| // UNC path, e.g. "file://server/share/x.json" -> "//server/share/x.json" | ||
| return `//${path}`; | ||
| } | ||
|
|
||
| return path; | ||
| } | ||
|
|
||
| function resolveSymbolicLink(filePath: string): string { | ||
| if (!fs.lstatSync(filePath).isSymbolicLink()) { | ||
| return filePath; | ||
|
|
@@ -56,7 +78,9 @@ export async function readableFromFileOrURL( | |
| httpHeaders?: string[], | ||
| ): Promise<Readable> { | ||
| try { | ||
| if (isURL(fileOrURL)) { | ||
| if (fileOrURL.startsWith("file://")) { | ||
| fileOrURL = filePathFromFileURI(fileOrURL); | ||
| } else if (isURL(fileOrURL)) { | ||
| const response = await fetch(fileOrURL, { | ||
| headers: parseHeaders(httpHeaders), | ||
| }); | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| // Regression check for issue #2869: schema inputs given as Windows absolute | ||
| // paths must work. | ||
| // | ||
| // urijs parses the drive letter of a Windows absolute path such as | ||
| // "C:\Users\me\top.schema.json" as a URI *scheme* ("c:"), so the address is | ||
| // mangled (lowercased scheme, backslashes kept as an opaque path) and | ||
| // relative $refs resolve to bogus addresses like "c:///item.schema.json", | ||
| // which NodeIO then tries to fetch as HTTP URLs. The reported failures are | ||
| // "Could not fetch schema ..." and "Internal error: Defined value expected". | ||
| // | ||
| // The fix converts Windows absolute paths (drive-letter and UNC) to "file:" | ||
| // URIs before urijs sees them, and teaches NodeIO to read "file:" URIs from | ||
| // disk. On POSIX the resulting drive-letter file path ("C:/Users/me/...") is | ||
| // read relative to the working directory, which is what lets us test the | ||
| // whole pipeline on Linux CI: we create a literal "C:/Users/me/" directory | ||
| // tree in a temp dir, chdir into it, and run quicktype with the Windows-style | ||
| // path the issue reported. The fixture harness cannot cover this because its | ||
| // inputs are always plain POSIX paths. | ||
|
|
||
| import * as fs from "fs"; | ||
| import * as os from "os"; | ||
| import * as path from "path"; | ||
|
|
||
| import { | ||
| FetchingJSONSchemaStore, | ||
| InputData, | ||
| JSONSchemaInput, | ||
| quicktype, | ||
| } from "quicktype-core"; | ||
|
|
||
| const topLevelSchema = JSON.stringify({ | ||
| $schema: "http://json-schema.org/draft-07/schema#", | ||
| type: "array", | ||
| items: { $ref: "item.schema.json" }, | ||
| }); | ||
|
|
||
| const itemSchema = JSON.stringify({ | ||
| $schema: "http://json-schema.org/draft-07/schema#", | ||
| type: "object", | ||
| properties: { name: { type: "string" } }, | ||
| required: ["name"], | ||
| }); | ||
|
|
||
| interface SchemaPathCase { | ||
| description: string; | ||
| // The path passed to quicktype, as a user would type it. | ||
| schemaArg: (tempDir: string) => string; | ||
| // Where to create the schema files, relative to the temp dir. | ||
| schemaDir: string; | ||
| } | ||
|
|
||
| const cases: SchemaPathCase[] = [ | ||
| { | ||
| description: "Windows absolute path with backslashes", | ||
| schemaDir: "C:/Users/quicktype", | ||
| schemaArg: () => "C:\\Users\\quicktype\\top.schema.json", | ||
| }, | ||
| { | ||
| description: "Windows absolute path with forward slashes", | ||
| schemaDir: "C:/Users/quicktype", | ||
| schemaArg: () => "C:/Users/quicktype/top.schema.json", | ||
| }, | ||
| { | ||
| // Must keep working exactly as before the fix. | ||
| description: "POSIX absolute path", | ||
| schemaDir: "posix", | ||
| schemaArg: (tempDir) => path.join(tempDir, "posix", "top.schema.json"), | ||
| }, | ||
| ]; | ||
|
|
||
| async function generateTypeScript(schemaURI: string): Promise<string> { | ||
| // The same setup the CLI uses for `-s schema <path>`. | ||
| const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore()); | ||
| await schemaInput.addSource({ name: "TopLevel", uris: [schemaURI] }); | ||
| const inputData = new InputData(); | ||
| inputData.addInput(schemaInput); | ||
|
|
||
| const result = await quicktype({ inputData, lang: "typescript" }); | ||
| return result.lines.join("\n"); | ||
| } | ||
|
|
||
| async function runCase(c: SchemaPathCase): Promise<string | undefined> { | ||
| const tempDir = fs.mkdtempSync( | ||
| path.join(os.tmpdir(), "quicktype-windows-paths-"), | ||
| ); | ||
| const previousCwd = process.cwd(); | ||
| try { | ||
| const schemaDir = path.join(tempDir, c.schemaDir); | ||
| fs.mkdirSync(schemaDir, { recursive: true }); | ||
| fs.writeFileSync( | ||
| path.join(schemaDir, "top.schema.json"), | ||
| topLevelSchema, | ||
| ); | ||
| fs.writeFileSync(path.join(schemaDir, "item.schema.json"), itemSchema); | ||
|
|
||
| // On POSIX the drive-letter path is read relative to the working | ||
| // directory, so the "C:/Users/quicktype" tree must be under the cwd. | ||
| process.chdir(tempDir); | ||
| const output = await generateTypeScript(c.schemaArg(tempDir)); | ||
|
|
||
| // The item schema is only reachable through the relative $ref, so | ||
| // this asserts that $ref resolution against the address works, too. | ||
| if (!/name\s*:\s*string/.test(output)) { | ||
| return ` ${c.description}: generated output does not contain the type from the $ref'd schema:\n${output}`; | ||
| } | ||
|
|
||
| return undefined; | ||
| } catch (e) { | ||
| return ` ${c.description}: quicktype failed: ${e}`; | ||
| } finally { | ||
| process.chdir(previousCwd); | ||
| fs.rmSync(tempDir, { recursive: true, force: true }); | ||
| } | ||
| } | ||
|
|
||
| export async function checkWindowsSchemaPaths(): Promise<void> { | ||
| const failures: string[] = []; | ||
| for (const c of cases) { | ||
| const failure = await runCase(c); | ||
| if (failure !== undefined) { | ||
| failures.push(failure); | ||
| } | ||
| } | ||
|
|
||
| if (failures.length > 0) { | ||
| console.error( | ||
| `error: schema inputs given as absolute paths must work (issue #2869): | ||
|
|
||
| ${failures.join("\n")} | ||
|
|
||
| Windows absolute paths (drive-letter and UNC) must be converted to "file:" | ||
| URIs before urijs parses them — see normalizeURI and friends in | ||
| packages/quicktype-core/src/input/JSONSchemaInput.ts and the "file:" URI | ||
| handling in packages/quicktype-core/src/input/io/NodeIO.ts.`, | ||
| ); | ||
| process.exit(1); | ||
| } | ||
| } | ||
|
|
||
| // Allow running the check standalone: | ||
| // NODE_PATH=`pwd`/node_modules npx ts-node --project test/tsconfig.json test/check-windows-schema-paths.ts | ||
| if (require.main === module) { | ||
| checkWindowsSchemaPaths().then(() => { | ||
| console.error( | ||
| "* Schema inputs given as Windows and POSIX absolute paths work", | ||
| ); | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put that in a shared utility file and add comprehensive unit tests.