Skip to content

Commit

Permalink
chore: improve typing (#968)
Browse files Browse the repository at this point in the history
  • Loading branch information
JustinBeckwith committed Jan 26, 2018
1 parent 8d56eac commit ca1f910
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 163 deletions.
15 changes: 8 additions & 7 deletions src/lib/apirequest.ts
Expand Up @@ -18,6 +18,7 @@ import * as qs from 'qs';
import * as stream from 'stream';
import * as parseString from 'string-template';
import * as uuid from 'uuid';
import {APIRequest} from './discovery';

export interface APIRequestParams {
options: AxiosRequestConfig;
Expand All @@ -42,6 +43,7 @@ export interface APIRequestContextOptions {
}

export interface APIRequestMethodParams {
url?: string;
media?: {body?: string|stream.Readable; mimeType?: string;};
resource?: {mimeType?: string;};
key?: string;
Expand All @@ -56,7 +58,7 @@ function isReadableStream(obj: any) {
return obj instanceof stream.Readable && typeof obj._read === 'function';
}

function createCallback(callback: BodyResponseCallback<{}>) {
function createCallback<T>(callback: BodyResponseCallback<T>) {
return typeof callback === 'function' ? callback : (err: Error|null) => {
if (err) {
console.error(err);
Expand All @@ -82,12 +84,11 @@ function getMissingParams(params, required) {

/**
* Create and send request to Google API
* @param {object} parameters Parameters used to form request
* @param {Function} callback Callback when request finished or error found
* @return {Request} Returns Request object or null
* @param parameters Parameters used to form request
* @param callback Callback when request finished or error found
*/
export function createAPIRequest(
parameters: APIRequestParams, callback: BodyResponseCallback<{}>): void {
export function createAPIRequest<T>(
parameters: APIRequestParams, callback: BodyResponseCallback<T>): void {
let missingParams;
let params = parameters.params;
let options = Object.assign({}, parameters.options);
Expand Down Expand Up @@ -135,7 +136,7 @@ export function createAPIRequest(
});

// Normalize callback
callback = createCallback(callback);
callback = createCallback<T>(callback);

// Check for missing required parameters in the API request
missingParams = getMissingParams(params, parameters.requiredParams);
Expand Down
219 changes: 87 additions & 132 deletions src/lib/discovery.ts
Expand Up @@ -12,154 +12,95 @@
// limitations under the License.

import * as async from 'async';
import {AxiosRequestConfig, AxiosResponse} from 'axios';
import * as fs from 'fs';
import {DefaultTransporter} from 'google-auth-library';
import {Schema} from 'inspector';
import * as url from 'url';
import * as util from 'util';

import {buildurl, handleError} from '../scripts/generator_utils';

import {APIRequestParams, createAPIRequest} from './apirequest';
import {APIRequestMethodParams, APIRequestParams, createAPIRequest} from './apirequest';
import {Endpoint} from './endpoint';

export type EndpointCreator = (options: {}) => Endpoint;

interface DiscoverAPIsResponse {
items: API[];
}

interface API {
export interface API {
discoveryRestUrl: string;
api;
api: EndpointCreator;
}

interface DiscoveryOptions {
includePrivate?: boolean;
debug?: boolean;
}

export interface APIRequest {
auth: {};
basePath: string;
baseUrl: string;
batchPath: string;
description: string;
discoveryVersion: string;
documentationLink: string;
etag: string;
icons: {};
id: string;
kind: string;
name: string;
ownerDomain: string;
ownerName: string;
parameters: {};
protocol: string;
resources: {};
revision: string;
rootUrl: string;
schemas: {};
servicePath: string;
title: string;
version: string;
}

export class Discovery {
private transporter = new DefaultTransporter();
private options: DiscoveryOptions;

private getPathParams(params) {
const pathParams = new Array<string>();
if (typeof params !== 'object') {
params = {};
}
Object.keys(params).forEach(key => {
if (params[key].location === 'path') {
pathParams.push(key);
}
});
return pathParams;
}

/**
* Given a method schema, add a method to a target.
*
* @param {object} target The target to which to add the method.
* @param {object} schema The top-level schema that contains the rootUrl, etc.
* @param {object} method The method schema from which to generate the method.
* @param {object} context The context to add to the method.
*/
private makeMethod(schema, method, context) {
return (params, callback) => {
const schemaUrl =
buildurl(schema.rootUrl + schema.servicePath + method.path);

const parameters = {
options: {
url: schemaUrl.substring(1, schemaUrl.length - 1),
method: method.httpMethod
},
params,
requiredParams: method.parameterOrder || [],
pathParams: this.getPathParams(method.parameters),
context,
mediaUrl: null
} as APIRequestParams;

if (method.mediaUpload && method.mediaUpload.protocols &&
method.mediaUpload.protocols.simple &&
method.mediaUpload.protocols.simple.path) {
const mediaUrl =
buildurl(schema.rootUrl + method.mediaUpload.protocols.simple.path);
parameters.mediaUrl = mediaUrl.substring(1, mediaUrl.length - 1);
}

return createAPIRequest(parameters, callback);
};
}

/**
* Given a schema, add methods to a target.
*
* @param {object} target The target to which to apply the methods.
* @param {object} rootSchema The top-level schema, so we don't lose track of it
* during recursion.
* @param {object} schema The current schema from which to extract methods.
* @param {object} context The context to add to each method.
*/
private applyMethodsFromSchema(target, rootSchema, schema, context) {
if (schema.methods) {
for (const name in schema.methods) {
if (schema.methods.hasOwnProperty(name)) {
const method = schema.methods[name];
target[name] = this.makeMethod(rootSchema, method, context);
}
}
}
}

/**
* Given a schema, add methods and resources to a target.
* Discovery for discovering API endpoints
*
* @param {object} target The target to which to apply the schema.
* @param {object} rootSchema The top-level schema, so we don't lose track of it
* during recursion.
* @param {object} schema The current schema from which to extract methods and
* resources.
* @param {object} context The context to add to each method.
* @param {object} options Options for discovery
*/
private applySchema(target, rootSchema, schema, context) {
this.applyMethodsFromSchema(target, rootSchema, schema, context);

if (schema.resources) {
for (const resourceName in schema.resources) {
if (schema.resources.hasOwnProperty(resourceName)) {
const resource = schema.resources[resourceName];
if (!target[resourceName]) {
target[resourceName] = {};
}
this.applySchema(target[resourceName], rootSchema, resource, context);
}
}
}
constructor(options) {
this.options = options || {};
}

/**
* Generate and Endpoint from an endpoint schema object.
*
* @param {object} schema The schema from which to generate the Endpoint.
* @return Function The Endpoint.
* @param schema The schema from which to generate the Endpoint.
* @return A function that creates an endpoint.
*/
private makeEndpoint(schema) {
// Creating an object, so Pascal case is appropriate.
const that = this;
// tslint:disable-next-line
const Endpoint = function(options) {
const self = this;
self._options = options || {};
that.applySchema(self, schema.data, schema.data, self);
private makeEndpoint(schema: APIRequest): EndpointCreator {
// // Creating an object, so Pascal case is appropriate.
// const that = this;
// // tslint:disable-next-line
// const Endpoint = function(options) {
// const self = this;
// self._options = options || {};
// that.applySchema(self, schema.data, schema.data, self);
// };
// return Endpoint;
return (options: {}) => {
const ep = new Endpoint(options);
ep.applySchema(ep, schema, schema, ep);
return ep;
};
return Endpoint;
}

/**
* Discovery for discovering API endpoints
*
* @param {object} options Options for discovery
*/
constructor(options) {
this.options = options || {};
}

/**
Expand All @@ -173,18 +114,18 @@ export class Discovery {

/**
* Generate all APIs and return as in-memory object.
*
* @param {function} callback Callback when all APIs have been generated
* @throws {Error} If there is an error generating any of the APIs
* @param discoveryUrl
* @param callback Callback when all APIs have been generated
*/
discoverAllAPIs(discoveryUrl, callback) {
discoverAllAPIs(
discoveryUrl: string, callback: (err: Error|null, api?: {}) => void) {
const headers = this.options.includePrivate ? {} : {'X-User-Ip': '0.0.0.0'};
this.transporter.request<DiscoverAPIsResponse>({url: discoveryUrl, headers})
.then(res => {
const items = res.data.items;
async.parallel(
items.map(api => {
return (cb) => {
return (cb: (err: Error|null, api?: API) => void) => {
this.discoverAPI(api.discoveryRestUrl, (e, newApi) => {
if (e) {
return cb(e);
Expand All @@ -194,7 +135,7 @@ export class Discovery {
});
};
}),
(e, apis) => {
(e: Error|null, apis) => {
if (e) {
return callback(e);
}
Expand All @@ -207,7 +148,7 @@ export class Discovery {
versionIndex[api.name] = {};
apisIndex[api.name] = (options) => {
const type = typeof options;
let version;
let version: string;
if (type === 'string') {
version = options;
options = {};
Expand All @@ -219,10 +160,9 @@ export class Discovery {
'Argument error: Accepts only string or object');
}
try {
// Creating an object, so Pascal case is appropriate.
// tslint:disable-next-line
const Endpoint = versionIndex[api.name][version];
const ep = new Endpoint(options);
const endpointCreator: EndpointCreator =
versionIndex[api.name][version];
const ep = endpointCreator(options);
ep.google = this; // for drive.google.transporter
return Object.freeze(ep); // create new & freeze
} catch (e) {
Expand All @@ -246,16 +186,17 @@ export class Discovery {
/**
* Generate API file given discovery URL
*
* @param {String} apiDiscoveryUrl URL or filename of discovery doc for API
* @param {function} callback Callback when successful write of API
* @throws {Error} If there is an error generating the API.
* @param apiDiscoveryUrl URL or filename of discovery doc for API
* @param callback Callback when successful write of API
*/
async discoverAPI(apiDiscoveryUrl, callback) {
const _generate = (err, resp) => {
async discoverAPI(
apiDiscoveryUrl: string|APIRequestMethodParams,
callback: (err: Error|null, endpointCreator: EndpointCreator) => void) {
const _generate = (err: Error|null, res?: APIRequest) => {
if (err) {
return handleError(err, callback);
}
return callback(null, this.makeEndpoint(resp));
return callback(null, this.makeEndpoint(res!));
};

if (typeof apiDiscoveryUrl === 'string') {
Expand All @@ -273,7 +214,13 @@ export class Discovery {
}
} else {
this.log('Requesting ' + apiDiscoveryUrl);
this.transporter.request({url: apiDiscoveryUrl}, _generate);
this.transporter.request<APIRequest>(
{url: apiDiscoveryUrl}, (err, res) => {
if (err || (res && !res.data)) {
return _generate(err);
}
return _generate(err, res!.data);
});
}
} else {
const options = apiDiscoveryUrl;
Expand All @@ -287,7 +234,15 @@ export class Discovery {
params: options,
context: {google: {_options: {}}, _options: {}}
};
createAPIRequest(parameters, _generate);
createAPIRequest<APIRequest>(parameters, (err, res) => {
if (err) {
return _generate(err);
} else {
if (res) {
return _generate(null, res.data);
}
}
});
}
}
}

0 comments on commit ca1f910

Please sign in to comment.