Skip to content

Commit

Permalink
Separate user errors from bugs in the CLI output and telemetry
Browse files Browse the repository at this point in the history
  • Loading branch information
mnapoli committed Jun 18, 2021
1 parent a571f1b commit 5e98a0b
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 12 deletions.
12 changes: 8 additions & 4 deletions src/constructs/StaticWebsite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { log } from "../utils/logger";
import { s3Sync } from "../utils/s3-sync";
import AwsProvider from "../classes/AwsProvider";
import Construct from "../classes/Construct";
import ServerlessError from "../utils/error";

export const STATIC_WEBSITE_DEFINITION = {
type: "object",
Expand Down Expand Up @@ -70,8 +71,10 @@ export class StaticWebsite extends CdkConstruct implements Construct {
super(scope, id);

if (configuration.domain !== undefined && configuration.certificate === undefined) {
throw new Error(
`Invalid configuration for the static website ${id}: if a domain is configured, then a certificate ARN must be configured as well.`
throw new ServerlessError(
`Invalid configuration for the static website '${id}': if a domain is configured, then a certificate ARN must be configured in the 'certificate' option.\n` +
"See https://github.com/getlift/lift/blob/master/docs/static-website.md#custom-domain",
"LIFT_INVALID_CONSTRUCT_CONFIGURATION"
);
}

Expand Down Expand Up @@ -177,8 +180,9 @@ export class StaticWebsite extends CdkConstruct implements Construct {

const bucketName = await this.getBucketName();
if (bucketName === undefined) {
throw new Error(
`Could not find the bucket in which to deploy the '${this.id}' website: did you forget to run 'serverless deploy' first?`
throw new ServerlessError(
`Could not find the bucket in which to deploy the '${this.id}' website: did you forget to run 'serverless deploy' first?`,
"LIFT_MISSING_STACK_OUTPUT"
);
}

Expand Down
13 changes: 9 additions & 4 deletions src/constructs/Webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { FromSchema } from "json-schema-to-ts";
import { PolicyDocument, PolicyStatement, Role, ServicePrincipal } from "@aws-cdk/aws-iam";
import AwsProvider from "../classes/AwsProvider";
import Construct from "../classes/Construct";
import ServerlessError from "../utils/error";

export const WEBHOOK_DEFINITION = {
type: "object",
Expand Down Expand Up @@ -65,17 +66,21 @@ export class Webhook extends CdkConstruct implements Construct {

const resolvedConfiguration = Object.assign({}, WEBHOOK_DEFAULTS, configuration);
if (resolvedConfiguration.insecure && resolvedConfiguration.authorizer !== undefined) {
throw new Error(
throw new ServerlessError(
`Webhook ${id} is specified as insecure, however an authorizer is configured for this webhook. ` +
"Either declare this webhook as secure by removing `insecure: true` property (recommended), " +
"or specify the webhook as insecure and remove the authorizer property altogether."
"or specify the webhook as insecure and remove the authorizer property altogether.\n" +
"See https://github.com/getlift/lift/blob/master/docs/webhook.md#authorizer",
"LIFT_INVALID_CONSTRUCT_CONFIGURATION"
);
}
if (!resolvedConfiguration.insecure && resolvedConfiguration.authorizer === undefined) {
throw new Error(
throw new ServerlessError(
`Webhook ${id} is specified as secure, however no authorizer is configured for this webhook. ` +
"Please provide an authorizer property for this webhook (recommended), " +
"or specify the webhook as insecure by adding `insecure: true` property."
"or specify the webhook as insecure by adding `insecure: true` property.\n" +
"See https://github.com/getlift/lift/blob/master/docs/webhook.md#authorizer",
"LIFT_INVALID_CONSTRUCT_CONFIGURATION"
);
}

Expand Down
18 changes: 14 additions & 4 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import Construct from "./classes/Construct";
import AwsProvider from "./classes/AwsProvider";
import { constructs } from "./constructs";
import { log } from "./utils/logger";
import ServerlessError from "./utils/error";

const CONSTRUCTS_DEFINITION = {
type: "object",
Expand Down Expand Up @@ -106,6 +107,13 @@ class LiftPlugin {
typeof CONSTRUCTS_DEFINITION
>;
for (const [id, configuration] of Object.entries(constructsInputConfiguration)) {
if (!has(constructs, configuration.type)) {
throw new ServerlessError(
`The construct '${id}' has an unknown type '${configuration.type}'\n` +
"Find all construct types available here: https://github.com/getlift/lift#constructs",
"LIFT_UNKNOWN_CONSTRUCT_TYPE"
);
}
const constructConstructor = constructs[configuration.type].class;
// Typescript cannot infer configuration specific to a type, thus computing intersetion of all configurations to never
this.constructs[id] = new constructConstructor(awsProvider.stack, id, configuration as never, awsProvider);
Expand All @@ -115,18 +123,20 @@ class LiftPlugin {
resolveReference({ address }: { address: string }): { value: Record<string, unknown> } {
const [id, property] = address.split(".", 2);
if (!has(this.constructs, id)) {
throw new Error(
`No construct named '${id}' was found, the \${construct:${id}.${property}} variable is invalid.`
throw new ServerlessError(
`No construct named '${id}' was found, the \${construct:${id}.${property}} variable is invalid.`,
"LIFT_VARIABLE_UNKNOWN_CONSTRUCT"
);
}
const construct = this.constructs[id];

const properties = construct.references();
if (!has(properties, property)) {
throw new Error(
throw new ServerlessError(
`\${construct:${id}.${property}} does not exist. Properties available on \${construct:${id}} are: ${Object.keys(
properties
).join(", ")}.`
).join(", ")}.`,
"LIFT_VARIABLE_UNKNOWN_PROPERTY"
);
}

Expand Down
17 changes: 17 additions & 0 deletions src/utils/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Represents a user error.
*
* This class mirrors the official ServerlessError class:
* https://github.com/serverless/serverless/blob/f4c9b58b10a45ae342934e9a61dcdea0c2ef11e2/lib/serverless-error.js
* The original class is available via `serverless.classes.Error` but that means
* we must hold an instance of the `serverless` object to use it.
* That isn't always the case, for example in constructs, which are decoupled from the `serverless` object.
*/
export default class ServerlessError extends Error {
private code: string;
constructor(message: string, code: string) {
super(message);
this.name = "ServerlessError";
this.code = code;
}
}

0 comments on commit 5e98a0b

Please sign in to comment.