Skip to content

Commit

Permalink
chore: improve typing (#975)
Browse files Browse the repository at this point in the history
  • Loading branch information
JustinBeckwith committed Jan 30, 2018
1 parent 0ea5654 commit 2012a59
Show file tree
Hide file tree
Showing 23 changed files with 473 additions and 363 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -108,6 +108,7 @@
"@types/qs": "^6.5.1",
"@types/rimraf": "^2.0.2",
"@types/source-map-support": "^0.4.0",
"@types/string-template": "^1.0.2",
"@types/tmp": "0.0.33",
"@types/uuid": "^3.4.3",
"axios": "^0.17.1",
Expand Down
54 changes: 54 additions & 0 deletions src/lib/api.ts
@@ -0,0 +1,54 @@
// Copyright 2018, Google, LLC.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {AxiosRequestConfig} from 'axios';
import {OAuth2Client} from 'google-auth-library/build/src/auth/oauth2client';
import {OutgoingHttpHeaders} from 'http';
import * as stream from 'stream';

import {Endpoint} from './endpoint';
import {SchemaParameters} from './schema';

export interface APIRequestParams {
options: AxiosRequestConfig;
params: APIRequestMethodParams;
requiredParams: string[];
pathParams: string[];
context: APIRequestContext;
mediaUrl?: string|null;
}

export interface APIRequestContext {
google: {_options: APIRequestContextOptions;};
_options: APIRequestContextOptions;
}

export interface APIRequestContextOptions {
params?: SchemaParameters;
auth?: OAuth2Client|string|null;
}

export interface APIRequestMethodParams {
// tslint:disable-next-line: no-any
[index: string]: any;
url?: string;
media?: {body?: string|stream.Readable; mimeType?: string;};
resource?: {mimeType?: string;};
key?: string;
uploadType?: string;
auth?: OAuth2Client|string|null;
headers?: OutgoingHttpHeaders;
}

// tslint:disable-next-line: no-any
export type APIEndpoint = Readonly<Endpoint&any>;
69 changes: 20 additions & 49 deletions src/lib/apirequest.ts
Expand Up @@ -11,50 +11,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import {AxiosRequestConfig} from 'axios';
import {DefaultTransporter, OAuth2Client} from 'google-auth-library';
import {DefaultTransporter} from 'google-auth-library';
import {BodyResponseCallback} from 'google-auth-library/build/src/transporters';
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;
params: APIRequestMethodParams;
// tslint:disable-next-line no-any
requiredParams: any[];
// tslint:disable-next-line no-any
pathParams: any[];
context: APIRequestContext;
mediaUrl?: string|null;
}

export interface APIRequestContext {
google: {_options: APIRequestContextOptions;};
_options: APIRequestContextOptions;
}

export interface APIRequestContextOptions {
// tslint:disable-next-line no-any
params?: any;
auth?: OAuth2Client|string|null;
}

export interface APIRequestMethodParams {
url?: string;
media?: {body?: string|stream.Readable; mimeType?: string;};
resource?: {mimeType?: string;};
key?: string;
uploadType?: string;
auth?: OAuth2Client|string|null;
// tslint:disable-next-line no-any
headers?: any;
}
import {APIRequestParams} from './api';
import {SchemaParameters} from './schema';

// tslint:disable-next-line: no-any
function isReadableStream(obj: any) {
function isReadableStream(obj: stream.Readable|string) {
return obj instanceof stream.Readable && typeof obj._read === 'function';
}

Expand All @@ -66,10 +33,8 @@ function createCallback<T>(callback: BodyResponseCallback<T>) {
};
}

function getMissingParams(params, required) {
// tslint:disable-next-line no-any
const missing = new Array<any>();

function getMissingParams(params: SchemaParameters, required: string[]) {
const missing = new Array<string>();
required.forEach(param => {
// Is the required param in the params object?
if (params[param] === undefined) {
Expand All @@ -89,9 +54,8 @@ function getMissingParams(params, required) {
*/
export function createAPIRequest<T>(
parameters: APIRequestParams, callback: BodyResponseCallback<T>): void {
let missingParams;
let params = parameters.params;
let options = Object.assign({}, parameters.options);
const options = Object.assign({}, parameters.options);

// If the params are not present, and callback was passed instead,
// use params as the callback and create empty params.
Expand Down Expand Up @@ -139,7 +103,7 @@ export function createAPIRequest<T>(
callback = createCallback<T>(callback);

// Check for missing required parameters in the API request
missingParams = getMissingParams(params, parameters.requiredParams);
const missingParams = getMissingParams(params, parameters.requiredParams);
if (missingParams) {
// Some params are missing - stop further operations and inform the
// developer which required params are not included in the request
Expand Down Expand Up @@ -226,15 +190,22 @@ export function createAPIRequest<T>(

options.headers = headers;
options.params = params;
options = Object.assign(

// Combine the AxiosRequestConfig options passed with this specific
// API call witht the global options configured at the API Context
// level, or at the global level.
const mergedOptions = Object.assign(
{}, parameters.context.google._options, parameters.context._options,
options);
delete options.auth; // is overridden by our auth code
delete mergedOptions.auth; // is overridden by our auth code

// create request (using authClient or otherwise and return request obj)
// Perform the HTTP request. NOTE: this function used to return a
// mikeal/request object. Since the transition to Axios, the method is
// now void. This may be a source of confusion for users upgrading from
// version 24.0 -> 25.0 or up.
if (authClient && typeof authClient === 'object') {
authClient.request(options, callback);
authClient.request(mergedOptions, callback);
} else {
(new DefaultTransporter()).request(options, callback);
(new DefaultTransporter()).request(mergedOptions, callback);
}
}
95 changes: 33 additions & 62 deletions src/lib/discovery.ts
Expand Up @@ -17,55 +17,26 @@ import * as pify from 'pify';
import * as url from 'url';
import * as util from 'util';

import {APIRequestMethodParams, createAPIRequest} from './apirequest';
import {GeneratedAPIs} from '../apis/index';

import {APIRequestMethodParams} from './api';
import {createAPIRequest} from './apirequest';
import {Endpoint} from './endpoint';
import {Schema, Schemas} from './schema';

const fsp = pify(fs);
interface Versionable {
version?: string;
}

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

interface DiscoverAPIsResponse {
items: API[];
}

export interface API {
discoveryRestUrl: string;
api: EndpointCreator;
name: string;
version: string;
}
const fsp = pify(fs);

export 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;
Expand All @@ -85,7 +56,7 @@ export class Discovery {
* @param schema The schema from which to generate the Endpoint.
* @return A function that creates an endpoint.
*/
private makeEndpoint(schema: APIRequest): EndpointCreator {
private makeEndpoint(schema: Schema) {
return (options: {}) => {
const ep = new Endpoint(options);
ep.applySchema(ep, schema, schema, ep);
Expand All @@ -106,50 +77,50 @@ export class Discovery {
* Generate all APIs and return as in-memory object.
* @param discoveryUrl
*/
async discoverAllAPIs(discoveryUrl: string) {
async discoverAllAPIs(discoveryUrl: string): Promise<GeneratedAPIs> {
const headers = this.options.includePrivate ? {} : {'X-User-Ip': '0.0.0.0'};
const res = await this.transporter.request<DiscoverAPIsResponse>(
{url: discoveryUrl, headers});
const res =
await this.transporter.request<Schemas>({url: discoveryUrl, headers});
const items = res.data.items;
const apis = await Promise.all(items.map(async api => {
const newApi = await this.discoverAPI(api.discoveryRestUrl);
api.api = newApi;
return api;
const endpointCreator = await this.discoverAPI(api.discoveryRestUrl);
return {api, endpointCreator};
}));

const versionIndex = {};
const versionIndex:
{[index: string]: {[index: string]: EndpointCreator}} = {};
const apisIndex = {};
for (const api of apis) {
if (!apisIndex[api.name]) {
versionIndex[api.name] = {};
apisIndex[api.name] = (options) => {
for (const set of apis) {
if (!apisIndex[set.api.name]) {
versionIndex[set.api.name] = {};
apisIndex[set.api.name] = (options: Versionable|string) => {
const type = typeof options;
let version: string;
if (type === 'string') {
version = options;
version = options as string;
options = {};
} else if (type === 'object') {
version = options.version;
delete options.version;
version = (options as Versionable).version!;
delete (options as Versionable).version;
} else {
throw new Error('Argument error: Accepts only string or object');
}
try {
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
const endpointCreator = versionIndex[set.api.name][version];
const ep = set.endpointCreator(options);
// tslint:disable-next-line: no-any
(ep as any).google = this; // for drive.google.transporter
return Object.freeze(ep); // create new & freeze
} catch (e) {
throw new Error(util.format(
'Unable to load endpoint %s("%s"): %s', api.name, version,
'Unable to load endpoint %s("%s"): %s', set.api.name, version,
e.message));
}
};
}
versionIndex[api.name][api.version] = api.api;
versionIndex[set.api.name][set.api.version] = set.endpointCreator;
}
return apisIndex;
return apisIndex as GeneratedAPIs;
}

/**
Expand All @@ -169,7 +140,7 @@ export class Discovery {
} else {
this.log('Requesting ' + apiDiscoveryUrl);
const res =
await this.transporter.request<APIRequest>({url: apiDiscoveryUrl});
await this.transporter.request<Schema>({url: apiDiscoveryUrl});
return this.makeEndpoint(res.data);
}
} else {
Expand Down

0 comments on commit 2012a59

Please sign in to comment.