Repository: aws-cloudformation/cfn-lint

Issue:
E1017, E1019 missing key reported when value is falsy
### CloudFormation Lint Version

1.3.5

### What operating system are you using?

Mac

### Describe the bug

Thanks for fixing #3385

Now `cfn-lint` incorrectly complains about missing keys if the resolved value is falsy (0 or "")

```
E1019 'Idx' is not one of ['Idx'] for mapping 'Map' and key 'Key' when 'Ref' is resolved
map.yaml:10:7

E1019 'Idx' is not one of ['Idx'] for mapping 'Map' and key 'Key'
map.yaml:12:45
```

### Expected behavior

no errors or warnings

### Reproduction template

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Mappings:
  Map:
    Key:
      Idx: 0
Resources:
  Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub
        - "bucket-${number}"
        - number: !FindInMap ["Map", "Key", "Idx"]
```
False Postive for E1011 for Missing Mapping Key when a DefaultValue of AWS::NoValue
### CloudFormation Lint Version

cfn-lint 1.3.5

### What operating system are you using?

Arch

### Describe the bug

Extension to #3396 where E1011 was raised due to a Type Error. 

This issue is that cfn-lint reports an E1011 because Key `RoleName` Doesn't exists Mapping, despite the presence of a DefaultValue.


### Expected behavior

When using LanguageExtensions and a DefaultValue in a FindInMap the

- DefaultValue is Resolved and validated to be of a valid type
- As the Key has a Default Value, its considered as an Optional key in the Map


### Reproduction template

```yaml
Mappings:
  Roles:
    A:
     Description: "Test"

Transform: AWS::LanguageExtensions

Resources:
  Role:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !FindInMap
        - Roles
        - A
        - RoleName
        - DefaultValue: !Ref AWS::NoValue
      Description: !FindInMap
        - Roles
        - A
        - Description
        - DefaultValue: !Ref AWS::NoValue
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Principal:
              AWS: '*'
            Effect: Allow
            Action: sts:AssumeRole
```

```shell
E1011 'RoleName' is not one of ['Description'] for mapping 'Roles' and key 'A'
E1011.cfn.yaml:15:11
```


Discussion:
Another variant of the same problem reports as `E1017` (this is closer to what I distilled to the original reproducer but I didn't notice the error number changed):

```
E1017 'Idx' is not one of ['Idx'] for mapping 'Map' and key 'Key'
mapv.yaml:25:13
```

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  CidrBlock:
    Type: String
    Default: "192.168.0.0/24"
  Ipv6CidrList:
    Type: CommaDelimitedList
    Default: ""
Mappings:
  Map:
    Key:
      Idx: 0
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref CidrBlock
  Subnet:
    Type: AWS::EC2::Subnet
    Properties:
      Ipv6CidrBlock: !Select
        - !FindInMap
          - Map
          - Key
          - Idx
        - !Ref Ipv6CidrList
      VpcId: !Ref 'VPC'
```
Another `FindInMap` problem (do you need a separate issue for this?)

`cfn-lint` 1.3.0-1.3.5 do not report any problem with this template that is completely missing the map `RegionMap`

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !FindInMap
        - RegionMap
        - !Ref 'AWS::Region'
        - Key
```






Patch:
diff --git a/src/cfnlint/jsonschema/_resolvers_cfn.py b/src/cfnlint/jsonschema/_resolvers_cfn.py
index b2a86f3311..623cddca05 100644
--- a/src/cfnlint/jsonschema/_resolvers_cfn.py
+++ b/src/cfnlint/jsonschema/_resolvers_cfn.py
@@ -39,7 +39,7 @@ def find_in_map(validator: Validator, instance: Any) -> ResolutionResult:
     if len(instance) not in [3, 4]:
         return
 
-    default_value = None
+    default_value_found = None
     if len(instance) == 4:
         options = instance[3]
         if validator.is_type(options, "object"):
@@ -52,7 +52,16 @@ def find_in_map(validator: Validator, instance: Any) -> ResolutionResult:
                             )
                         ),
                     ), None
-                    default_value = value
+                default_value_found = True
+
+    if not default_value_found and not validator.context.mappings:
+        yield None, validator, ValidationError(
+            (
+                f"{instance[0]!r} is not one of "
+                f"{list(validator.context.mappings.keys())!r}"
+            ),
+            path=deque([0]),
+        )
 
     if (
         validator.is_type(instance[0], "string")
@@ -63,8 +72,8 @@ def find_in_map(validator: Validator, instance: Any) -> ResolutionResult:
         and validator.is_type(instance[2], "string")
     ):
         map = validator.context.mappings.get(instance[0])
-        if not map:
-            if not default_value:
+        if map is None:
+            if not default_value_found:
                 yield None, validator, ValidationError(
                     (
                         f"{instance[0]!r} is not one of "
@@ -75,8 +84,8 @@ def find_in_map(validator: Validator, instance: Any) -> ResolutionResult:
             return
 
         top_key = map.keys.get(instance[1])
-        if not top_key:
-            if not default_value:
+        if top_key is None:
+            if not default_value_found:
                 yield None, validator, ValidationError(
                     (
                         f"{instance[1]!r} is not one of "
@@ -88,9 +97,9 @@ def find_in_map(validator: Validator, instance: Any) -> ResolutionResult:
             return
 
         value = top_key.keys.get(instance[2])
-        if not value:
-            if not default_value:
-                yield default_value, validator, ValidationError(
+        if value is None:
+            if not default_value_found:
+                yield value, validator, ValidationError(
                     (
                         f"{instance[2]!r} is not one of "
                         f"{list(top_key.keys.keys())!r} for mapping "


Test patch:
diff --git a/test/unit/module/jsonschema/test_resolvers_cfn.py b/test/unit/module/jsonschema/test_resolvers_cfn.py
index b9f097bd7b..93fa0409fb 100644
--- a/test/unit/module/jsonschema/test_resolvers_cfn.py
+++ b/test/unit/module/jsonschema/test_resolvers_cfn.py
@@ -182,7 +182,10 @@ def test_resolvers_ref(name, instance, response):
     ],
 )
 def test_invalid_functions(name, instance, response):
-    _resolve(name, instance, response)
+    context = Context()
+    context.mappings["foo"] = Map({"first": {"second": "bar"}})
+
+    _resolve(name, instance, response, context=context)
 
 
 @pytest.mark.parametrize(
@@ -221,6 +224,14 @@ def test_invalid_functions(name, instance, response):
             {"Fn::FindInMap": ["foo", "bar", "value", {"DefaultValue": "default"}]},
             [("default", deque([4, "DefaultValue"]), None)],
         ),
+        (
+            "Valid FindInMap with a default value",
+            {"Fn::FindInMap": ["foo", "first", "second", {"DefaultValue": "default"}]},
+            [
+                ("default", deque([4, "DefaultValue"]), None),
+                ("bar", deque([2]), None),
+            ],
+        ),
         (
             "Valid FindInMap with a bad mapping",
             {"Fn::FindInMap": ["bar", "first", "second"]},
@@ -240,6 +251,18 @@ def test_invalid_functions(name, instance, response):
             {"Fn::FindInMap": ["bar", "first", "second", {"DefaultValue": "default"}]},
             [("default", deque([4, "DefaultValue"]), None)],
         ),
+        (
+            "Valid FindInMap with a bad mapping and aws no value",
+            {
+                "Fn::FindInMap": [
+                    "bar",
+                    "first",
+                    "second",
+                    {"DefaultValue": {"Ref": "AWS::NoValue"}},
+                ]
+            },
+            [],
+        ),
         (
             "Valid FindInMap with a bad top key",
             {"Fn::FindInMap": ["foo", "second", "first"]},
@@ -298,3 +321,31 @@ def test_valid_functions(name, instance, response):
     context.mappings["foo"] = Map({"first": {"second": "bar"}})
 
     _resolve(name, instance, response, context=context)
+
+
+@pytest.mark.parametrize(
+    "name,instance,response",
+    [
+        (
+            "Invalid FindInMap with no mappings",
+            {"Fn::FindInMap": [{"Ref": "MyParameter"}, "B", "C"]},
+            [
+                (
+                    None,
+                    deque([]),
+                    ValidationError(
+                        ("{'Ref': 'MyParameter'} is not one of []"),
+                        path=deque(["Fn::FindInMap", 0]),
+                    ),
+                )
+            ],
+        ),
+        (
+            "Invalid FindInMap with no mappings and default value",
+            {"Fn::FindInMap": ["A", "B", "C", {"DefaultValue": "default"}]},
+            [("default", deque([4, "DefaultValue"]), None)],
+        ),
+    ],
+)
+def test_no_mapping(name, instance, response):
+    _resolve(name, instance, response)