Skip to content

Commit ccd3bbb

Browse files
authored
Improve remote specs (#508)
* Improve remote schema error message #505, #507 * Add --auth flag
1 parent 2b41596 commit ccd3bbb

File tree

7 files changed

+12245
-68
lines changed

7 files changed

+12245
-68
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ For anything more complicated, or for generating specs dynamically, you can also
109109
| Option | Alias | Default | Description |
110110
| :----------------------------- | :---- | :------: | :--------------------------------------------------------------- |
111111
| `--output [location]` | `-o` | (stdout) | Where should the output file be saved? |
112+
| `--auth [token]` | | | (optional) Provide an auth token to be passed along in the request (only if accessing a private schema). |
112113
| `--prettier-config [location]` | | | (optional) Path to your custom Prettier configuration for output |
113114
| `--raw-schema` | | `false` | Generate TS types from partial schema (e.g. having `components.schema` at the top level) |
114115

bin/cli.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ const cli = meow(
1313
1414
Options
1515
--help display this
16-
--output, -o (optional) specify output file (default: stdout)
16+
--output, -o Specify output file (default: stdout)
17+
--auth (optional) Provide an authentication token for private URL
1718
--prettier-config (optional) specify path to Prettier config file
1819
--raw-schema (optional) Read from raw schema instead of document
1920
--version (optional) Schema version (must be present for raw schemas)
@@ -24,6 +25,9 @@ Options
2425
type: "string",
2526
alias: "o",
2627
},
28+
auth: {
29+
type: "string",
30+
},
2731
prettierConfig: {
2832
type: "string",
2933
},
@@ -54,7 +58,10 @@ async function main() {
5458
// 1. input
5559
let spec = undefined;
5660
try {
57-
spec = await loadSpec(pathToSpec, { log: output !== "STDOUT" });
61+
spec = await loadSpec(pathToSpec, {
62+
auth: cli.flags.auth,
63+
log: output !== "STDOUT",
64+
});
5865
} catch (err) {
5966
process.exitCode = 1; // needed for async functions
6067
throw new Error(red(`❌ ${err}`));

bin/loaders/index.js

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1+
const mime = require("mime");
12
const yaml = require("js-yaml");
23
const { bold, yellow } = require("kleur");
34

45
const loadFromFs = require("./loadFromFs");
56
const loadFromHttp = require("./loadFromHttp");
67

7-
async function load(pathToSpec) {
8+
async function load(pathToSpec, { auth }) {
89
// option 1: remote URL
910
if (/^https?:\/\//.test(pathToSpec)) {
1011
try {
11-
const rawSpec = await loadFromHttp(pathToSpec);
12+
const rawSpec = await loadFromHttp(pathToSpec, { auth });
1213
return rawSpec;
1314
} catch (e) {
1415
if (e.code === "ENOTFOUND") {
@@ -24,33 +25,32 @@ async function load(pathToSpec) {
2425
return loadFromFs(pathToSpec);
2526
}
2627

27-
function isYamlSpec(rawSpec, pathToSpec) {
28-
return /\.ya?ml$/i.test(pathToSpec) || rawSpec[0] !== "{";
29-
}
30-
31-
module.exports.loadSpec = async (pathToSpec, { log = true }) => {
28+
async function loadSpec(pathToSpec, { auth, log = true }) {
3229
if (log === true) {
3330
console.log(yellow(`🤞 Loading spec from ${bold(pathToSpec)}…`)); // only log if not writing to stdout
3431
}
35-
const rawSpec = await load(pathToSpec);
3632

37-
try {
38-
if (isYamlSpec(rawSpec, pathToSpec)) {
39-
return yaml.load(rawSpec);
40-
}
41-
} catch (err) {
42-
let message = `The spec under ${pathToSpec} seems to be YAML, but it couldn’t be parsed.`;
33+
const rawSpec = await load(pathToSpec, { auth });
4334

44-
if (err.message) {
45-
message += `\n${err.message}`;
35+
switch (mime.getType(pathToSpec)) {
36+
case "text/yaml": {
37+
try {
38+
return yaml.load(rawSpec);
39+
} catch (err) {
40+
throw new Error(`YAML: ${err.toString()}`);
41+
}
42+
}
43+
case "application/json":
44+
case "application/json5": {
45+
try {
46+
return JSON.parse(rawSpec);
47+
} catch (err) {
48+
throw new Error(`JSON: ${err.toString()}`);
49+
}
50+
}
51+
default: {
52+
throw new Error(`Unknown format: "${contentType}". Only YAML or JSON supported.`);
4653
}
47-
48-
throw new Error(message);
49-
}
50-
51-
try {
52-
return JSON.parse(rawSpec);
53-
} catch {
54-
throw new Error(`The spec under ${pathToSpec} couldn’t be parsed neither as YAML nor JSON.`);
5554
}
56-
};
55+
}
56+
exports.loadSpec = loadSpec;

bin/loaders/loadFromFs.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const fs = require("fs");
22
const path = require("path");
33

4-
module.exports = (pathToSpec) => {
4+
function loadFromFs(pathToSpec) {
55
const pathname = path.resolve(process.cwd(), pathToSpec);
66
const pathExists = fs.existsSync(pathname);
77

@@ -10,4 +10,5 @@ module.exports = (pathToSpec) => {
1010
}
1111

1212
return fs.readFileSync(pathname, "utf8");
13-
};
13+
}
14+
module.exports = loadFromFs;

bin/loaders/loadFromHttp.js

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,39 @@
11
const url = require("url");
2-
const adapters = {
3-
"http:": require("http"),
4-
"https:": require("https"),
5-
};
62

7-
function fetchFrom(inputUrl) {
8-
return adapters[url.parse(inputUrl).protocol];
9-
}
3+
function loadFromHttp(pathToSpec, { auth }) {
4+
const { protocol } = url.parse(pathToSpec);
105

11-
function buildOptions(pathToSpec) {
12-
const requestUrl = url.parse(pathToSpec);
13-
return {
14-
method: "GET",
15-
hostname: requestUrl.hostname,
16-
port: requestUrl.port,
17-
path: requestUrl.path,
18-
};
19-
}
6+
if (protocol !== "http:" && protocol !== "https:") {
7+
throw new Error(`Unsupported protocol: "${protocol}". URL must start with "http://" or "https://".`);
8+
}
209

21-
module.exports = (pathToSpec) => {
10+
const fetch = require(protocol === "https:" ? "https" : "http");
2211
return new Promise((resolve, reject) => {
23-
const opts = buildOptions(pathToSpec);
24-
const req = fetchFrom(pathToSpec).request(opts, (res) => {
25-
let rawData = "";
26-
res.setEncoding("utf8");
27-
res.on("data", (chunk) => {
28-
rawData += chunk;
29-
});
30-
res.on("end", () => {
31-
resolve(rawData);
32-
});
33-
});
12+
const req = fetch.request(
13+
pathToSpec,
14+
{
15+
method: "GET",
16+
auth,
17+
},
18+
(res) => {
19+
let rawData = "";
20+
res.setEncoding("utf8");
21+
res.on("data", (chunk) => {
22+
rawData += chunk;
23+
});
24+
res.on("end", () => {
25+
if (res.statusCode >= 200 && res.statusCode < 300) {
26+
resolve(rawData);
27+
} else {
28+
reject(rawData || `${res.statusCode} ${res.statusMessage}`);
29+
}
30+
});
31+
}
32+
);
3433
req.on("error", (err) => {
3534
reject(err);
3635
});
3736
req.end();
3837
});
39-
};
38+
}
39+
module.exports = loadFromHttp;

0 commit comments

Comments
 (0)