UNPKG

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