Converting an Output to a string or JSON now produces a warning . (#2496)

This commit is contained in:
CyrusNajmabadi 2019-02-27 16:00:54 -08:00 committed by GitHub
parent 0d2d1eb61a
commit aa3c6d0ba6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 86 additions and 11 deletions

View file

@ -2,6 +2,11 @@
### Improvements
- Attempting to convert an [Output<T>] to a string or to JSON will now result in a warning
message being printed, as well as information on how to rectify the situation. This is
to help with diagnosing cryptic problems that can occur when Outputs are accidentally
concatenated into a string in some part of the program.
## 0.16.17 (Released February 27th, 2019)
### Improvements

View file

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import * as log from "./log";
import { Resource } from "./resource";
import * as runtime from "./runtime";
@ -94,6 +95,38 @@ export class Output<T> {
*/
public readonly get: () => T;
/**
* [toString] on an [Output<T>] is not supported. This is because the value an [Output] points
* to is asynchronously computed (and thus, this is akin to calling [toString] on a [Promise]).
*
* Calling this will simply return useful text about the issue, and will log a warning. In a
* future version of `@pulumi/pulumi` this will be changed to throw an error when this occurs.
*
* To get the value of an Output<T> as an Output<string> consider either:
* 1. `o.apply(v => ``prefix${v}suffix``)` or
* 2. `pulumi.interpolate ``prefix${v}suffix`` `
*
* This will return an Output with the inner computed value and all resources still tracked. See
* https://pulumi.io/help/outputs for more details
*/
/** @internal */ public toString: () => string;
/**
* [toJSON] on an [Output<T>] is not supported. This is because the value an [Output] points
* to is asynchronously computed (and thus, this is akin to calling [toJSON] on a [Promise]).
*
* Calling this will simply return useful text about the issue, and will log a warning. In a
* future version of `@pulumi/pulumi` this will be changed to throw an error when this occurs.
*
* To get the value of an Output as a JSON value or JSON string consider either:
* 1. `o.apply(v => v.toJSON())` or
* 2. `o.apply(v => JSON.stringify(v))`
*
* This will return an Output with the inner computed value and all resources still tracked.
* See https://pulumi.io/help/outputs for more details
*/
/** @internal */ public toJSON: () => any;
// Statics
/**
@ -118,17 +151,49 @@ export class Output<T> {
resources: Set<Resource> | Resource[] | Resource, promise: Promise<T>, isKnown: Promise<boolean>) {
this.isKnown = isKnown;
let resourcesArray: Resource[];
// Always create a copy so that no one accidentally modifies our Resource list.
if (Array.isArray(resources)) {
this.resources = () => new Set<Resource>(resources);
resourcesArray = resources;
} else if (resources instanceof Set) {
this.resources = () => new Set<Resource>(resources);
resourcesArray = [...resources];
} else {
this.resources = () => new Set<Resource>([resources]);
resourcesArray = [resources];
}
this.resources = () => new Set<Resource>(resourcesArray);
this.promise = () => promise;
const firstResource = resourcesArray[0];
this.toString = () => {
const message =
`Calling [toString] on an [Output<T>] is not supported.
To get the value of an Output<T> as an Output<string> consider either:
1: o.apply(v => \`prefix\${v}suffix\`)
2: pulumi.interpolate \`prefix\${v}suffix\`
See https://pulumi.io/help/outputs for more details.
This function may throw in a future version of @pulumi/pulumi.`;
log.warn(message, firstResource);
return message;
};
this.toJSON = () => {
const message =
`Calling [toJSON] on an [Output<T>] is not supported.
To get the value of an Output as a JSON value or JSON string consider either:
1: o.apply(v => v.toJSON())
2: o.apply(v => JSON.stringify(v))
See https://pulumi.io/help/outputs for more details.
This function may throw in a future version of @pulumi/pulumi.`;
log.warn(message, firstResource);
return message;
};
this.apply = <U>(func: (t: T) => Input<U>) => {
let innerIsKnownResolve: (val: boolean) => void;
const innerIsKnown = new Promise<boolean>(resolve => {

View file

@ -193,6 +193,10 @@ class SerializedOutput<T> implements Output<T> {
/* @internal */ public isKnown: Promise<boolean>;
/* @internal */ public readonly promise: () => Promise<T>;
/* @internal */ public readonly resources: () => Set<resource.Resource>;
/* @internal */ public readonly toString: () => string;
/* @internal */ public readonly toJSON: () => any;
/* @internal */ private readonly value: T;
public constructor(value: T) {

View file

@ -65,7 +65,7 @@ export function readResource(res: Resource, t: string, name: string, props: Inpu
}
const label = `resource:${name}[${t}]#...`;
log.debug(`Reading resource: id=${id}, t=${t}, name=${name}`);
log.debug(`Reading resource: id=${Output.isInstance(id) ? "Output<T>" : id}, t=${t}, name=${name}`);
const monitor: any = getMonitor();
const resopAsync = prepareResource(label, res, true, props, opts);

View file

@ -56,14 +56,15 @@ export function transferProperties(onto: Resource, label: string, props: Inputs)
resolveIsKnown(isKnown);
};
const propString = Output.isInstance(props[k]) ? "Output<T>" : `${props[k]}`;
(<any>onto)[k] = new Output(
onto,
debuggablePromise(
new Promise<any>(resolve => resolveValue = resolve),
`transferProperty(${label}, ${k}, ${props[k]})`),
`transferProperty(${label}, ${k}, ${propString})`),
debuggablePromise(
new Promise<boolean>(resolve => resolveIsKnown = resolve),
`transferIsStable(${label}, ${k}, ${props[k]})`));
`transferIsStable(${label}, ${k}, ${propString})`));
}
return resolvers;

View file

@ -5044,10 +5044,10 @@ return function () { typescript.parseCommandLine([""]); };
function 'func': tsClosureCases.js(0,0): captured
module './bin/index.js' which indirectly referenced
function 'debug':(...)
module './bin/runtime/settings.js' which indirectly referenced
function 'getEngine':(...)
module './bin/proto/engine_grpc_pb.js' which indirectly referenced
function 'Output':(...)
module './bin/log/index.js' which indirectly referenced
function 'warn':(...)
module './bin/runtime/settings.js' which indirectly referenced
(...)
Function code:
(...)
@ -5485,7 +5485,7 @@ return function () { console.log(regex); foo(); };
return;
}
// if (test.title !== "Output capture") {
// if (test.title !== "Fail to capture non-deployment module due to native code") {
// continue;
// }