Repository: vapor/multipart-kit (swift)

Issue:
Decoding String enum
<!-- 🚀 Thank you for contributing! --->

### Describe the bug

Server show the error on a multipart/form-data request with a String enum  - "Could not convert value at "license" to type License from multipart part. for key license". Earlier versions of multipart-kit work as expected. At least 4.0.0.

```
enum License: String, Codable, CaseIterable {
    case dme1
}
```

### To Reproduce

<!-- Tell us how to reproduce this issue. -->
<!-- Please provide as much detail as possible (the more code snippets, the better)! -->
<!-- If we cannot recreate it, we will not be able to figure out how to fix it. -->

Steps to reproduce the behavior:
1. Send a multipart/form-data request from a client with a String enum.
2. Try decoding the request in a Content conform struct on the server.
3. Server shows the error.

### Expected behavior

Correct decoding String enum.

### Environment

<!-- We must know your exact environment or it is very difficult to help. -->
<!-- Hint: use `vapor --version` in the root directory of your Vapor project. -->

* Vapor Framework version: 4.47.0
* OS version: Ubuntu 20.04, macOS 11.4
* multipart-kit: 4.2.0

Pull request:
Fixes regression that broke falling back to `Codable` for types not conforming to `MultipartPartConvertible` (#66, fixes #65)

Note: this removes conformance of `UUID` to `MultipartPartConvertible` introduced in 4.2.0 because it is no longer needed.

Patch:
diff --git a/Sources/MultipartKit/FormDataDecoder.swift b/Sources/MultipartKit/FormDataDecoder.swift
index 96f7349d..69314727 100644
--- a/Sources/MultipartKit/FormDataDecoder.swift
+++ b/Sources/MultipartKit/FormDataDecoder.swift
@@ -228,7 +228,12 @@ private extension MultipartFormData {
 private extension MultipartPart {
     func decode<T>(_ type: T.Type, at codingPath: [CodingKey]) throws -> T where T: Decodable {
         guard
-            let Convertible = T.self as? MultipartPartConvertible.Type,
+            let Convertible = T.self as? MultipartPartConvertible.Type
+        else {
+            return try T(from: _FormDataDecoder(codingPath: codingPath, data: .single(self)))
+        }
+
+        guard
             let decoded = Convertible.init(multipart: self) as? T
         else {
             let path = codingPath.map(\.stringValue).joined(separator: ".")
diff --git a/Sources/MultipartKit/MultipartPartConvertible.swift b/Sources/MultipartKit/MultipartPartConvertible.swift
index 89e15924..ae070b70 100644
--- a/Sources/MultipartKit/MultipartPartConvertible.swift
+++ b/Sources/MultipartKit/MultipartPartConvertible.swift
@@ -20,7 +20,7 @@ extension String: MultipartPartConvertible {
     public var multipart: MultipartPart? {
         return MultipartPart(body: self)
     }
-    
+
     public init?(multipart: MultipartPart) {
         self.init(decoding: multipart.body.readableBytesView, as: UTF8.self)
     }
@@ -30,7 +30,7 @@ extension FixedWidthInteger {
     public var multipart: MultipartPart? {
         return MultipartPart(body: self.description)
     }
-    
+
     public init?(multipart: MultipartPart) {
         guard let string = String(multipart: multipart) else {
             return nil
@@ -54,7 +54,7 @@ extension Float: MultipartPartConvertible {
     public var multipart: MultipartPart? {
         return MultipartPart(body: self.description)
     }
-    
+
     public init?(multipart: MultipartPart) {
         guard let string = String(multipart: multipart) else {
             return nil
@@ -67,7 +67,7 @@ extension Double: MultipartPartConvertible {
     public var multipart: MultipartPart? {
         return MultipartPart(body: self.description)
     }
-    
+
     public init?(multipart: MultipartPart) {
         guard let string = String(multipart: multipart) else {
             return nil
@@ -80,7 +80,7 @@ extension Bool: MultipartPartConvertible {
     public var multipart: MultipartPart? {
         return MultipartPart(body: self.description)
     }
-    
+
     public init?(multipart: MultipartPart) {
         guard let string = String(multipart: multipart) else {
             return nil
@@ -98,16 +98,3 @@ extension Data: MultipartPartConvertible {
         self.init(multipart.body.readableBytesView)
     }
 }
-
-extension UUID: MultipartPartConvertible {
-    public var multipart: MultipartPart? {
-        .init(body: uuidString)
-    }
-
-    public init?(multipart: MultipartPart) {
-        guard let string = String(multipart: multipart) else {
-            return nil
-        }
-        self.init(uuidString: string)
-    }
-}


Test patch:
diff --git a/Tests/MultipartKitTests/MultipartKitTests.swift b/Tests/MultipartKitTests/MultipartKitTests.swift
index 65b39e26..8e9230d4 100644
--- a/Tests/MultipartKitTests/MultipartKitTests.swift
+++ b/Tests/MultipartKitTests/MultipartKitTests.swift
@@ -307,8 +307,8 @@ class MultipartTests: XCTestCase {
         }
 
         XCTAssertThrowsError(try FormDataDecoder().decode(Foo.self, from: data, boundary: "hello")) { error in
-            guard case let DecodingError.dataCorrupted(context) = error else {
-                XCTFail("Was expecting an error of type DecodingError.dataCorrupted")
+            guard case let DecodingError.typeMismatch(_, context) = error else {
+                XCTFail("Was expecting an error of type DecodingError.typeMismatch")
                 return
             }
             XCTAssertEqual(context.codingPath.map(\.stringValue), ["link"])
@@ -559,6 +559,23 @@ class MultipartTests: XCTestCase {
         XCTAssertEqual(try FormDataEncoder().encode(uuid, boundary: "-"), multipart)
         XCTAssertEqual(try FormDataDecoder().decode(UUID.self, from: multipart, boundary: "-"), uuid)
     }
+
+    // https://github.com/vapor/multipart-kit/issues/65
+    func testEncodingAndDecodingNonMultipartPartConvertibleCodableTypes() throws {
+        enum License: String, Codable, CaseIterable, Equatable {
+            case dme1
+        }
+        let license = License.dme1
+        let multipart = """
+        ---\r
+        Content-Disposition: form-data\r
+        \r
+        \(license.rawValue)\r
+        -----\r\n
+        """
+        XCTAssertEqual(try FormDataEncoder().encode(license, boundary: "-"), multipart)
+        XCTAssertEqual(try FormDataDecoder().decode(License.self, from: multipart, boundary: "-"), license)
+    }
 }
 
 // https://stackoverflow.com/a/54524110/1041105