Repository: mbonig/state-machine (language: python)

Issue:
feature request: support of YAML 
Since AWS [offers support also for YAML workflow files](https://aws.amazon.com/about-aws/whats-new/2021/03/aws-step-functions-adds-tooling-support-for-yaml/?nc1=h_ls), it would be great to have possibility to declare a state machine in YAML.




Discussion / hints:

Fix patch:
diff --git a/.projen/deps.json b/.projen/deps.json
index a147a16..a2efd0d 100644
--- a/.projen/deps.json
+++ b/.projen/deps.json
@@ -5,6 +5,10 @@
       "version": "^27",
       "type": "build"
     },
+    {
+      "name": "@types/js-yaml",
+      "type": "build"
+    },
     {
       "name": "@types/node",
       "version": "^14",
@@ -103,6 +107,10 @@
       "name": "case",
       "type": "bundled"
     },
+    {
+      "name": "js-yaml",
+      "type": "bundled"
+    },
     {
       "name": "lodash.merge",
       "type": "bundled"
@@ -135,6 +143,10 @@
       "name": "case",
       "type": "runtime"
     },
+    {
+      "name": "js-yaml",
+      "type": "runtime"
+    },
     {
       "name": "lodash.merge",
       "type": "runtime"
diff --git a/.projenrc.ts b/.projenrc.ts
index 8f99d58..861c2c6 100644
--- a/.projenrc.ts
+++ b/.projenrc.ts
@@ -11,9 +11,10 @@ const project = new awscdk.AwsCdkConstructLibrary({
   constructsVersion: '10.1.203',
   name: '@matthewbonig/state-machine',
   repositoryUrl: 'https://github.com/mbonig/state-machine.git',
-  deps: [lodash, 'case', 'projen'],
+  deps: [lodash, 'case', 'projen', 'js-yaml'],
   peerDeps: ['projen', 'constructs'],
-  bundledDeps: [lodash, 'case'],
+  devDeps: ['@types/js-yaml'],
+  bundledDeps: [lodash, 'case', 'js-yaml'],
   npmAccess: NpmAccess.PUBLIC,
   gitignore: ['.idea/'],
   keywords: ['awscdk', 'cdk', 'AWS Step Functions'],
diff --git a/API.md b/API.md
index 7ee3d99..fb931a5 100644
--- a/API.md
+++ b/API.md
@@ -588,6 +588,7 @@ const stateMachineProps: StateMachineProps = { ... }
 | **Name** | **Type** | **Description** |
 | --- | --- | --- |
 | <code><a href="#@matthewbonig/state-machine.StateMachineProps.property.definition">definition</a></code> | <code>any</code> | An object that can be serialized into an ASL. |
+| <code><a href="#@matthewbonig/state-machine.StateMachineProps.property.aslYaml">aslYaml</a></code> | <code>boolean</code> | Should the ASL definition be written as YAML. |
 | <code><a href="#@matthewbonig/state-machine.StateMachineProps.property.logs">logs</a></code> | <code>aws-cdk-lib.aws_stepfunctions.LogOptions</code> | Defines what execution history events are logged and where they are logged. |
 | <code><a href="#@matthewbonig/state-machine.StateMachineProps.property.overrides">overrides</a></code> | <code>any</code> | An object that matches the schema/shape of the ASL .States map with overridden values. |
 | <code><a href="#@matthewbonig/state-machine.StateMachineProps.property.role">role</a></code> | <code>aws-cdk-lib.aws_iam.IRole</code> | The execution role for the state machine service. |
@@ -610,6 +611,19 @@ An object that can be serialized into an ASL.
 
 ---
 
+##### `aslYaml`<sup>Optional</sup> <a name="aslYaml" id="@matthewbonig/state-machine.StateMachineProps.property.aslYaml"></a>
+
+```typescript
+public readonly aslYaml: boolean;
+```
+
+- *Type:* boolean
+- *Default:* false
+
+Should the ASL definition be written as YAML.
+
+---
+
 ##### `logs`<sup>Optional</sup> <a name="logs" id="@matthewbonig/state-machine.StateMachineProps.property.logs"></a>
 
 ```typescript
@@ -709,10 +723,6 @@ Specifies whether Amazon X-Ray tracing is enabled for this state machine.
 
 ### StepFunctionsAutoDiscoverOptions <a name="StepFunctionsAutoDiscoverOptions" id="@matthewbonig/state-machine.StepFunctionsAutoDiscoverOptions"></a>
 
-For future use.
-
-No properties, yet.
-
 #### Initializer <a name="Initializer" id="@matthewbonig/state-machine.StepFunctionsAutoDiscoverOptions.Initializer"></a>
 
 ```typescript
@@ -721,6 +731,26 @@ import { StepFunctionsAutoDiscoverOptions } from '@matthewbonig/state-machine'
 const stepFunctionsAutoDiscoverOptions: StepFunctionsAutoDiscoverOptions = { ... }
 ```
 
+#### Properties <a name="Properties" id="Properties"></a>
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| <code><a href="#@matthewbonig/state-machine.StepFunctionsAutoDiscoverOptions.property.extension">extension</a></code> | <code>string</code> | An optional extension to use for discovering state machine files. |
+
+---
+
+##### `extension`<sup>Optional</sup> <a name="extension" id="@matthewbonig/state-machine.StepFunctionsAutoDiscoverOptions.property.extension"></a>
+
+```typescript
+public readonly extension: string;
+```
+
+- *Type:* string
+- *Default:* '.workflow.json' (JSON_STEPFUNCTION_EXT)
+
+An optional extension to use for discovering state machine files.
+
+---
 
 ## Classes <a name="Classes" id="Classes"></a>
 
diff --git a/README.md b/README.md
index c887424..4d9bd26 100644
--- a/README.md
+++ b/README.md
@@ -72,6 +72,48 @@ export class SomeStack extends Stack {
 > :warning: **The interfaces and constructs generated here are NOT jsii compliant (they use Partials and Omits) and cannot be 
 compiled by jsii into other languages. If you plan to distribute any libraries you cannot use this.**
 
+### Alternative Extensions
+
+There is an optional parameter, `extension` that you can pass to have it search for alternative extensions.
+AWS recommends that ASL definition files have a `.asl.json` extension, which will be picked up by some IDE
+tools. This extension was recommended after initial development of this component. Therefore, the default is
+to use the original extension. But, you can override this by passing a different extension to the
+AutoDiscover's constructor options. There are two constants defined, `JSON_STEPFUNCTION_EXT` and `AWS_RECOMMENDED_JSON_EXT` that you can use.
+
+```js
+// ...
+const { StepFunctionsAutoDiscover, AWS_RECOMMENDED_EXT } = require('@matthewbonig/state-machine');
+
+const project = new awscdk.AwsCdkTypeScriptApp({
+  // ...,
+  deps: [
+    // ...,
+    '@matthewbonig/state-machine',
+  ]
+});
+
+new StepFunctionsAutoDiscover(project, { extension: AWS_RECOMMENDED_JSON_EXT });
+```
+
+### Yaml files
+
+Yaml files are supported as well. You can provide an extension to the AutoDiscover component to have it search for yaml files. If the file has 'yaml' or 'yml' anywhere in the name it will be parsed as yaml. If not, it will be parsed as json.
+
+```js
+// ...
+const { StepFunctionsAutoDiscover } = require('@matthewbonig/state-machine');
+
+const project = new awscdk.AwsCdkTypeScriptApp({
+  // ...,
+  deps: [
+    // ...,
+    '@matthewbonig/state-machine',
+  ]
+});
+
+new StepFunctionsAutoDiscover(project, { extension: '.yaml.asl' });
+```
+
 ### Examples
 
 ```ts
@@ -156,8 +198,6 @@ new StateMachine(stack, 'Test', {
   });
 ```
 
-
-
 For Python, be sure to use a context manager when opening your JSON file.  
 - You do not need to `str()` the dictionary object you supply as your `definition` prop.  
 - Elements of your override path **do** need to be strings.
@@ -180,6 +220,7 @@ state_machine = StateMachine(
     },
   })
 ```
+
 In this example, the ASL has a state called 'Read database credentials secret' and the SecretId parameter is overridden with a 
 CDK generated value.
 Future changes can be done by editing, debugging, and testing the state machine directly in the Workflow Studio.
diff --git a/package.json b/package.json
index a22f547..3a628f5 100644
--- a/package.json
+++ b/package.json
@@ -35,6 +35,7 @@
   },
   "devDependencies": {
     "@types/jest": "^27",
+    "@types/js-yaml": "*",
     "@types/node": "^14",
     "@typescript-eslint/eslint-plugin": "^5",
     "@typescript-eslint/parser": "^5",
@@ -64,11 +65,13 @@
   },
   "dependencies": {
     "case": "^1.6.3",
+    "js-yaml": "^4.1.0",
     "lodash.merge": "^4.6.2",
     "projen": "^0.65.74"
   },
   "bundledDependencies": [
     "case",
+    "js-yaml",
     "lodash.merge"
   ],
   "keywords": [
diff --git a/src/StateMachine.ts b/src/StateMachine.ts
index bc3d8ab..7c9f802 100644
--- a/src/StateMachine.ts
+++ b/src/StateMachine.ts
@@ -3,6 +3,8 @@ import * as iam from 'aws-cdk-lib/aws-iam';
 import * as aws_stepfunctions from 'aws-cdk-lib/aws-stepfunctions';
 import { CfnStateMachine, LogOptions, Pass, StateMachineType } from 'aws-cdk-lib/aws-stepfunctions';
 import { Construct } from 'constructs';
+import * as yaml from 'js-yaml';
+
 // eslint-disable-next-line @typescript-eslint/no-require-imports
 const merge = require('lodash.merge');
 
@@ -62,6 +64,13 @@ export interface StateMachineProps {
    */
   readonly tracingEnabled?: boolean;
 
+  /**
+   * Should the ASL definition be written as YAML
+   *
+   * @default false
+   */
+  readonly aslYaml?: boolean;
+
 }
 
 export class StateMachine extends aws_stepfunctions.StateMachine {
@@ -73,7 +82,14 @@ export class StateMachine extends aws_stepfunctions.StateMachine {
     });
     scope.node.tryRemoveChild('THISWILLBEDELETEDRIGHTAWAY');
     const mergedDefinition = merge(props.definition, { States: props.overrides });
-    (this.node.defaultChild as CfnStateMachine).definitionString = JSON.stringify(mergedDefinition);
+    let definitionString: string;
+    if (props.aslYaml) {
+      definitionString = yaml.dump(mergedDefinition);
+    } else {
+      definitionString = JSON.stringify(mergedDefinition);
+
+    }
+    (this.node.defaultChild as CfnStateMachine).definitionString = definitionString;
   }
 }
 
diff --git a/src/StepFunctionsAutoDiscover.ts b/src/StepFunctionsAutoDiscover.ts
index ffcee90..67091a7 100644
--- a/src/StepFunctionsAutoDiscover.ts
+++ b/src/StepFunctionsAutoDiscover.ts
@@ -2,19 +2,35 @@ import * as fs from 'fs';
 import * as path from 'path';
 import { basename, dirname, extname, join } from 'path';
 import { pascal } from 'case';
+import * as yaml from 'js-yaml';
 import { Component, Project, SourceCode } from 'projen';
 import { AwsCdkTypeScriptApp } from 'projen/lib/awscdk';
 import { AwsCdkDeps } from 'projen/lib/awscdk/awscdk-deps';
 import { AutoDiscoverBase } from 'projen/lib/cdk';
 import { buildStateType } from './BuildStateType';
 
-const JSON_STEPFUNCTION_EXT = '.workflow.json';
+/**
+ * The original extension used to search for ASL files.
+ */
+export const JSON_STEPFUNCTION_EXT = '.workflow.json';
 
 /**
- * For future use. No properties, yet.
+ * The AWS-recommended extension for ASL files.
  */
-export interface StepFunctionsAutoDiscoverOptions {
+export const AWS_RECOMMENDED_JSON_EXT = '.json.asl';
 
+/**
+ * The AWS-recommended extension for YAML ASL files.
+ */
+export const AWS_RECOMMENDED_YAML_EXT = '.yaml.asl';
+
+export interface StepFunctionsAutoDiscoverOptions {
+  /**
+   * An optional extension to use for discovering state machine files.
+   *
+   * @default '.workflow.json' (JSON_STEPFUNCTION_EXT)
+   */
+  readonly extension?: string;
 }
 
 /**
@@ -33,20 +49,28 @@ export interface StepFunctionsAutoDiscoverOptions {
  */
 export class StepFunctionsAutoDiscover extends AutoDiscoverBase {
   constructor(project: AwsCdkTypeScriptApp, _options?: StepFunctionsAutoDiscoverOptions) {
+    if (_options?.extension) {
+      if (!_options.extension.startsWith('.')) {
+        throw new Error('extension must start with a .');
+      }
+    }
+    let extension = _options?.extension || JSON_STEPFUNCTION_EXT;
     super(project, {
-      extension: JSON_STEPFUNCTION_EXT,
+      extension: extension,
       projectdir: project.srcdir,
     });
     for (const entrypoint of this.entrypoints) {
       new StepFunctionsStateMachine(this.project, {
         workflowAsl: entrypoint,
         cdkDeps: project.cdkDeps,
+        extension,
       });
     }
   }
 }
 
 export interface StepFunctionsStateMachineOptions {
+  readonly extension: string;
   readonly constructFile?: string;
   readonly workflowAsl: string;
   readonly constructName?: string;
@@ -60,20 +84,21 @@ export class StepFunctionsStateMachine extends Component {
   constructor(project: Project, options: StepFunctionsStateMachineOptions) {
     super(project);
 
+    const extension = options.extension ?? JSON_STEPFUNCTION_EXT;
     const workflowAsl = options.workflowAsl;
 
     if (
-      !workflowAsl.endsWith(JSON_STEPFUNCTION_EXT)
+      !workflowAsl.endsWith(extension)
     ) {
       throw new Error(
-        `${workflowAsl} must have a ${JSON_STEPFUNCTION_EXT} extension`,
+        `${workflowAsl} must have a ${extension} extension`,
       );
     }
     const basePath = join(
       dirname(workflowAsl),
       basename(
         workflowAsl,
-        JSON_STEPFUNCTION_EXT,
+        extension,
       ),
     );
     const constructFile = options.constructFile ?? `${basePath}-statemachine.ts`;
@@ -97,9 +122,20 @@ export class StepFunctionsStateMachine extends Component {
     src.line('import fs from \'fs\';');
     src.line('import path from \'path\';');
 
+    let isYaml = /(yaml|yml)/.test(extension);
+    if (isYaml) {
+      src.line('import * as yaml \'js-yaml\';');
+    }
+
     src.open(`export interface ${constructName}Overrides {`);
 
-    const workflowDefinition = JSON.parse(fs.readFileSync(join(project.outdir, workflowAsl)).toString());
+    let workflowDefinition: any;
+    if (isYaml) {
+      workflowDefinition = yaml.load(fs.readFileSync(join(project.outdir, workflowAsl)).toString());
+    } else {
+      workflowDefinition = JSON.parse(fs.readFileSync(join(project.outdir, workflowAsl)).toString());
+
+    }
     if (!workflowDefinition.States) {
       throw new Error(`The workflow file ${workflowAsl} doesn't appear to be a valid ASL file, it doesn't contain a 'States' field`);
     }
@@ -119,7 +155,13 @@ export class StepFunctionsStateMachine extends Component {
     src.open('super(scope, id, {');
     src.line('...props,');
     const relativeFile = path.basename(workflowAsl);
-    src.line(`definition: JSON.parse(fs.readFileSync(path.join(__dirname, '${relativeFile}')).toString()),`);
+    if (isYaml) {
+      src.line(`definition: yaml.load(fs.readFileSync(path.join(__dirname, '${relativeFile}')).toString()),`);
+      src.line('aslYaml: true,');
+    } else {
+      src.line(`definition: JSON.parse(fs.readFileSync(path.join(__dirname, '${relativeFile}')).toString()),`);
+
+    }
     src.close('});');
     src.close('}');
     src.close('}');
diff --git a/yarn.lock b/yarn.lock
index 56c5f47..c82ce47 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -774,6 +774,11 @@
     jest-matcher-utils "^27.0.0"
     pretty-format "^27.0.0"
 
+"@types/js-yaml@*":
+  version "4.0.5"
+  resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138"
+  integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==
+
 "@types/json-schema@^7.0.9":
   version "7.0.11"
   resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"


Test patch:
diff --git a/test/StateMachineAutoDiscover.test.ts b/test/StateMachineAutoDiscover.test.ts
index 3e632ea..a2fce95 100644
--- a/test/StateMachineAutoDiscover.test.ts
+++ b/test/StateMachineAutoDiscover.test.ts
@@ -5,38 +5,67 @@ import * as path from 'path';
 import { AwsCdkTypeScriptApp } from 'projen/lib/awscdk';
 import { synthSnapshot } from 'projen/lib/util/synth';
 import { StepFunctionsAutoDiscover } from '../src';
+import { AWS_RECOMMENDED_JSON_EXT, AWS_RECOMMENDED_YAML_EXT, JSON_STEPFUNCTION_EXT } from '../src/StepFunctionsAutoDiscover';
 
 
-describe('AutoDiscover', () => {
-
-  function setupTestProject(testFile: string) {
-    const tempDir = mkdtempSync(path.join(os.tmpdir(), 'test2'));
-    const project = new AwsCdkTypeScriptApp({
-      name: 'test',
-      defaultReleaseBranch: 'main',
-      cdkVersion: '2.53.0',
-      outdir: tempDir,
-    });
-
-    // add a test file...
-    fs.mkdirSync(path.join(project.outdir, project.srcdir));
-    fs.copyFileSync(
-      path.join(__dirname, 'step-functions', testFile),
-      path.join(project.outdir, project.srcdir, 'test.workflow.json'),
-    );
-    new StepFunctionsAutoDiscover(project);
-    return project;
-  }
-
-  test('test 1', () => {
+function setupTestProject(testFile: string, extension: string = JSON_STEPFUNCTION_EXT, srcFile: string = 'test.workflow.json') {
+  const tempDir = mkdtempSync(path.join(os.tmpdir(), 'test2'));
+  const project = new AwsCdkTypeScriptApp({
+    name: 'test',
+    defaultReleaseBranch: 'main',
+    cdkVersion: '2.53.0',
+    outdir: tempDir,
+  });
+
+  // add a test file...
+  fs.mkdirSync(path.join(project.outdir, project.srcdir));
+  fs.copyFileSync(
+    path.join(__dirname, 'step-functions', testFile),
+    path.join(project.outdir, project.srcdir, srcFile),
+  );
+  new StepFunctionsAutoDiscover(project, { extension: extension });
+  return project;
+}
+
+
+describe('Json', () => {
+  test('simple case', () => {
     const project = setupTestProject('test.workflow.json');
     const snap = synthSnapshot(project);
     expect(snap['src/test-statemachine.ts']).toMatchSnapshot();
   });
 
-  test('test 2', async () => {
+  test('more complex case', async () => {
     const project = setupTestProject('test2.workflow.json');
     const snap = synthSnapshot(project);
     expect(snap['src/test-statemachine.ts']).toMatchSnapshot();
   });
 });
+
+describe('yaml', () => {
+  test('handles yaml file', async () => {
+    const project = setupTestProject('test.yaml.asl', AWS_RECOMMENDED_YAML_EXT, 'test.yaml.asl' );
+    const snap = synthSnapshot(project);
+    expect(snap['src/test-statemachine.ts']).toMatchSnapshot();
+  });
+
+  test('handles yml file', async () => {
+    const project = setupTestProject('test.yaml.asl', '.yml.asl', 'test.yml.asl' );
+    const snap = synthSnapshot(project);
+    expect(snap['src/test-statemachine.ts']).toMatchSnapshot();
+  });
+
+});
+
+describe('Extension parameter', () => {
+  test('takes another extension', async () => {
+    const project = setupTestProject('test.json.asl', AWS_RECOMMENDED_JSON_EXT, 'test.json.asl');
+    const snap = synthSnapshot(project);
+    expect(snap['src/test-statemachine.ts']).toMatchSnapshot();
+  });
+
+  test('fails when extension does not start with a .', () => {
+    expect(() => setupTestProject('test.json.asl', 'asdfasdf', 'test.json.asl')).toThrow('extension must start with a .');
+  });
+});
+
diff --git a/test/__snapshots__/StateMachineAutoDiscover.test.ts.snap b/test/__snapshots__/StateMachineAutoDiscover.test.ts.snap
index bb47448..203a97b 100644
--- a/test/__snapshots__/StateMachineAutoDiscover.test.ts.snap
+++ b/test/__snapshots__/StateMachineAutoDiscover.test.ts.snap
@@ -1,6 +1,6 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`AutoDiscover test 1 1`] = `
+exports[`Extension parameter takes another extension 1`] = `
 "// ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".
 import { StateMachine, StateMachineProps } from '@matthewbonig/state-machine';
 import { Construct } from 'constructs';
@@ -271,13 +271,13 @@ export class TestStateMachine extends StateMachine {
   constructor(scope: Construct, id: string, props: TestStateMachineProps) {
     super(scope, id, {
       ...props,
-      definition: JSON.parse(fs.readFileSync(path.join(__dirname, 'test.workflow.json')).toString()),
+      definition: JSON.parse(fs.readFileSync(path.join(__dirname, 'test.json.asl')).toString()),
     });
   }
 }"
 `;
 
-exports[`AutoDiscover test 2 1`] = `
+exports[`Json more complex case 1`] = `
 "// ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".
 import { StateMachine, StateMachineProps } from '@matthewbonig/state-machine';
 import { Construct } from 'constructs';
@@ -409,3 +409,442 @@ export class TestStateMachine extends StateMachine {
   }
 }"
 `;
+
+exports[`Json simple case 1`] = `
+"// ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".
+import { StateMachine, StateMachineProps } from '@matthewbonig/state-machine';
+import { Construct } from 'constructs';
+import fs from 'fs';
+import path from 'path';
+export interface TestStateMachineOverrides {
+  readonly 'Which database?': Partial<{
+    'Type': string;
+    'Choices': Partial<[Partial<{
+      'Variable': string;
+      'StringMatches': string;
+      'Next': string;
+    }>,Partial<{
+      'Variable': string;
+      'StringMatches': string;
+      'Next': string;
+    }>,Partial<{
+      'Variable': string;
+      'StringMatches': string;
+      'Next': string;
+    }>]>;
+  }>;
+  readonly 'Get belle secret': Partial<{
+    'Type': string;
+    'Next': string;
+    'Parameters': Partial<{
+      'SecretId': string;
+    }>;
+    'Resource': string;
+    'ResultPath': string;
+  }>;
+  readonly 'Parse belle secret value': Partial<{
+    'Type': string;
+    'Parameters': Partial<{
+      'ParsedValue.$': string;
+    }>;
+    'ResultPath': string;
+    'Next': string;
+  }>;
+  readonly 'Create new database username and password': Partial<{
+    'Type': string;
+    'Resource': string;
+    'Parameters': Partial<{
+      'Payload.$': string;
+      'FunctionName': string;
+    }>;
+    'Retry': Partial<[Partial<{
+      'ErrorEquals': Partial<[string,string,string]>;
+      'IntervalSeconds': number;
+      'MaxAttempts': number;
+      'BackoffRate': number;
+    }>]>;
+    'ResultPath': string;
+    'Comment': string;
+    'Next': string;
+    'ResultSelector': Partial<{
+      'username.$': string;
+      'password.$': string;
+      'otsUrl.$': string;
+    }>;
+  }>;
+  readonly 'Create user on the database': Partial<{
+    'Type': string;
+    'Resource': string;
+    'Parameters': Partial<{
+      'FunctionName': string;
+      'Payload': Partial<{
+        'script.$': string;
+        'databaseName.$': string;
+      }>;
+    }>;
+    'Retry': Partial<[Partial<{
+      'ErrorEquals': Partial<[string,string,string]>;
+      'IntervalSeconds': number;
+      'MaxAttempts': number;
+      'BackoffRate': number;
+    }>]>;
+    'ResultPath': string;
+    'Next': string;
+  }>;
+  readonly 'Send credentials to user': Partial<{
+    'Type': string;
+    'Parameters': Partial<{
+      'Destination': Partial<{
+        'ToAddresses.$': string;
+      }>;
+      'Message': Partial<{
+        'Body': Partial<{
+          'Text': Partial<{
+            'Data.$': string;
+          }>;
+        }>;
+        'Subject': Partial<{
+          'Data': string;
+        }>;
+      }>;
+      'Source': string;
+    }>;
+    'Resource': string;
+    'End': Partial<{
+    }>;
+  }>;
+  readonly 'Get engines secret': Partial<{
+    'Type': string;
+    'Parameters': Partial<{
+      'SecretId': string;
+    }>;
+    'Resource': string;
+    'ResultPath': string;
+    'Next': string;
+  }>;
+  readonly 'Parse engines secret value': Partial<{
+    'Type': string;
+    'Parameters': Partial<{
+      'ParsedValue.$': string;
+    }>;
+    'ResultPath': string;
+    'Next': string;
+  }>;
+  readonly 'Create new database username and password for engines': Partial<{
+    'Type': string;
+    'Resource': string;
+    'Parameters': Partial<{
+      'Payload.$': string;
+      'FunctionName': string;
+    }>;
+    'Retry': Partial<[Partial<{
+      'ErrorEquals': Partial<[string,string,string]>;
+      'IntervalSeconds': number;
+      'MaxAttempts': number;
+      'BackoffRate': number;
+    }>]>;
+    'ResultPath': string;
+    'Comment': string;
+    'ResultSelector': Partial<{
+      'username.$': string;
+      'password.$': string;
+      'otsUrl.$': string;
+    }>;
+    'Next': string;
+  }>;
+  readonly 'Create user on jasmine': Partial<{
+    'Type': string;
+    'Resource': string;
+    'Parameters': Partial<{
+      'FunctionName': string;
+      'Payload': Partial<{
+        'script.$': string;
+        'databaseName.$': string;
+      }>;
+    }>;
+    'Retry': Partial<[Partial<{
+      'ErrorEquals': Partial<[string,string,string]>;
+      'IntervalSeconds': number;
+      'MaxAttempts': number;
+      'BackoffRate': number;
+    }>]>;
+    'ResultPath': string;
+    'Next': string;
+  }>;
+  readonly 'Send credentials to jasmine user': Partial<{
+    'Type': string;
+    'Parameters': Partial<{
+      'Destination': Partial<{
+        'ToAddresses.$': string;
+      }>;
+      'Message': Partial<{
+        'Body': Partial<{
+          'Text': Partial<{
+            'Data.$': string;
+          }>;
+        }>;
+        'Subject': Partial<{
+          'Data': string;
+        }>;
+      }>;
+      'Source': string;
+    }>;
+    'Resource': string;
+    'End': Partial<{
+    }>;
+  }>;
+  readonly 'Get anna secret': Partial<{
+    'Type': string;
+    'Next': string;
+    'Parameters': Partial<{
+      'SecretId': string;
+    }>;
+    'Resource': string;
+    'ResultPath': string;
+  }>;
+  readonly 'Parse anna secret value': Partial<{
+    'Type': string;
+    'Parameters': Partial<{
+      'ParsedValue.$': string;
+    }>;
+    'ResultPath': string;
+    'Next': string;
+  }>;
+  readonly 'Create new database username and password for anna': Partial<{
+    'Type': string;
+    'Resource': string;
+    'Parameters': Partial<{
+      'Payload.$': string;
+      'FunctionName': string;
+    }>;
+    'Retry': Partial<[Partial<{
+      'ErrorEquals': Partial<[string,string,string]>;
+      'IntervalSeconds': number;
+      'MaxAttempts': number;
+      'BackoffRate': number;
+    }>]>;
+    'ResultPath': string;
+    'Comment': string;
+    'Next': string;
+    'ResultSelector': Partial<{
+      'username.$': string;
+      'password.$': string;
+      'otsUrl.$': string;
+    }>;
+  }>;
+  readonly 'Create user on the anna database': Partial<{
+    'Type': string;
+    'Resource': string;
+    'Parameters': Partial<{
+      'FunctionName': string;
+      'Payload': Partial<{
+        'script.$': string;
+        'databaseName.$': string;
+      }>;
+    }>;
+    'Retry': Partial<[Partial<{
+      'ErrorEquals': Partial<[string,string,string]>;
+      'IntervalSeconds': number;
+      'MaxAttempts': number;
+      'BackoffRate': number;
+    }>]>;
+    'ResultPath': string;
+    'Next': string;
+  }>;
+  readonly 'Send credentials to user for anna': Partial<{
+    'Type': string;
+    'Parameters': Partial<{
+      'Destination': Partial<{
+        'ToAddresses.$': string;
+      }>;
+      'Message': Partial<{
+        'Body': Partial<{
+          'Text': Partial<{
+            'Data.$': string;
+          }>;
+        }>;
+        'Subject': Partial<{
+          'Data': string;
+        }>;
+      }>;
+      'Source': string;
+    }>;
+    'Resource': string;
+    'End': Partial<{
+    }>;
+  }>;
+}
+export interface TestStateMachineProps extends Omit<StateMachineProps, 'overrides' | 'definition'> {
+  readonly overrides: Partial<TestStateMachineOverrides>;
+}
+export class TestStateMachine extends StateMachine {
+  constructor(scope: Construct, id: string, props: TestStateMachineProps) {
+    super(scope, id, {
+      ...props,
+      definition: JSON.parse(fs.readFileSync(path.join(__dirname, 'test.workflow.json')).toString()),
+    });
+  }
+}"
+`;
+
+exports[`yaml handles yaml file 1`] = `
+"// ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".
+import { StateMachine, StateMachineProps } from '@matthewbonig/state-machine';
+import { Construct } from 'constructs';
+import fs from 'fs';
+import path from 'path';
+import * as yaml 'js-yaml';
+export interface TestStateMachineOverrides {
+  readonly 'Check Stock Price': Partial<{
+    'Type': string;
+    'Resource': string;
+    'Next': string;
+  }>;
+  readonly 'Generate Buy/Sell recommendation': Partial<{
+    'Type': string;
+    'Resource': string;
+    'ResultPath': string;
+    'Next': string;
+  }>;
+  readonly 'Request Human Approval': Partial<{
+    'Type': string;
+    'Resource': string;
+    'Parameters': Partial<{
+      'QueueUrl': string;
+      'MessageBody': Partial<{
+        'Input.$': string;
+        'TaskToken.$': string;
+      }>;
+    }>;
+    'ResultPath': any;
+    'Next': string;
+  }>;
+  readonly 'Buy or Sell?': Partial<{
+    'Type': string;
+    'Choices': Partial<[Partial<{
+      'Variable': string;
+      'StringEquals': string;
+      'Next': string;
+    }>,Partial<{
+      'Variable': string;
+      'StringEquals': string;
+      'Next': string;
+    }>]>;
+  }>;
+  readonly 'Buy Stock': Partial<{
+    'Type': string;
+    'Resource': string;
+    'Next': string;
+  }>;
+  readonly 'Sell Stock': Partial<{
+    'Type': string;
+    'Resource': string;
+    'Next': string;
+  }>;
+  readonly 'Report Result': Partial<{
+    'Type': string;
+    'Resource': string;
+    'Parameters': Partial<{
+      'TopicArn': string;
+      'Message': Partial<{
+        'Input.$': string;
+      }>;
+    }>;
+    'End': Partial<{
+    }>;
+  }>;
+}
+export interface TestStateMachineProps extends Omit<StateMachineProps, 'overrides' | 'definition'> {
+  readonly overrides: Partial<TestStateMachineOverrides>;
+}
+export class TestStateMachine extends StateMachine {
+  constructor(scope: Construct, id: string, props: TestStateMachineProps) {
+    super(scope, id, {
+      ...props,
+      definition: yaml.load(fs.readFileSync(path.join(__dirname, 'test.yaml.asl')).toString()),
+      aslYaml: true,
+    });
+  }
+}"
+`;
+
+exports[`yaml handles yml file 1`] = `
+"// ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".
+import { StateMachine, StateMachineProps } from '@matthewbonig/state-machine';
+import { Construct } from 'constructs';
+import fs from 'fs';
+import path from 'path';
+import * as yaml 'js-yaml';
+export interface TestStateMachineOverrides {
+  readonly 'Check Stock Price': Partial<{
+    'Type': string;
+    'Resource': string;
+    'Next': string;
+  }>;
+  readonly 'Generate Buy/Sell recommendation': Partial<{
+    'Type': string;
+    'Resource': string;
+    'ResultPath': string;
+    'Next': string;
+  }>;
+  readonly 'Request Human Approval': Partial<{
+    'Type': string;
+    'Resource': string;
+    'Parameters': Partial<{
+      'QueueUrl': string;
+      'MessageBody': Partial<{
+        'Input.$': string;
+        'TaskToken.$': string;
+      }>;
+    }>;
+    'ResultPath': any;
+    'Next': string;
+  }>;
+  readonly 'Buy or Sell?': Partial<{
+    'Type': string;
+    'Choices': Partial<[Partial<{
+      'Variable': string;
+      'StringEquals': string;
+      'Next': string;
+    }>,Partial<{
+      'Variable': string;
+      'StringEquals': string;
+      'Next': string;
+    }>]>;
+  }>;
+  readonly 'Buy Stock': Partial<{
+    'Type': string;
+    'Resource': string;
+    'Next': string;
+  }>;
+  readonly 'Sell Stock': Partial<{
+    'Type': string;
+    'Resource': string;
+    'Next': string;
+  }>;
+  readonly 'Report Result': Partial<{
+    'Type': string;
+    'Resource': string;
+    'Parameters': Partial<{
+      'TopicArn': string;
+      'Message': Partial<{
+        'Input.$': string;
+      }>;
+    }>;
+    'End': Partial<{
+    }>;
+  }>;
+}
+export interface TestStateMachineProps extends Omit<StateMachineProps, 'overrides' | 'definition'> {
+  readonly overrides: Partial<TestStateMachineOverrides>;
+}
+export class TestStateMachine extends StateMachine {
+  constructor(scope: Construct, id: string, props: TestStateMachineProps) {
+    super(scope, id, {
+      ...props,
+      definition: yaml.load(fs.readFileSync(path.join(__dirname, 'test.yml.asl')).toString()),
+      aslYaml: true,
+    });
+  }
+}"
+`;
diff --git a/test/step-functions/test.json.asl b/test/step-functions/test.json.asl
new file mode 100644
index 0000000..c11a1ac
--- /dev/null
+++ b/test/step-functions/test.json.asl
@@ -0,0 +1,300 @@
+{
+  "StartAt": "Which database?",
+  "States": {
+    "Which database?": {
+      "Type": "Choice",
+      "Choices": [
+        {
+          "Variable": "$.databaseName",
+          "StringMatches": "belle*",
+          "Next": "Get belle secret"
+        },
+        {
+          "Variable": "$.databaseName",
+          "StringMatches": "jasmine*",
+          "Next": "Get engines secret"
+        },
+        {
+          "Variable": "$.databaseName",
+          "StringMatches": "anna*",
+          "Next": "Get anna secret"
+        }
+      ]
+    },
+    "Get belle secret": {
+      "Type": "Task",
+      "Next": "Parse belle secret value",
+      "Parameters": {
+        "SecretId": "qa2/belle-sql-secrets"
+      },
+      "Resource": "arn:aws:states:::aws-sdk:secretsmanager:getSecretValue",
+      "ResultPath": "$.databaseSecret"
+    },
+    "Parse belle secret value": {
+      "Type": "Pass",
+      "Parameters": {
+        "ParsedValue.$": "States.StringToJson($.databaseSecret.SecretString)"
+      },
+      "ResultPath": "$.databaseSecret",
+      "Next": "Create new database username and password"
+    },
+    "Create new database username and password": {
+      "Type": "Task",
+      "Resource": "arn:aws:states:::lambda:invoke",
+      "Parameters": {
+        "Payload.$": "$",
+        "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa2-Database-UserDatabaseCreatorPersonalUsernameGe-dgk0cAkQs0VW"
+      },
+      "Retry": [
+        {
+          "ErrorEquals": [
+            "Lambda.ServiceException",
+            "Lambda.AWSLambdaException",
+            "Lambda.SdkClientException"
+          ],
+          "IntervalSeconds": 2,
+          "MaxAttempts": 6,
+          "BackoffRate": 2
+        }
+      ],
+      "ResultPath": "$.newCreds",
+      "Comment": "returns back {username,password}",
+      "Next": "Create user on the database",
+      "ResultSelector": {
+        "username.$": "$.Payload.username",
+        "password.$": "$.Payload.password",
+        "otsUrl.$": "$.Payload.otsUrl"
+      }
+    },
+    "Create user on the database": {
+      "Type": "Task",
+      "Resource": "arn:aws:states:::lambda:invoke",
+      "Parameters": {
+        "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa2-Database-Databaseadhocsingl2E02F587-yZxuEpPiDCUc",
+        "Payload": {
+          "script.$": "States.Format('CREATE ROLE {} LOGIN PASSWORD \\'{}\\'; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public to {}; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public to {}; GRANT {} to {};', $.newCreds.username, $.newCreds.password, $.newCreds.username, $.newCreds.username, $.databaseSecret.ParsedValue.SQL_USER,$.newCreds.username)",
+          "databaseName.$": "$.databaseSecret.ParsedValue.SQL_DATABASE"
+        }
+      },
+      "Retry": [
+        {
+          "ErrorEquals": [
+            "Lambda.ServiceException",
+            "Lambda.AWSLambdaException",
+            "Lambda.SdkClientException"
+          ],
+          "IntervalSeconds": 2,
+          "MaxAttempts": 6,
+          "BackoffRate": 2
+        }
+      ],
+      "ResultPath": "$.databaseResults",
+      "Next": "Send credentials to user"
+    },
+    "Send credentials to user": {
+      "Type": "Task",
+      "Parameters": {
+        "Destination": {
+          "ToAddresses.$": "States.Array($.email)"
+        },
+        "Message": {
+          "Body": {
+            "Text": {
+              "Data.$": "States.Format('New credentials were generated for you: \n\ndatabase: {}\nusername: {}\npassword: {} \nhost: {}', $.databaseSecret.ParsedValue.SQL_DATABASE, $.newCreds.username, $.newCreds.otsUrl, $.databaseSecret.ParsedValue.SQL_HOST)"
+            }
+          },
+          "Subject": {
+            "Data": "New database access credentials have been created for you!"
+          }
+        },
+        "Source": "devops@momnt.com"
+      },
+      "Resource": "arn:aws:states:::aws-sdk:ses:sendEmail",
+      "End": true
+    },
+    "Get engines secret": {
+      "Type": "Task",
+      "Parameters": {
+        "SecretId": "qa2/engines-secrets"
+      },
+      "Resource": "arn:aws:states:::aws-sdk:secretsmanager:getSecretValue",
+      "ResultPath": "$.databaseSecret",
+      "Next": "Parse engines secret value"
+    },
+    "Parse engines secret value": {
+      "Type": "Pass",
+      "Parameters": {
+        "ParsedValue.$": "States.StringToJson($.databaseSecret.SecretString)"
+      },
+      "ResultPath": "$.databaseSecret",
+      "Next": "Create new database username and password for engines"
+    },
+    "Create new database username and password for engines": {
+      "Type": "Task",
+      "Resource": "arn:aws:states:::lambda:invoke",
+      "Parameters": {
+        "Payload.$": "$",
+        "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa1-Database-UserDatabaseCreatorPersonalUsernameGe-dmIs8HXvJAYc"
+      },
+      "Retry": [
+        {
+          "ErrorEquals": [
+            "Lambda.ServiceException",
+            "Lambda.AWSLambdaException",
+            "Lambda.SdkClientException"
+          ],
+          "IntervalSeconds": 2,
+          "MaxAttempts": 6,
+          "BackoffRate": 2
+        }
+      ],
+      "ResultPath": "$.newCreds",
+      "Comment": "returns back {username,password}",
+      "ResultSelector": {
+        "username.$": "$.Payload.username",
+        "password.$": "$.Payload.password",
+        "otsUrl.$": "$.Payload.otsUrl"
+      },
+      "Next": "Create user on jasmine"
+    },
+    "Create user on jasmine": {
+      "Type": "Task",
+      "Resource": "arn:aws:states:::lambda:invoke",
+      "Parameters": {
+        "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa2-Database-Databaseadhocsingl2E02F587-yZxuEpPiDCUc",
+        "Payload": {
+          "script.$": "States.Format('CREATE ROLE {} LOGIN PASSWORD \\'{}\\'; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA engine_rules to {}; GRANT ALL PRIVILEGES ON SCHEMA engine_rules to {}; GRANT {} to {};', $.newCreds.username, $.newCreds.password, $.newCreds.username, $.newCreds.username, $.databaseSecret.ParsedValue.PGUSER, $.newCreds.username)",
+          "databaseName.$": "$.databaseSecret.ParsedValue.PGDATABASE"
+        }
+      },
+      "Retry": [
+        {
+          "ErrorEquals": [
+            "Lambda.ServiceException",
+            "Lambda.AWSLambdaException",
+            "Lambda.SdkClientException"
+          ],
+          "IntervalSeconds": 2,
+          "MaxAttempts": 6,
+          "BackoffRate": 2
+        }
+      ],
+      "ResultPath": "$.databaseResults",
+      "Next": "Send credentials to jasmine user"
+    },
+    "Send credentials to jasmine user": {
+      "Type": "Task",
+      "Parameters": {
+        "Destination": {
+          "ToAddresses.$": "States.Array($.email)"
+        },
+        "Message": {
+          "Body": {
+            "Text": {
+              "Data.$": "States.Format('New credentials were generated for you: \n\ndatabase: {}\nusername: {}\npassword: {} \nhost: {}', $.databaseSecret.ParsedValue.PGDATABASE, $.newCreds.username, $.newCreds.otsUrl, $.databaseSecret.ParsedValue.PGHOST)"
+            }
+          },
+          "Subject": {
+            "Data": "New database access credentials have been created for you!"
+          }
+        },
+        "Source": "devops@momnt.com"
+      },
+      "Resource": "arn:aws:states:::aws-sdk:ses:sendEmail",
+      "End": true
+    },
+
+    "Get anna secret": {
+      "Type": "Task",
+      "Next": "Parse anna secret value",
+      "Parameters": {
+        "SecretId": "qa2/anna-sql-secrets"
+      },
+      "Resource": "arn:aws:states:::aws-sdk:secretsmanager:getSecretValue",
+      "ResultPath": "$.databaseSecret"
+    },
+    "Parse anna secret value": {
+      "Type": "Pass",
+      "Parameters": {
+        "ParsedValue.$": "States.StringToJson($.databaseSecret.SecretString)"
+      },
+      "ResultPath": "$.databaseSecret",
+      "Next": "Create new database username and password for anna"
+    },
+    "Create new database username and password for anna": {
+      "Type": "Task",
+      "Resource": "arn:aws:states:::lambda:invoke",
+      "Parameters": {
+        "Payload.$": "$",
+        "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa2-Database-UserDatabaseCreatorPersonalUsernameGe-dgk0cAkQs0VW"
+      },
+      "Retry": [
+        {
+          "ErrorEquals": [
+            "Lambda.ServiceException",
+            "Lambda.AWSLambdaException",
+            "Lambda.SdkClientException"
+          ],
+          "IntervalSeconds": 2,
+          "MaxAttempts": 6,
+          "BackoffRate": 2
+        }
+      ],
+      "ResultPath": "$.newCreds",
+      "Comment": "returns back {username,password}",
+      "Next": "Create user on the anna database",
+      "ResultSelector": {
+        "username.$": "$.Payload.username",
+        "password.$": "$.Payload.password",
+        "otsUrl.$": "$.Payload.otsUrl"
+      }
+    },
+    "Create user on the anna database": {
+      "Type": "Task",
+      "Resource": "arn:aws:states:::lambda:invoke",
+      "Parameters": {
+        "FunctionName": "arn:aws:lambda:us-east-1:754664436462:function:qa2-Database-Databaseadhocsingl2E02F587-yZxuEpPiDCUc",
+        "Payload": {
+          "script.$": "States.Format('CREATE ROLE {} LOGIN PASSWORD \\'{}\\'; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public to {}; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public to {}; GRANT {} to {};', $.newCreds.username, $.newCreds.password, $.newCreds.username, $.newCreds.username, $.databaseSecret.ParsedValue.PGUSER, $.newCreds.username)",
+          "databaseName.$": "$.databaseSecret.ParsedValue.PGDATABASE"
+        }
+      },
+      "Retry": [
+        {
+          "ErrorEquals": [
+            "Lambda.ServiceException",
+            "Lambda.AWSLambdaException",
+            "Lambda.SdkClientException"
+          ],
+          "IntervalSeconds": 2,
+          "MaxAttempts": 6,
+          "BackoffRate": 2
+        }
+      ],
+      "ResultPath": "$.databaseResults",
+      "Next": "Send credentials to user for anna"
+    },
+    "Send credentials to user for anna": {
+      "Type": "Task",
+      "Parameters": {
+        "Destination": {
+          "ToAddresses.$": "States.Array($.email)"
+        },
+        "Message": {
+          "Body": {
+            "Text": {
+              "Data.$": "States.Format('New credentials were generated for you: \n\ndatabase: {}\nusername: {}\npassword: {} \nhost: {}', $.databaseSecret.ParsedValue.PGDATABASE, $.newCreds.username, $.newCreds.otsUrl, $.databaseSecret.ParsedValue.PGHOST)"
+            }
+          },
+          "Subject": {
+            "Data": "New database access credentials have been created for you!"
+          }
+        },
+        "Source": "devops@momnt.com"
+      },
+      "Resource": "arn:aws:states:::aws-sdk:ses:sendEmail",
+      "End": true
+    }
+  },
+  "Comment": "must provide:\nusername  (will be prefix)\ndatabaseName (jasmine or belle, don't have to have full name, but should be able to?)\nemail (the email address to send results to)"
+}
diff --git a/test/step-functions/test.yaml.asl b/test/step-functions/test.yaml.asl
new file mode 100644
index 0000000..e9c2665
--- /dev/null
+++ b/test/step-functions/test.yaml.asl
@@ -0,0 +1,52 @@
+StartAt: Check Stock Price
+States:
+  Check Stock Price:
+    Type: Task
+    Resource: >-
+      arn:aws:lambda:us-east-1:843153345658:function:StepFunctionsSample-HelloLam-CheckStockPriceLambda-9Ql8L0vFGNyX
+    Next: Generate Buy/Sell recommendation
+  Generate Buy/Sell recommendation:
+    Type: Task
+    Resource: >-
+      arn:aws:lambda:us-east-1:843153345658:function:StepFunctionsSample-Hello-GenerateBuySellRecommend-RV43mVlWrlRp
+    ResultPath: $.recommended_type
+    Next: Request Human Approval
+  Request Human Approval:
+    Type: Task
+    Resource: arn:aws:states:::sqs:sendMessage.waitForTaskToken
+    Parameters:
+      QueueUrl: >-
+        https://sqs.us-east-1.amazonaws.com/843153345658/StepFunctionsSample-HelloLambda1cfd9e64-a1b-RequestHumanApprovalSqs-1ctyDMjtCfTL
+      MessageBody:
+        Input.$: $
+        TaskToken.$: $$.Task.Token
+    ResultPath: null
+    Next: Buy or Sell?
+  Buy or Sell?:
+    Type: Choice
+    Choices:
+      - Variable: $.recommended_type
+        StringEquals: buy
+        Next: Buy Stock
+      - Variable: $.recommended_type
+        StringEquals: sell
+        Next: Sell Stock
+  Buy Stock:
+    Type: Task
+    Resource: >-
+      arn:aws:lambda:us-east-1:843153345658:function:StepFunctionsSample-HelloLambda1cfd-BuyStockLambda-ggejR4EeAftC
+    Next: Report Result
+  Sell Stock:
+    Type: Task
+    Resource: >-
+      arn:aws:lambda:us-east-1:843153345658:function:StepFunctionsSample-HelloLambda1cf-SellStockLambda-HY85TtYKY9xk
+    Next: Report Result
+  Report Result:
+    Type: Task
+    Resource: arn:aws:states:::sns:publish
+    Parameters:
+      TopicArn: >-
+        arn:aws:sns:us-east-1:843153345658:StepFunctionsSample-HelloLambda1cfd9e64-a1bb-45f1-b2b0-7e258d8ba55f-ReportResultSnsTopic-GABSbYyOACQR
+      Message:
+        Input.$: $
+    End: true