http.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.formatResponseError = exports.formatSuperAgentError = exports.createFatalAPIFormat = exports.transformAPIResponse = exports.ResourceClient = exports.TokenPaginator = exports.Paginator = exports.Client = exports.ERROR_UNKNOWN_RESPONSE_FORMAT = exports.ERROR_UNKNOWN_CONTENT_TYPE = void 0;
  4. const tslib_1 = require("tslib");
  5. const chalk_1 = tslib_1.__importDefault(require("chalk"));
  6. const lodash = tslib_1.__importStar(require("lodash"));
  7. const util = tslib_1.__importStar(require("util"));
  8. const guards_1 = require("../guards");
  9. const color_1 = require("./color");
  10. const errors_1 = require("./errors");
  11. const http_1 = require("./utils/http");
  12. const FORMAT_ERROR_BODY_MAX_LENGTH = 1000;
  13. exports.ERROR_UNKNOWN_CONTENT_TYPE = 'UNKNOWN_CONTENT_TYPE';
  14. exports.ERROR_UNKNOWN_RESPONSE_FORMAT = 'UNKNOWN_RESPONSE_FORMAT';
  15. class Client {
  16. constructor(config) {
  17. this.config = config;
  18. }
  19. async make(method, path, contentType = "application/json" /* ContentType.JSON */) {
  20. const url = path.startsWith('http://') || path.startsWith('https://') ? path : `${this.config.getAPIUrl()}${path}`;
  21. const { req } = await (0, http_1.createRequest)(method, url, this.config.getHTTPConfig());
  22. req
  23. .set('Content-Type', contentType)
  24. .set('Accept', "application/json" /* ContentType.JSON */);
  25. return { req };
  26. }
  27. async do(req) {
  28. const res = await req;
  29. const r = transformAPIResponse(res);
  30. if ((0, guards_1.isAPIResponseError)(r)) {
  31. throw new errors_1.FatalException('API request was successful, but the response output format was that of an error.\n' +
  32. formatAPIResponse(req, r));
  33. }
  34. return r;
  35. }
  36. paginate(args) {
  37. return new Paginator({ client: this, ...args });
  38. }
  39. }
  40. exports.Client = Client;
  41. class Paginator {
  42. constructor({ client, reqgen, guard, state, max }) {
  43. const defaultState = { page: 1, done: false, loaded: 0 };
  44. this.client = client;
  45. this.reqgen = reqgen;
  46. this.guard = guard;
  47. this.max = max;
  48. if (!state) {
  49. state = { page_size: 100, ...defaultState };
  50. }
  51. this.state = lodash.assign({}, state, defaultState);
  52. }
  53. next() {
  54. if (this.state.done) {
  55. return { done: true }; // TODO: why can't I exclude value?
  56. }
  57. return {
  58. done: false,
  59. value: (async () => {
  60. const { req } = await this.reqgen();
  61. req.query(lodash.pick(this.state, ['page', 'page_size']));
  62. const res = await this.client.do(req);
  63. if (!this.guard(res)) {
  64. throw createFatalAPIFormat(req, res);
  65. }
  66. this.state.loaded += res.data.length;
  67. if (res.data.length === 0 || // no resources in this page, we're done
  68. (typeof this.max === 'number' && this.state.loaded >= this.max) || // met or exceeded maximum requested
  69. (typeof this.state.page_size === 'number' && res.data.length < this.state.page_size) // number of resources less than page size, so nothing on next page
  70. ) {
  71. this.state.done = true;
  72. }
  73. this.state.page++;
  74. return res;
  75. })(),
  76. };
  77. }
  78. [Symbol.iterator]() {
  79. return this;
  80. }
  81. }
  82. exports.Paginator = Paginator;
  83. class TokenPaginator {
  84. constructor({ client, reqgen, guard, state, max }) {
  85. const defaultState = { done: false, loaded: 0 };
  86. this.client = client;
  87. this.reqgen = reqgen;
  88. this.guard = guard;
  89. this.max = max;
  90. if (!state) {
  91. state = { ...defaultState };
  92. }
  93. this.state = lodash.assign({}, state, defaultState);
  94. }
  95. next() {
  96. if (this.state.done) {
  97. return { done: true }; // TODO: why can't I exclude value?
  98. }
  99. return {
  100. done: false,
  101. value: (async () => {
  102. const { req } = await this.reqgen();
  103. if (this.state.page_token) {
  104. req.query({ page_token: this.state.page_token });
  105. }
  106. const res = await this.client.do(req);
  107. if (!this.isPageTokenResponseMeta(res.meta)) {
  108. throw createFatalAPIFormat(req, res);
  109. }
  110. const nextPageToken = res.meta.next_page_token;
  111. if (!this.guard(res)) {
  112. throw createFatalAPIFormat(req, res);
  113. }
  114. this.state.loaded += res.data.length;
  115. if (res.data.length === 0 || // no resources in this page, we're done
  116. (typeof this.max === 'number' && this.state.loaded >= this.max) || // met or exceeded maximum requested
  117. !nextPageToken // no next page token, must be done
  118. ) {
  119. this.state.done = true;
  120. }
  121. this.state.page_token = nextPageToken;
  122. return res;
  123. })(),
  124. };
  125. }
  126. isPageTokenResponseMeta(meta) {
  127. return meta
  128. && (!meta.prev_page_token || typeof meta.prev_page_token === 'string')
  129. && (!meta.next_page_token || typeof meta.next_page_token === 'string');
  130. }
  131. [Symbol.iterator]() {
  132. return this;
  133. }
  134. }
  135. exports.TokenPaginator = TokenPaginator;
  136. class ResourceClient {
  137. applyModifiers(req, modifiers) {
  138. if (!modifiers) {
  139. return;
  140. }
  141. if (modifiers.fields) {
  142. req.query({ fields: modifiers.fields });
  143. }
  144. }
  145. applyAuthentication(req, token) {
  146. req.set('Authorization', `Bearer ${token}`);
  147. }
  148. }
  149. exports.ResourceClient = ResourceClient;
  150. function transformAPIResponse(r) {
  151. if (r.status === 204) {
  152. r.body = { meta: { status: 204, version: '', request_id: '' } };
  153. }
  154. if (r.status !== 204 && r.type !== "application/json" /* ContentType.JSON */) {
  155. throw exports.ERROR_UNKNOWN_CONTENT_TYPE;
  156. }
  157. const j = r.body;
  158. if (!j.meta) {
  159. throw exports.ERROR_UNKNOWN_RESPONSE_FORMAT;
  160. }
  161. return j;
  162. }
  163. exports.transformAPIResponse = transformAPIResponse;
  164. function createFatalAPIFormat(req, res) {
  165. return new errors_1.FatalException('API request was successful, but the response format was unrecognized.\n' +
  166. formatAPIResponse(req, res));
  167. }
  168. exports.createFatalAPIFormat = createFatalAPIFormat;
  169. function formatSuperAgentError(e) {
  170. const res = e.response;
  171. const req = res.request; // TODO: `req` and `request` exist: https://visionmedia.github.io/superagent/docs/test.html
  172. const statusCode = e.response.status;
  173. let f = '';
  174. try {
  175. const r = transformAPIResponse(res);
  176. f += formatAPIResponse(req, r);
  177. }
  178. catch (e) {
  179. f += (`HTTP Error ${statusCode}: ${req.method.toUpperCase()} ${req.url}\n` +
  180. '\n' + (res.text ? res.text.substring(0, FORMAT_ERROR_BODY_MAX_LENGTH) : '<no buffered body>'));
  181. if (res.text && res.text.length > FORMAT_ERROR_BODY_MAX_LENGTH) {
  182. f += ` ...\n\n[ truncated ${res.text.length - FORMAT_ERROR_BODY_MAX_LENGTH} characters ]`;
  183. }
  184. }
  185. return (0, color_1.failure)((0, color_1.strong)(f));
  186. }
  187. exports.formatSuperAgentError = formatSuperAgentError;
  188. function formatAPIResponse(req, r) {
  189. return formatResponseError(req, r.meta.status, (0, guards_1.isAPIResponseSuccess)(r) ? r.data : r.error);
  190. }
  191. function formatResponseError(req, status, body) {
  192. return (0, color_1.failure)(`Request: ${req.method} ${req.url}\n` +
  193. (status ? `Response: ${status}\n` : '') +
  194. (body ? `Body: \n${util.inspect(body, { colors: chalk_1.default.level > 0 })}` : ''));
  195. }
  196. exports.formatResponseError = formatResponseError;