Repository: aws-cloudformation/cfn-lint

Issue:
cfn-lint breaks with any errors in template and errors on `ServerlessRestApi` implicit reference 
### CloudFormation Lint Version

0.87.7

### What operating system are you using?

macOS Sonoma 14.5

### Describe the bug

It's twofold.  What I am ultimately trying to do is allow the use of ServerlessRestApi in a SAM template. I am using this in VSCode and linter yields "Unable to find referenced value, 'ServerlessRestApi" for something like this:
```
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
# Find out more about other implicit resources you can reference within SAM
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
HelloWorldApi:
  Description: API Gateway endpoint URL for Prod stage for Hello World function
  Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/'
```
In an attempt to fix this I have tried the following:
- install sam-cli using Brew and using the AWS installer
- installing cfn-lint using brew and using pipx
- installing aws-sam-translator using pipx

I am using the VSCode plugin for cfn-lint. 

What I noticed was that if the template is fine then cfn-lint just exits quietly. If I run from command line I get:
```
❯ cfn-lint /Users/Me/Code/sam-app/template.yaml
Traceback (most recent call last):
  File "/opt/homebrew/bin/cfn-lint", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/opt/homebrew/Cellar/cfn-lint/0.87.7/libexec/lib/python3.12/site-packages/cfnlint/__main__.py", line 40, in main
    matches = list(cfnlint.core.get_matches(filenames, args))
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/cfn-lint/0.87.7/libexec/lib/python3.12/site-packages/cfnlint/core.py", line 179, in get_matches
    matches = run_cli(
              ^^^^^^^^
  File "/opt/homebrew/Cellar/cfn-lint/0.87.7/libexec/lib/python3.12/site-packages/cfnlint/core.py", line 84, in run_cli
    return run_checks(filename, template, rules, regions, mandatory_rules)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/cfn-lint/0.87.7/libexec/lib/python3.12/site-packages/cfnlint/core.py", line 354, in run_checks
    errors.extend(runner.transform())
                  ^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/cfn-lint/0.87.7/libexec/lib/python3.12/site-packages/cfnlint/runner.py", line 35, in transform
    matches = self.cfn.transform()
              ^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/cfn-lint/0.87.7/libexec/lib/python3.12/site-packages/cfnlint/template/template.py", line 170, in transform
    return transform.transform(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/cfn-lint/0.87.7/libexec/lib/python3.12/site-packages/cfnlint/template/transforms/transform.py", line 54, in transform
    cfn.graph = Graph(cfn)
                ^^^^^^^^^^
  File "/opt/homebrew/Cellar/cfn-lint/0.87.7/libexec/lib/python3.12/site-packages/cfnlint/graph.py", line 90, in __init__
    self._add_outputs(cfn)
  File "/opt/homebrew/Cellar/cfn-lint/0.87.7/libexec/lib/python3.12/site-packages/cfnlint/graph.py", line 124, in _add_outputs
    for output_id in cfn.template.get("Outputs", {}).keys():
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'keys'
```

No errors are printed regarding the issue such as "Unable to find referenced value, 'ServerlessRestApi'".

If I run the VSCode linter it will have errors next to these lines AND the top entry "AWSTemplateFormatVersion: '2010-09-09'" shows a warning on hover which prints the same traceback errors as above. So it seems to be working? but always dumps errors when so?

I've attempted to ignore the issue by passing the flag "--ignore-checks W" which does nothing for this error.

Additionally 'cfn-lint u' hangs forever and appears to do nothing
__

### Expected behavior

I'd like to lint the default SAM HelloWorld template without warnings.

### Reproduction template

```yaml AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: My integration

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Runtime: nodejs20.x
    Timeout: 180
    Handler: app.lambdaHandler
    Architectures:
      - x86_64

Resources:
  TriggerFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: trigger/

    Metadata: # Manage esbuild properties
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Target: es2020
        EntryPoints:
          - app.ts

Outputs:
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
# Find out more about other implicit resources you can reference within SAM
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
HelloWorldApi:
  Description: API Gateway endpoint URL for Prod stage for Hello World function
  Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/'
```


Discussion:
I just disabled the extension and I see there are still errors regarding `ServerlessRestApi` which makes me think another plugin is showing error - I will close this while I fix this to see if that is the cause of the error dump.
I'm able to reproduce on the latest version of the code.  The extension is calling cfn-lint and probably producing this error.
The transformed template I'm getting is.  I'm not sure why I'm getting nulls for Outputs from the sam translator.
```json
{
 "Description": "My integration",
 "HelloWorldApi": {
  "Description": "API Gateway endpoint URL for Prod stage for Hello World function",
  "Value": {
   "Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
  }
 },
 "Outputs": null,
 "Resources": {
  "TriggerFunction": {
   "Metadata": {
    "BuildMethod": "esbuild",
    "BuildProperties": {
     "EntryPoints": [
      "app.ts"
     ],
     "Minify": true,
     "Target": "es2020"
    }
   },
   "Properties": {
    "Architectures": [
     "x86_64"
    ],
    "Code": {
     "S3Bucket": "bucket",
     "S3Key": "value"
    },
    "Handler": "app.lambdaHandler",
    "Role": {
     "Fn::GetAtt": [
      "TriggerFunctionRole",
      "Arn"
     ]
    },
    "Runtime": "nodejs20.x",
    "Tags": [
     {
      "Key": "lambda:createdBy",
      "Value": "SAM"
     }
    ],
    "Timeout": 180
   },
   "Type": "AWS::Lambda::Function"
  },
  "TriggerFunctionRole": {
   "Metadata": {
    "BuildMethod": "esbuild",
    "BuildProperties": {
     "EntryPoints": [
      "app.ts"
     ],
     "Minify": true,
     "Target": "es2020"
    }
   },
   "Properties": {
    "AssumeRolePolicyDocument": {
     "Statement": [
      {
       "Action": [
        "sts:AssumeRole"
       ],
       "Effect": "Allow",
       "Principal": {
        "Service": [
         "lambda.amazonaws.com"
        ]
       }
      }
     ],
     "Version": "2012-10-17"
    },
    "ManagedPolicyArns": [
     "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
    ],
    "Tags": [
     {
      "Key": "lambda:createdBy",
      "Value": "SAM"
     }
    ]
   },
   "Type": "AWS::IAM::Role"
  }
 }
}
```
@cyberwombat based on your indentation I can see why Outputs are null.  I'll update the graphing functionality to validate the Outputs before assuming its correct.

Resolving patch:
diff --git a/src/cfnlint/graph.py b/src/cfnlint/graph.py
--- a/src/cfnlint/graph.py
+++ b/src/cfnlint/graph.py
@@ -121,9 +121,13 @@ def _add_parameters(self, cfn: Any) -> None:
 
     def _add_outputs(self, cfn: Any) -> None:
         # add all outputs in the template as nodes
-        for output_id in cfn.template.get("Outputs", {}).keys():
-            graph_label = str.format(f'"{output_id}"')
-            self._add_node(output_id, label=graph_label, settings=self.settings.output)
+        outputs = cfn.template.get("Outputs", {})
+        if isinstance(outputs, dict):
+            for output_id in outputs.keys():
+                graph_label = str.format(f'"{output_id}"')
+                self._add_node(
+                    output_id, label=graph_label, settings=self.settings.output
+                )
 
     def _add_resources(self, cfn: Any):
         # add all resources in the template as nodes
diff --git a/src/cfnlint/rules/common.py b/src/cfnlint/rules/common.py
--- a/src/cfnlint/rules/common.py
+++ b/src/cfnlint/rules/common.py
@@ -49,7 +49,10 @@ def approaching_number_limit(cfn, section):
 
 def name_limit(cfn, section):
     matches = []
-    for name in cfn.template.get(section, {}):
+    items = cfn.template.get(section)
+    if not isinstance(items, dict):
+        return matches
+    for name in items:
         if len(name) > LIMITS[section]["name"]:
             message = (
                 "The length of " + section[:-1] + " name ({0}) exceeds the limit ({1})"
@@ -65,8 +68,10 @@ def name_limit(cfn, section):
 def number_limit(cfn, section):
     matches = []
     number = (
-        cfn.get_resources() if section == "Resources" else cfn.template.get(section, {})
+        cfn.get_resources() if section == "Resources" else cfn.template.get(section)
     )
+    if not isinstance(number, dict):
+        return matches
     if len(number) > LIMITS[section]["number"]:
         message = "The number of " + section + " ({0}) exceeds the limit ({1})"
         matches.append(
@@ -77,7 +82,10 @@ def number_limit(cfn, section):
 
 def alphanumeric_name(cfn, section):
     matches = []
-    for name, _ in cfn.template.get(section, {}).items():
+    items = cfn.template.get(section)
+    if not isinstance(items, dict):
+        return matches
+    for name, _ in items.items():
         if not re.match(REGEX_ALPHANUMERIC, name):
             message = (
                 section[:-1] + " {0} has invalid name.  Name has to be alphanumeric."


Test patch: