Skip to content

Commit

Permalink
ref: handle fetch errors consistently (Fetcher.fetch)
Browse files Browse the repository at this point in the history
  • Loading branch information
coolaj86 committed Dec 16, 2024
1 parent fe59a2f commit f1d1027
Show file tree
Hide file tree
Showing 14 changed files with 375 additions and 181 deletions.
75 changes: 41 additions & 34 deletions _common/brew.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,56 @@
'use strict';

let Fetcher = require('../_common/fetcher.js');

/**
* Gets releases from 'brew'.
*
* @param {null} _
* @param {string} formula
* @returns {PromiseLike<any> | Promise<any>}
* @returns {Promise<any>}
*/
function getDistributables(_, formula) {
async function getDistributables(_, formula) {
if (!formula) {
return Promise.reject('missing formula for brew');
}
return fetch('https://formulae.brew.sh/api/formula/' + formula + '.json')
.then(function (resp) {
if (!resp.ok) {
throw new Error(`HTTP error! Status: ${resp.status}`);
}
return resp.json(); // Parse JSON response
})
.then(function (body) {
var ver = body.versions.stable;
var dl = (
body.bottle.stable.files.high_sierra ||
body.bottle.stable.files.catalina
).url.replace(new RegExp(ver.replace(/\./g, '\\.'), 'g'), '{{ v }}');
return [
{
version: ver,
download: dl.replace(/{{ v }}/g, ver),
},
].concat(
body.versioned_formulae.map(function (f) {
var ver = f.replace(/.*@/, '');
return {
version: ver,
download: dl,
};
}),
);
})
.catch(function (err) {
console.error('Error fetching MariaDB versions (brew)');
console.error(err);
return [];

let resp;
try {
let url = `https://formulae.brew.sh/api/formula/${formula}.json`;
resp = await Fetcher.fetch(url, {
headers: { Accept: 'application/json' },
});
} catch (e) {
/** @type {Error & { code: string, response: { status: number, body: string } }} */ //@ts-expect-error
let err = e;
if (err.code === 'E_FETCH_RELEASES') {
err.message = `failed to fetch '${formula}' release data from 'brew': ${err.response.status} ${err.response.body}`;
}
throw e;
}
let body = JSON.parse(resp.body);

var ver = body.versions.stable;
var dl = (
body.bottle.stable.files.high_sierra || body.bottle.stable.files.catalina
).url.replace(new RegExp(ver.replace(/\./g, '\\.'), 'g'), '{{ v }}');
return [
{
version: ver,
download: dl.replace(/{{ v }}/g, ver),
},
].concat(
body.versioned_formulae.map(
/** @param {String} f */
function (f) {
var ver = f.replace(/.*@/, '');
return {
version: ver,
download: dl,
};
},
),
);
}

module.exports = getDistributables;
Expand Down
56 changes: 56 additions & 0 deletions _common/fetcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use strict';

let Fetcher = module.exports;

/**
* @typedef ResponseSummary
* @prop {Boolean} ok
* @prop {Headers} headers
* @prop {Number} status
* @prop {String} body
*/

/**
* @param {String} url
* @param {RequestInit} opts
* @returns {Promise<ResponseSummary>}
*/
Fetcher.fetch = async function (url, opts) {
let resp = await fetch(url, opts);
let summary = Fetcher.throwIfNotOk(resp);

return summary;
};

/**
* @param {Response} resp
* @returns {Promise<ResponseSummary>}
*/
Fetcher.throwIfNotOk = async function (resp) {
let text = await resp.text();

if (!resp.ok) {
let headers = Array.from(resp.headers);
console.error('[Fetcher] error: Response Headers:', headers);
console.error('[Fetcher] error: Response Text:', text);
let err = new Error(`fetch was not ok`);
Object.assign({
status: 503,
code: 'E_FETCH_RELEASES',
response: {
status: resp.status,
headers: headers,
body: text,
},
});
throw err;
}

let summary = {
ok: resp.ok,
headers: resp.headers,
status: resp.status,
body: text,
};
return summary;
};
57 changes: 39 additions & 18 deletions _common/githubish-source.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

let Fetcher = require('../_common/fetcher.js');

let GitHubishSource = module.exports;

/**
Expand Down Expand Up @@ -44,30 +46,22 @@ GitHubishSource.getDistributables = async function ({
});
}

let resp = await fetch(url, opts);
if (!resp.ok) {
let headers = Array.from(resp.headers);
console.error('Bad Resp Headers:', headers);
let text = await resp.text();
console.error('Bad Resp Body:', text);
let msg = `failed to fetch releases from '${baseurl}' with user '${username}'`;
throw new Error(msg);
}

let respText = await resp.text();
let gHubResp;
let resp;
try {
gHubResp = JSON.parse(respText);
resp = await Fetcher.fetch(url, opts);
} catch (e) {
console.error('Bad Resp JSON:', respText);
console.error(e.message);
let msg = `failed to parse releases from '${baseurl}' with user '${username}'`;
throw new Error(msg);
/** @type {Error & { code: string, response: { status: number, body: string } }} */ //@ts-expect-error
let err = e;
if (err.code === 'E_FETCH_RELEASES') {
err.message = `failed to fetch '${baseurl}' (githubish-source, user '${username}) release data: ${err.response.status} ${err.response.body}`;
}
throw e;
}
let gHubResp = JSON.parse(resp.body);

let all = {
/** @type {Array<BuildInfo>} */
releases: [],
// TODO make this ':baseurl' + ':releasename'
download: '',
};

Expand All @@ -84,6 +78,29 @@ GitHubishSource.getDistributables = async function ({
return all;
};

/**
* @typedef BuildInfo
* @prop {String} [name] - name to use instead of filename for hash urls
* @prop {String} version
* @prop {String} [_version]
* @prop {String} [arch]
* @prop {String} channel
* @prop {String} date
* @prop {String} download
* @prop {String} [ext]
* @prop {String} [_filename]
* @prop {String} [hash]
* @prop {String} [libc]
* @prop {Boolean} [_musl]
* @prop {Boolean} [lts]
* @prop {String} [size]
* @prop {String} os
*/

/**
* @param {any} ghRelease - TODO
* @returns {Array<BuildInfo>}
*/
GitHubishSource.releaseToDistributables = function (ghRelease) {
let ghTag = ghRelease['tag_name']; // TODO tags aren't always semver / sensical
let lts = /(\b|_)(lts)(\b|_)/.test(ghRelease['tag_name']);
Expand All @@ -95,6 +112,7 @@ GitHubishSource.releaseToDistributables = function (ghRelease) {
date = date.replace(/T.*/, '');

let urls = [ghRelease.tarball_url, ghRelease.zipball_url];
/** @type {Array<BuildInfo>} */
let dists = [];
for (let url of urls) {
dists.push({
Expand All @@ -114,6 +132,9 @@ GitHubishSource.releaseToDistributables = function (ghRelease) {
return dists;
};

/**
* @param {BuildInfo} dist
*/
GitHubishSource.followDistributableDownloadAttachment = async function (dist) {
let abortCtrl = new AbortController();
let resp = await fetch(dist.download, {
Expand Down
35 changes: 17 additions & 18 deletions _common/githubish.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

let Fetcher = require('../_common/fetcher.js');

/**
* @typedef DistributableRaw
* @prop {String} name
Expand Down Expand Up @@ -57,26 +59,18 @@ GitHubish.getDistributables = async function ({
});
}

let resp = await fetch(url, opts);
if (!resp.ok) {
let headers = Array.from(resp.headers);
console.error('Bad Resp Headers:', headers);
let text = await resp.text();
console.error('Bad Resp Body:', text);
let msg = `failed to fetch releases from '${baseurl}' with user '${username}'`;
throw new Error(msg);
}

let respText = await resp.text();
let gHubResp;
let resp;
try {
gHubResp = JSON.parse(respText);
resp = await Fetcher.fetch(url, opts);
} catch (e) {
console.error('Bad Resp JSON:', respText);
console.error(e.message);
let msg = `failed to parse releases from '${baseurl}' with user '${username}'`;
throw new Error(msg);
/** @type {Error & { code: string, response: { status: number, body: string } }} */ //@ts-expect-error
let err = e;
if (err.code === 'E_FETCH_RELEASES') {
err.message = `failed to fetch '${baseurl}' (githubish, user '${username}) release data: ${err.response.status} ${err.response.body}`;
}
throw e;
}
let gHubResp = JSON.parse(resp.body);

let all = {
/** @type {Array<DistributableRaw>} */
Expand All @@ -88,13 +82,18 @@ GitHubish.getDistributables = async function ({
try {
gHubResp.forEach(transformReleases);
} catch (e) {
console.error(e.message);
/** @type {Error & { code: string, response: { status: number, body: string } }} */ //@ts-expect-error
let err = e;
console.error(err.message);
console.error('Error Headers:', resp.headers);
console.error('Error Body:', resp.body);
let msg = `failed to transform releases from '${baseurl}' with user '${username}'`;
throw new Error(msg);
}

/**
* @param {any} release - TODO
*/
function transformReleases(release) {
for (let asset of release['assets']) {
let name = asset['name'];
Expand Down
25 changes: 16 additions & 9 deletions chromedriver/releases.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

let Fetcher = require('../_common/fetcher.js');

// See <https://googlechromelabs.github.io/chrome-for-testing/>
const releaseApiUrl =
'https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json';
Expand Down Expand Up @@ -41,18 +43,23 @@ const releaseApiUrl =
// }

module.exports = async function () {
let resp = await fetch(releaseApiUrl);

if (!resp.ok) {
let text = await resp.text();
let msg = `failed to fetch releases from '${releaseApiUrl}': ${resp.status} ${text}`;
throw new Error(msg);
let resp;
try {
resp = await Fetcher.fetch(releaseApiUrl, {
headers: { Accept: 'application/json' },
});
} catch (e) {
/** @type {Error & { code: string, response: { status: number, body: string } }} */ //@ts-expect-error
let err = e;
if (err.code === 'E_FETCH_RELEASES') {
err.message = `failed to fetch 'chromedriver' release data: ${err.response.status} ${err.response.body}`;
}
throw e;
}

let body = await resp.json();
let data = JSON.parse(resp.body);

let builds = [];
for (let release of body.versions) {
for (let release of data.versions) {
if (!release.downloads.chromedriver) {
continue;
}
Expand Down
30 changes: 19 additions & 11 deletions flutter/releases.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
'use strict';

var FLUTTER_OSES = ['macos', 'linux', 'windows'];
let Fetcher = require('../_common/fetcher.js');

let FLUTTER_OSES = ['macos', 'linux', 'windows'];

/**
* stable, beta, dev
* @type {Object.<String, Boolean>}
*/
var channelMap = {};
let channelMap = {};

// This can be spot-checked against
// https://docs.flutter.dev/release/archive?tab=windows
Expand Down Expand Up @@ -77,16 +79,22 @@ module.exports = async function () {
};

for (let osname of FLUTTER_OSES) {
let response = await fetch(
`https://storage.googleapis.com/flutter_infra_release/releases/releases_${osname}.json`,
{ headers: { Accept: 'application/json' } },
);
if (!response.ok) {
throw new Error(
`Failed to fetch data for ${osname}: ${response.statusText}`,
);
let resp;
try {
let url = `https://storage.googleapis.com/flutter_infra_release/releases/releases_${osname}.json`;
resp = await Fetcher.fetch(url, {
headers: { Accept: 'application/json' },
});
} catch (e) {
/** @type {Error & { code: string, response: { status: number, body: string } }} */ //@ts-expect-error
let err = e;
if (err.code === 'E_FETCH_RELEASES') {
err.message = `failed to fetch 'flutter' release data for ${osname}: ${err.response.status} ${err.response.body}`;
}
throw e;
}
let data = await response.json();
let data = JSON.parse(resp.body);

let osBaseUrl = data.base_url;
let osReleases = data.releases;

Expand Down
Loading

0 comments on commit f1d1027

Please sign in to comment.