@@ -57,16 +57,26 @@ export abstract class Expression<T extends string | number | boolean | string[]>
5757 }
5858}
5959
60- function quoteIfString < T extends string | number | boolean | string [ ] > ( literal : T ) : T {
61- // TODO(vsfan@): CEL's string escape semantics are slightly different than Javascript's, what do we do here?
62- return typeof literal === "string" ? ( `"${ literal } "` as T ) : literal ;
63- }
64-
6560function valueOf < T extends string | number | boolean | string [ ] > ( arg : T | Expression < T > ) : T {
6661 return arg instanceof Expression ? arg . runtimeValue ( ) : arg ;
6762}
63+ /**
64+ * Returns how an entity (either an Expression or a literal value) should be represented in CEL.
65+ * - Expressions delegate to the .toString() method, which is used by the WireManifest
66+ * - Strings have to be quoted explicitly
67+ * - Arrays are represented as []-delimited, parsable JSON
68+ * - Numbers and booleans are not quoted explicitly
69+ */
6870function refOf < T extends string | number | boolean | string [ ] > ( arg : T | Expression < T > ) : string {
69- return arg instanceof Expression ? arg . toString ( ) : quoteIfString ( arg ) . toString ( ) ;
71+ if ( arg instanceof Expression ) {
72+ return arg . toString ( ) ;
73+ } else if ( typeof arg === "string" ) {
74+ return `"${ arg } "` ;
75+ } else if ( Array . isArray ( arg ) ) {
76+ return JSON . stringify ( arg ) ;
77+ } else {
78+ return arg . toString ( ) ;
79+ }
7080}
7181
7282/**
@@ -123,9 +133,9 @@ export class CompareExpression<
123133 const right = valueOf ( this . rhs ) ;
124134 switch ( this . cmp ) {
125135 case "==" :
126- return left === right ;
136+ return Array . isArray ( left ) ? this . arrayEquals ( left , right as string [ ] ) : left === right ;
127137 case "!=" :
128- return left !== right ;
138+ return Array . isArray ( left ) ? ! this . arrayEquals ( left , right as string [ ] ) : left !== right ;
129139 case ">" :
130140 return left > right ;
131141 case ">=" :
@@ -139,6 +149,11 @@ export class CompareExpression<
139149 }
140150 }
141151
152+ /** @internal */
153+ arrayEquals ( a : string [ ] , b : string [ ] ) : boolean {
154+ return a . every ( ( item ) => b . includes ( item ) ) && b . every ( ( item ) => a . includes ( item ) ) ;
155+ }
156+
142157 toString ( ) {
143158 const rhsStr = refOf ( this . rhs ) ;
144159 return `${ this . lhs } ${ this . cmp } ${ rhsStr } ` ;
@@ -159,6 +174,7 @@ type ParamValueType = "string" | "list" | "boolean" | "int" | "float" | "secret"
159174type ParamInput < T > =
160175 | { text : TextInput < T > }
161176 | { select : SelectInput < T > }
177+ | { multiSelect : MultiSelectInput }
162178 | { resource : ResourceInput } ;
163179
164180/**
@@ -201,6 +217,15 @@ export interface SelectInput<T = unknown> {
201217 options : Array < SelectOptions < T > > ;
202218}
203219
220+ /**
221+ * Specifies that a Param's value should be determined by having the user select
222+ * a subset from a list of pre-canned options interactively at deploy-time.
223+ * Will result in errors if used on Params of type other than string[].
224+ */
225+ export interface MultiSelectInput {
226+ options : Array < SelectOptions < string > > ;
227+ }
228+
204229/**
205230 * One of the options provided to a SelectInput, containing a value and
206231 * optionally a human-readable label to display in the selection interface.
@@ -464,3 +489,44 @@ export class BooleanParam extends Param<boolean> {
464489 return new TernaryExpression ( this , ifTrue , ifFalse ) ;
465490 }
466491}
492+
493+ /**
494+ * A parametrized value of String[] type that will be read from .env files
495+ * if present, or prompted for by the CLI if missing.
496+ */
497+ export class ListParam extends Param < string [ ] > {
498+ static type : ParamValueType = "list" ;
499+
500+ /** @internal */
501+ runtimeValue ( ) : string [ ] {
502+ const val = JSON . parse ( process . env [ this . name ] ) ;
503+ if ( ! Array . isArray ( val ) || ! ( val as string [ ] ) . every ( ( v ) => typeof v === "string" ) ) {
504+ return [ ] ;
505+ }
506+ return val as string [ ] ;
507+ }
508+
509+ /** @hidden */
510+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
511+ greaterThan ( rhs : string [ ] | Expression < string [ ] > ) : CompareExpression < string [ ] > {
512+ throw new Error ( ">/< comparison operators not supported on params of type List" ) ;
513+ }
514+
515+ /** @hidden */
516+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
517+ greaterThanOrEqualTo ( rhs : string [ ] | Expression < string [ ] > ) : CompareExpression < string [ ] > {
518+ throw new Error ( ">/< comparison operators not supported on params of type List" ) ;
519+ }
520+
521+ /** @hidden */
522+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
523+ lessThan ( rhs : string [ ] | Expression < string [ ] > ) : CompareExpression < string [ ] > {
524+ throw new Error ( ">/< comparison operators not supported on params of type List" ) ;
525+ }
526+
527+ /** @hidden */
528+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
529+ lessThanorEqualTo ( rhs : string [ ] | Expression < string [ ] > ) : CompareExpression < string [ ] > {
530+ throw new Error ( ">/< comparison operators not supported on params of type List" ) ;
531+ }
532+ }
0 commit comments