Parcourir la source

Add encodeIfPresent Support to URLEncodedFormEncoder (#3779)

### Issue Link :link:
Fixes #3778

### Goals :soccer:
The encoding of synthesized `Encodable` values is different than
`[String: String?]` so we were missing coverage for `encodeIfPresent`,
causing `Optional` values to be dropped. This PR adds the missing
support.

### Implementation Details :construction:
Added all of the `encodeIfPresent` overloads to
`_URLEncodedFormEncoder.KeyedContainer` so that the protocol uses the
custom implementation.

### Testing Details :mag:
Tests added for `Encodable` values with optional properties.
Jon Shier il y a 2 ans
Parent
commit
1df2a75e74
2 fichiers modifiés avec 117 ajouts et 0 suppressions
  1. 68 0
      Source/URLEncodedFormEncoder.swift
  2. 49 0
      Tests/ParameterEncoderTests.swift

+ 68 - 0
Source/URLEncodedFormEncoder.swift

@@ -695,6 +695,74 @@ extension _URLEncodedFormEncoder.KeyedContainer: KeyedEncodingContainerProtocol
         try encode(nilValue, forKey: key)
     }
 
+    func encodeIfPresent(_ value: Bool?, forKey key: Key) throws {
+        try _encodeIfPresent(value, forKey: key)
+    }
+
+    func encodeIfPresent(_ value: String?, forKey key: Key) throws {
+        try _encodeIfPresent(value, forKey: key)
+    }
+
+    func encodeIfPresent(_ value: Double?, forKey key: Key) throws {
+        try _encodeIfPresent(value, forKey: key)
+    }
+
+    func encodeIfPresent(_ value: Float?, forKey key: Key) throws {
+        try _encodeIfPresent(value, forKey: key)
+    }
+
+    func encodeIfPresent(_ value: Int?, forKey key: Key) throws {
+        try _encodeIfPresent(value, forKey: key)
+    }
+
+    func encodeIfPresent(_ value: Int8?, forKey key: Key) throws {
+        try _encodeIfPresent(value, forKey: key)
+    }
+
+    func encodeIfPresent(_ value: Int16?, forKey key: Key) throws {
+        try _encodeIfPresent(value, forKey: key)
+    }
+
+    func encodeIfPresent(_ value: Int32?, forKey key: Key) throws {
+        try _encodeIfPresent(value, forKey: key)
+    }
+
+    func encodeIfPresent(_ value: Int64?, forKey key: Key) throws {
+        try _encodeIfPresent(value, forKey: key)
+    }
+
+    func encodeIfPresent(_ value: UInt?, forKey key: Key) throws {
+        try _encodeIfPresent(value, forKey: key)
+    }
+
+    func encodeIfPresent(_ value: UInt8?, forKey key: Key) throws {
+        try _encodeIfPresent(value, forKey: key)
+    }
+
+    func encodeIfPresent(_ value: UInt16?, forKey key: Key) throws {
+        try _encodeIfPresent(value, forKey: key)
+    }
+
+    func encodeIfPresent(_ value: UInt32?, forKey key: Key) throws {
+        try _encodeIfPresent(value, forKey: key)
+    }
+
+    func encodeIfPresent(_ value: UInt64?, forKey key: Key) throws {
+        try _encodeIfPresent(value, forKey: key)
+    }
+
+    func encodeIfPresent<Value>(_ value: Value?, forKey key: Key) throws where Value: Encodable {
+        try _encodeIfPresent(value, forKey: key)
+    }
+
+    func _encodeIfPresent<Value>(_ value: Value?, forKey key: Key) throws where Value: Encodable {
+        if let value = value {
+            try encode(value, forKey: key)
+        } else {
+            try encodeNil(forKey: key)
+        }
+    }
+
     func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable {
         var container = nestedSingleValueEncoder(for: key)
         try container.encode(value)

+ 49 - 0
Tests/ParameterEncoderTests.swift

@@ -852,6 +852,17 @@ final class URLEncodedFormEncoderTests: BaseTestCase {
         XCTAssertEqual(result.success, "")
     }
 
+    func testThatNilCanBeEncodedInSynthesizedEncodableByDroppingTheKeyByDefault() {
+        // Given
+        let encoder = URLEncodedFormEncoder()
+
+        // When
+        let result = Result<String, Error> { try encoder.encode(OptionalEncodableStruct()) }
+
+        // Then
+        XCTAssertEqual(result.success, "one=one")
+    }
+
     func testThatNilCanBeEncodedAsNull() {
         // Given
         let encoder = URLEncodedFormEncoder(nilEncoding: .null)
@@ -864,6 +875,17 @@ final class URLEncodedFormEncoderTests: BaseTestCase {
         XCTAssertEqual(result.success, "a=null")
     }
 
+    func testThatNilCanBeEncodedInSynthesizedEncodableAsNull() {
+        // Given
+        let encoder = URLEncodedFormEncoder(nilEncoding: .null)
+
+        // When
+        let result = Result<String, Error> { try encoder.encode(OptionalEncodableStruct()) }
+
+        // Then
+        XCTAssertEqual(result.success, "one=one&two=null")
+    }
+
     func testThatNilCanBeEncodedByDroppingTheKey() {
         // Given
         let encoder = URLEncodedFormEncoder(nilEncoding: .dropKey)
@@ -876,6 +898,17 @@ final class URLEncodedFormEncoderTests: BaseTestCase {
         XCTAssertEqual(result.success, "")
     }
 
+    func testThatNilCanBeEncodedInSynthesizedEncodableByDroppingTheKey() {
+        // Given
+        let encoder = URLEncodedFormEncoder(nilEncoding: .dropKey)
+
+        // When
+        let result = Result<String, Error> { try encoder.encode(OptionalEncodableStruct()) }
+
+        // Then
+        XCTAssertEqual(result.success, "one=one")
+    }
+
     func testThatNilCanBeEncodedByDroppingTheValue() {
         // Given
         let encoder = URLEncodedFormEncoder(nilEncoding: .dropValue)
@@ -888,6 +921,17 @@ final class URLEncodedFormEncoderTests: BaseTestCase {
         XCTAssertEqual(result.success, "a=")
     }
 
+    func testThatNilCanBeEncodedInSynthesizedEncodableByDroppingTheValue() {
+        // Given
+        let encoder = URLEncodedFormEncoder(nilEncoding: .dropValue)
+
+        // When
+        let result = Result<String, Error> { try encoder.encode(OptionalEncodableStruct()) }
+
+        // Then
+        XCTAssertEqual(result.success, "one=one&two=")
+    }
+
     func testThatSpacesCanBeEncodedAsPluses() {
         // Given
         let encoder = URLEncodedFormEncoder(spaceEncoding: .plusReplaced)
@@ -1093,6 +1137,11 @@ private struct NestedEncodableStruct: Encodable {
     let a = "a"
 }
 
+private struct OptionalEncodableStruct: Encodable {
+    let one = "one"
+    let two: String? = nil
+}
+
 private class EncodableSuperclass: Encodable {
     let one = "one"
     let two = 2