Repository: getmoto/moto

Issue:
`describe_receipt_rule` for `ses` not implemented
Hi, our team was writing tests for a lambda that uses SES and ran into the below error:

```
NotImplementedError: The describe_receipt_rule action has not been implemented
```

I've confirmed that this action is not implemented [here](https://github.com/spulec/moto/blob/master/IMPLEMENTATION_COVERAGE.md#ses).

I noticed that this repo accepts pull requests for [missing features](https://github.com/spulec/moto/blob/master/CONTRIBUTING.md#missing-features). Although, 
I don't have much experience with this library and am relatively new to working with AWS services.
I'd like to make a contribution to add this action so that we can complete our test and also because it's Hacktoberfest. 

Could I get more context into how methods are mocked in moto? I've followed the instructions on the contributing file, but I don't think I have enough experience with boto3 and botocore to fill in the blanks here.

Thanks in advance for any guidance on helping me with my first contribution to this repo!

Here is the API doc for [`describe_receipt_rule`](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ses.html#SES.Client.describe_receipt_rule).




Discussion:

Resolving patch:
diff --git a/moto/ses/exceptions.py b/moto/ses/exceptions.py
--- a/moto/ses/exceptions.py
+++ b/moto/ses/exceptions.py
@@ -88,6 +88,13 @@ def __init__(self, message):
         super(RuleSetDoesNotExist, self).__init__("RuleSetDoesNotExist", message)
 
 
+class RuleDoesNotExist(RESTError):
+    code = 400
+
+    def __init__(self, message):
+        super(RuleDoesNotExist, self).__init__("RuleDoesNotExist", message)
+
+
 class MissingRenderingAttributeException(RESTError):
     code = 400
 
diff --git a/moto/ses/models.py b/moto/ses/models.py
--- a/moto/ses/models.py
+++ b/moto/ses/models.py
@@ -18,6 +18,7 @@
     InvalidParameterValue,
     InvalidRenderingParameterException,
     TemplateDoesNotExist,
+    RuleDoesNotExist,
     RuleSetNameAlreadyExists,
     RuleSetDoesNotExist,
     RuleAlreadyExists,
@@ -413,7 +414,7 @@ def render_template(self, render_data):
 
     def create_receipt_rule_set(self, rule_set_name):
         if self.receipt_rule_set.get(rule_set_name) is not None:
-            raise RuleSetNameAlreadyExists("Duplicate receipt rule set Name.")
+            raise RuleSetNameAlreadyExists("Duplicate Receipt Rule Set Name.")
         self.receipt_rule_set[rule_set_name] = []
 
     def create_receipt_rule(self, rule_set_name, rule):
@@ -425,5 +426,17 @@ def create_receipt_rule(self, rule_set_name, rule):
         rule_set.append(rule)
         self.receipt_rule_set[rule_set_name] = rule_set
 
+    def describe_receipt_rule(self, rule_set_name, rule_name):
+        rule_set = self.receipt_rule_set.get(rule_set_name)
+
+        if rule_set is None:
+            raise RuleSetDoesNotExist("Invalid Rule Set Name.")
+
+        for receipt_rule in rule_set:
+            if receipt_rule["name"] == rule_name:
+                return receipt_rule
+        else:
+            raise RuleDoesNotExist("Invalid Rule Name.")
+
 
 ses_backend = SESBackend()
diff --git a/moto/ses/responses.py b/moto/ses/responses.py
--- a/moto/ses/responses.py
+++ b/moto/ses/responses.py
@@ -234,11 +234,25 @@ def create_receipt_rule_set(self):
 
     def create_receipt_rule(self):
         rule_set_name = self._get_param("RuleSetName")
-        rule = self._get_dict_param("Rule")
+        rule = self._get_dict_param("Rule.")
         ses_backend.create_receipt_rule(rule_set_name, rule)
         template = self.response_template(CREATE_RECEIPT_RULE)
         return template.render()
 
+    def describe_receipt_rule(self):
+        rule_set_name = self._get_param("RuleSetName")
+        rule_name = self._get_param("RuleName")
+
+        receipt_rule = ses_backend.describe_receipt_rule(rule_set_name, rule_name)
+
+        rule = {}
+
+        for k, v in receipt_rule.items():
+            self._parse_param(k, v, rule)
+
+        template = self.response_template(DESCRIBE_RECEIPT_RULE)
+        return template.render(rule=rule)
+
 
 VERIFY_EMAIL_IDENTITY = """<VerifyEmailIdentityResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/">
   <VerifyEmailIdentityResult/>
@@ -492,3 +506,46 @@ def create_receipt_rule(self):
     <RequestId>15e0ef1a-9bf2-11e1-9279-01ab88cf109a</RequestId>
   </ResponseMetadata>
 </CreateReceiptRuleResponse>"""
+
+DESCRIBE_RECEIPT_RULE = """<DescribeReceiptRuleResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/">
+  <DescribeReceiptRuleResult>
+    <Rule>
+      <Recipients>
+        {% for recipient in rule["recipients"] %}
+        <member>{{recipient}}</member>
+        {% endfor %}
+      </Recipients>
+      <Name>{{rule["name"]}}</Name>
+      <Actions>
+        {% for action in rule["actions"] %}
+        <member>
+          {% if action["_s3_action"] %}
+          <S3Action>
+            <BucketName>{{action["_s3_action"]["_bucket_name"]}}</BucketName>
+            <KmsKeyArn>{{action["_s3_action"]["_kms_key_arn"]}}</KmsKeyArn>
+            <ObjectKeyPrefix>{{action["_s3_action"]["_object_key_prefix"]}}</ObjectKeyPrefix>
+            <TopicArn>{{action["_s3_action"]["_topic_arn"]}}</TopicArn>
+          </S3Action>
+          {% endif %}
+          {% if action["_bounce_action"] %}
+          <BounceAction>
+            <TopicArn>{{action["_bounce_action"]["_topic_arn"]}}</TopicArn>
+            <SmtpReplyCode>{{action["_bounce_action"]["_smtp_reply_code"]}}</SmtpReplyCode>
+            <StatusCode>{{action["_bounce_action"]["_status_code"]}}</StatusCode>
+            <Message>{{action["_bounce_action"]["_message"]}}</Message>
+            <Sender>{{action["_bounce_action"]["_sender"]}}</Sender>
+          </BounceAction>
+          {% endif %}
+        </member>
+        {% endfor %}
+      </Actions>
+      <TlsPolicy>{{rule["tls_policy"]}}</TlsPolicy>
+      <ScanEnabled>{{rule["scan_enabled"]}}</ScanEnabled>
+      <Enabled>{{rule["enabled"]}}</Enabled>
+    </Rule>
+  </DescribeReceiptRuleResult>
+  <ResponseMetadata>
+    <RequestId>15e0ef1a-9bf2-11e1-9279-01ab88cf109a</RequestId>
+  </ResponseMetadata>
+</DescribeReceiptRuleResponse>
+"""


Test patch:
diff --git a/tests/test_ses/test_ses_boto3.py b/tests/test_ses/test_ses_boto3.py
--- a/tests/test_ses/test_ses_boto3.py
+++ b/tests/test_ses/test_ses_boto3.py
@@ -481,6 +481,103 @@ def test_create_receipt_rule():
     ex.value.response["Error"]["Code"].should.equal("RuleSetDoesNotExist")
 
 
+@mock_ses
+def test_describe_receipt_rule():
+    conn = boto3.client("ses", region_name="us-east-1")
+    rule_set_name = "testRuleSet"
+    conn.create_receipt_rule_set(RuleSetName=rule_set_name)
+
+    rule_name = "testRule"
+    conn.create_receipt_rule(
+        RuleSetName=rule_set_name,
+        Rule={
+            "Name": rule_name,
+            "Enabled": False,
+            "TlsPolicy": "Optional",
+            "Recipients": ["test@email.com", "test2@email.com"],
+            "Actions": [
+                {
+                    "S3Action": {
+                        "TopicArn": "string",
+                        "BucketName": "testBucketName",
+                        "ObjectKeyPrefix": "testObjectKeyPrefix",
+                        "KmsKeyArn": "string",
+                    },
+                    "BounceAction": {
+                        "TopicArn": "string",
+                        "SmtpReplyCode": "string",
+                        "StatusCode": "string",
+                        "Message": "string",
+                        "Sender": "string",
+                    },
+                }
+            ],
+            "ScanEnabled": False,
+        },
+    )
+
+    receipt_rule_response = conn.describe_receipt_rule(
+        RuleSetName=rule_set_name, RuleName=rule_name
+    )
+
+    receipt_rule_response["Rule"]["Name"].should.equal(rule_name)
+
+    receipt_rule_response["Rule"]["Enabled"].should.equal(False)
+
+    receipt_rule_response["Rule"]["TlsPolicy"].should.equal("Optional")
+
+    len(receipt_rule_response["Rule"]["Recipients"]).should.equal(2)
+    receipt_rule_response["Rule"]["Recipients"][0].should.equal("test@email.com")
+
+    len(receipt_rule_response["Rule"]["Actions"]).should.equal(1)
+    receipt_rule_response["Rule"]["Actions"][0].should.have.key("S3Action")
+
+    receipt_rule_response["Rule"]["Actions"][0]["S3Action"].should.have.key(
+        "TopicArn"
+    ).being.equal("string")
+    receipt_rule_response["Rule"]["Actions"][0]["S3Action"].should.have.key(
+        "BucketName"
+    ).being.equal("testBucketName")
+    receipt_rule_response["Rule"]["Actions"][0]["S3Action"].should.have.key(
+        "ObjectKeyPrefix"
+    ).being.equal("testObjectKeyPrefix")
+    receipt_rule_response["Rule"]["Actions"][0]["S3Action"].should.have.key(
+        "KmsKeyArn"
+    ).being.equal("string")
+
+    receipt_rule_response["Rule"]["Actions"][0].should.have.key("BounceAction")
+
+    receipt_rule_response["Rule"]["Actions"][0]["BounceAction"].should.have.key(
+        "TopicArn"
+    ).being.equal("string")
+    receipt_rule_response["Rule"]["Actions"][0]["BounceAction"].should.have.key(
+        "SmtpReplyCode"
+    ).being.equal("string")
+    receipt_rule_response["Rule"]["Actions"][0]["BounceAction"].should.have.key(
+        "StatusCode"
+    ).being.equal("string")
+    receipt_rule_response["Rule"]["Actions"][0]["BounceAction"].should.have.key(
+        "Message"
+    ).being.equal("string")
+    receipt_rule_response["Rule"]["Actions"][0]["BounceAction"].should.have.key(
+        "Sender"
+    ).being.equal("string")
+
+    receipt_rule_response["Rule"]["ScanEnabled"].should.equal(False)
+
+    with pytest.raises(ClientError) as error:
+        conn.describe_receipt_rule(RuleSetName="invalidRuleSetName", RuleName=rule_name)
+
+    error.value.response["Error"]["Code"].should.equal("RuleSetDoesNotExist")
+
+    with pytest.raises(ClientError) as error:
+        conn.describe_receipt_rule(
+            RuleSetName=rule_set_name, RuleName="invalidRuleName"
+        )
+
+    error.value.response["Error"]["Code"].should.equal("RuleDoesNotExist")
+
+
 @mock_ses
 def test_create_ses_template():
     conn = boto3.client("ses", region_name="us-east-1")