Skip to content

Commit 6d16d52

Browse files
authored
Adds "params" package for environment variable helpers and type coercion (#928)
1 parent 63a0cc4 commit 6d16d52

File tree

6 files changed

+573
-2
lines changed

6 files changed

+573
-2
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"./v2/core": "./lib/v2/core.js",
3535
"./v2/options": "./lib/v2/options.js",
3636
"./v2/https": "./lib/v2/providers/https.js",
37+
"./v2/params": "./lib/v2/params/index.js",
3738
"./v2/pubsub": "./lib/v2/providers/pubsub.js"
3839
},
3940
"typesVersions": {
@@ -59,6 +60,9 @@
5960
"v2/https": [
6061
"lib/v2/providers/https"
6162
],
63+
"v2/params": [
64+
"lib/v2/params"
65+
],
6266
"v2/pubsub": [
6367
"lib/v2/providers/pubsub"
6468
]

spec/v2/params.spec.ts

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
import { expect } from 'chai';
2+
import {
3+
declaredParams,
4+
defineBoolean,
5+
defineFloat,
6+
defineInt,
7+
defineJSON,
8+
defineList,
9+
defineString,
10+
} from '../../src/v2/params';
11+
import { ListParam, Param, ParamOptions } from '../../src/v2/params/types';
12+
13+
const TEST_PARAM = 'TEST_PARAM';
14+
15+
describe('params', () => {
16+
beforeEach(() => {
17+
delete process.env[TEST_PARAM];
18+
});
19+
20+
const VALUE_TESTS: {
21+
method: (name: string, options: ParamOptions<any>) => Param;
22+
tests: {
23+
title: string;
24+
env?: string;
25+
options?: ParamOptions;
26+
expect?: any;
27+
throws?: boolean;
28+
}[];
29+
}[] = [
30+
{
31+
method: defineString,
32+
tests: [
33+
{
34+
title: "should return a '' zero value when no value and no undefined",
35+
expect: '',
36+
},
37+
{
38+
title: 'should return the default when no value and one is provided',
39+
env: undefined,
40+
options: { default: 'test_val' },
41+
expect: 'test_val',
42+
},
43+
{
44+
title: 'should return the value over the default',
45+
env: 'expected_val',
46+
options: { default: 'default_val' },
47+
expect: 'expected_val',
48+
},
49+
],
50+
},
51+
{
52+
method: defineInt,
53+
tests: [
54+
{
55+
title: 'should return 0 zero value',
56+
expect: 0,
57+
},
58+
{
59+
title: 'should coerce a matching string into an int',
60+
env: '25',
61+
expect: 25,
62+
},
63+
{
64+
title: 'should return a matching default value',
65+
options: { default: 10 },
66+
expect: 10,
67+
},
68+
{
69+
title: 'should throw when invalid value is passed',
70+
env: 'not_a_number',
71+
throws: true,
72+
},
73+
],
74+
},
75+
{
76+
method: defineFloat,
77+
tests: [
78+
{
79+
title: 'should return 0 zero value',
80+
expect: 0,
81+
},
82+
{
83+
title: 'should coerce a matching string into a float',
84+
env: '3.14',
85+
expect: 3.14,
86+
},
87+
{
88+
title: 'should return a matching default value',
89+
options: { default: 10.1 },
90+
expect: 10.1,
91+
},
92+
{
93+
title: 'should throw when invalid value is passed',
94+
env: 'not_a_number',
95+
throws: true,
96+
},
97+
],
98+
},
99+
{
100+
method: defineBoolean,
101+
tests: [
102+
{
103+
title: 'should return false zero value',
104+
expect: false,
105+
},
106+
{
107+
title: 'should coerce "true" into true',
108+
env: 'true',
109+
expect: true,
110+
},
111+
{
112+
title: 'should coerce "TrUe" into true',
113+
env: 'TrUe',
114+
expect: true,
115+
},
116+
{
117+
title: 'should coerce "yes" into true',
118+
env: 'yes',
119+
expect: true,
120+
},
121+
{
122+
title: 'should coerce "1" into true',
123+
env: '1',
124+
expect: true,
125+
},
126+
{
127+
title: 'should coerce "false" into false',
128+
env: 'false',
129+
options: { default: true },
130+
expect: false,
131+
},
132+
{
133+
title: 'should coerce "FaLsE" into false',
134+
env: 'FaLsE',
135+
options: { default: true },
136+
expect: false,
137+
},
138+
{
139+
title: 'should coerce "no" into false',
140+
env: 'no',
141+
options: { default: true },
142+
expect: false,
143+
},
144+
{
145+
title: 'should coerce "0" into false',
146+
env: '0',
147+
options: { default: true },
148+
expect: false,
149+
},
150+
{
151+
title: 'should return a matching default value',
152+
options: { default: true },
153+
expect: true,
154+
},
155+
{
156+
title: 'should error with non-true/false value',
157+
env: 'foo',
158+
throws: true,
159+
},
160+
],
161+
},
162+
{
163+
method: defineList,
164+
tests: [
165+
{
166+
title: 'should return [] zero value',
167+
expect: [],
168+
},
169+
{
170+
title: 'should coerce comma-separated values into a string list',
171+
env: 'first,second,third',
172+
expect: ['first', 'second', 'third'],
173+
},
174+
{
175+
title:
176+
'should coerce a comma-and-space-separated values into a string list',
177+
env: 'first, second, third',
178+
expect: ['first', 'second', 'third'],
179+
},
180+
{
181+
title: 'should return a matching default value',
182+
options: { default: ['a', 'b', 'c'] },
183+
expect: ['a', 'b', 'c'],
184+
},
185+
],
186+
},
187+
{
188+
method: defineJSON,
189+
tests: [
190+
{
191+
title: 'should return {} zero value',
192+
expect: {},
193+
},
194+
{
195+
title: 'should coerce objects from JSON',
196+
env: '{"test":123}',
197+
expect: { test: 123 },
198+
},
199+
{
200+
title: 'should coerce arrays from JSON',
201+
env: '["test",123]',
202+
expect: ['test', 123],
203+
},
204+
{
205+
title: 'should return a matching default value',
206+
options: { default: { test: 123 } },
207+
expect: { test: 123 },
208+
},
209+
{
210+
title: 'should throw with invalid JSON',
211+
env: '{"invalid":json',
212+
throws: true,
213+
},
214+
],
215+
},
216+
];
217+
218+
for (const group of VALUE_TESTS) {
219+
describe(`${group.method.name}().value`, () => {
220+
for (const test of group.tests) {
221+
it(test.title, () => {
222+
if (typeof test.env !== 'undefined') {
223+
process.env[TEST_PARAM] = test.env;
224+
}
225+
226+
if (test.throws) {
227+
expect(
228+
() => group.method(TEST_PARAM, test.options).value
229+
).to.throw();
230+
} else {
231+
expect(group.method(TEST_PARAM, test.options).value).to.deep.eq(
232+
test.expect
233+
);
234+
}
235+
});
236+
}
237+
});
238+
}
239+
240+
describe('Param', () => {
241+
describe('#toSpec()', () => {
242+
it('should cast non-string defaults to strings', () => {
243+
expect(new Param(TEST_PARAM, { default: 123 }).toSpec().default).to.eq(
244+
'123'
245+
);
246+
});
247+
248+
it('should passthrough supplied options', () => {
249+
expect(
250+
new Param(TEST_PARAM, { description: 'hello expect' }).toSpec()
251+
.description
252+
).to.eq('hello expect');
253+
});
254+
255+
it('ListParam should properly stringify its default', () => {
256+
const spec = new ListParam(TEST_PARAM, {
257+
default: ['a', 'b', 'c'],
258+
}).toSpec();
259+
expect(spec.default).to.eq('a,b,c');
260+
});
261+
});
262+
});
263+
264+
it('should add a param to the declared params', () => {
265+
const param = defineString(TEST_PARAM);
266+
expect(declaredParams.find((p) => p === param)).to.eq(param);
267+
});
268+
269+
it('should replace a samed-name param in the declared params', () => {
270+
const oldParam = defineString(TEST_PARAM);
271+
expect(declaredParams.find((p) => p === oldParam)).to.eq(oldParam);
272+
const param = defineString(TEST_PARAM);
273+
expect(declaredParams.find((p) => p === oldParam)).to.be.undefined;
274+
expect(declaredParams.find((p) => p === param)).to.eq(param);
275+
});
276+
});

src/v2/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@
2323
import * as logger from '../logger';
2424
import * as https from './providers/https';
2525
import * as pubsub from './providers/pubsub';
26+
import * as params from './params';
2627

27-
export { https, pubsub, logger };
28+
export { https, pubsub, logger, params };
2829

2930
export { setGlobalOptions, GlobalOptions } from './options';
3031

src/v2/options.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ import {
2424
durationFromSeconds,
2525
serviceAccountFromShorthand,
2626
} from '../common/encoding';
27-
import { convertIfPresent, copyIfPresent } from '../common/encoding';
2827
import * as logger from '../logger';
28+
import { copyIfPresent, convertIfPresent } from '../common/encoding';
29+
import { ParamSpec } from './params/types';
30+
import { declaredParams } from './params';
2931
import { TriggerAnnotation } from './core';
3032

3133
/**
@@ -271,3 +273,16 @@ export function optionsToTriggerAnnotations(
271273

272274
return annotation;
273275
}
276+
277+
/**
278+
* @hidden
279+
*/
280+
export function __getSpec(): {
281+
globalOptions: GlobalOptions;
282+
params: ParamSpec[];
283+
} {
284+
return {
285+
globalOptions: getGlobalOptions(),
286+
params: declaredParams.map((p) => p.toSpec()),
287+
};
288+
}

0 commit comments

Comments
 (0)