vapor / multipart-kit

🏞 Parses and serializes multipart-encoded data with Codable support.
MIT License
139 stars 41 forks source link

Bug in FormDataEncoder with Optional<CustomValue> #63

Closed nerzh closed 3 years ago

nerzh commented 3 years ago

Describe the bug

I don't know english... file FormDataEncoder.swift


private final class FormDataEncoderContext {
    var parts: [MultipartPart]
    init() {
        self.parts = []
    }

    func encode<E>(_ encodable: E, at codingPath: [CodingKey]) throws where E: Encodable {
        guard let convertible = encodable as? MultipartPartConvertible else {
            throw MultipartError.convertibleType(E.self)
        }

        guard var part = convertible.multipart else {
            throw MultipartError.convertibleType(E.self)
        }

        switch codingPath.count {
        case 1: part.name = codingPath[0].stringValue
        case 2:
            guard codingPath[1].intValue != nil else {
                throw MultipartError.nesting
            }
            part.name = codingPath[0].stringValue + "[]"
        default:
            throw MultipartError.nesting
        }
        self.parts.append(part)
    }
}

private struct _FormDataEncoder: Encoder {
    let codingPath: [CodingKey]
    let multipart: FormDataEncoderContext
    var userInfo: [CodingUserInfoKey: Any] {
        return [:]
    }

    init(multipart: FormDataEncoderContext, codingPath: [CodingKey]) {
        self.multipart = multipart
        self.codingPath = codingPath
    }

    func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {
        return KeyedEncodingContainer(_FormDataKeyedEncoder(multipart: multipart, codingPath: codingPath))
    }

    func unkeyedContainer() -> UnkeyedEncodingContainer {
        return _FormDataUnkeyedEncoder(multipart: multipart, codingPath: codingPath)
    }

    func singleValueContainer() -> SingleValueEncodingContainer {
        return _FormDataSingleValueEncoder(multipart: multipart, codingPath: codingPath)
    }
}
struct A: Encodable {
    var number: Int 
}

struct B: Encodable {
    var a: A? /// this Optional<A>
}

and such optional objects follow the call path func singleValueContainer() -> SingleValueEncodingContainer

after the method is called

func encode(_ encodable: E, at codingPath: [CodingKey]) throws where E: Encodable {

and does not pass this test

encodable as? MultipartPartConvertible

because the struct A not conform MultipartPartConvertible

end

siemensikkema commented 3 years ago

@nerzh support for this is in #57. In your example, this would be the result:

    func testIssue63() throws {
        struct A: Encodable {
            var number: Int
        }

        struct B: Encodable {
            var a: A? /// this Optional<A>
        }

        let encoded = try FormDataEncoder().encode(B(a: A(number: 1)), boundary: "-")
        XCTAssertEqual(encoded, """
        ---\r
        Content-Disposition: form-data; name="a[number]"\r
        \r
        1\r
        -----\r\n
        """)
    }
nerzh commented 3 years ago

yes, this behavior that I was expecting. Then I will check the versions of your library, for now thanks

nerzh commented 3 years ago

oh, I saw, the pull request has not been accepted yet ...