diff --git a/Sources/InfomaniakCore/Networking/AbstractNetworkRequest.swift b/Sources/InfomaniakCore/Networking/AbstractNetworkRequest.swift index 16dcba1..b19a464 100644 --- a/Sources/InfomaniakCore/Networking/AbstractNetworkRequest.swift +++ b/Sources/InfomaniakCore/Networking/AbstractNetworkRequest.swift @@ -20,7 +20,7 @@ import Alamofire import Foundation /// Wrapping HTTP POST and GET parameters in this type -public typealias Parameters = [String: any Any & Sendable] +public typealias Parameters = [String: any Encodable & Sendable] /// Wrapping the body of an HTTP Request with common types public enum RequestBody { @@ -67,7 +67,7 @@ struct BodyDataEncoding: ParameterEncoding { } func encode(_ urlRequest: URLRequestConvertible, - with parameters: Parameters?) throws -> URLRequest { + with parameters: Alamofire.Parameters?) throws -> URLRequest { var request = try urlRequest.asURLRequest() request.httpBody = data return request diff --git a/Sources/InfomaniakCore/Networking/ApiFetcher+Dispatch.swift b/Sources/InfomaniakCore/Networking/ApiFetcher+Dispatch.swift index b1ac1f6..183e21f 100644 --- a/Sources/InfomaniakCore/Networking/ApiFetcher+Dispatch.swift +++ b/Sources/InfomaniakCore/Networking/ApiFetcher+Dispatch.swift @@ -54,9 +54,7 @@ extension ApiFetcher: RequestDispatchable { let method = requestable.method.alamofireMethod switch body { case .POSTParameters(let parameters): - request = try authenticatedRequest(endpoint, - method: method, - parameters: parameters) + request = authenticatedRequest(endpoint, method: method, parameters: parameters) case .requestBody(let data): let headers: HTTPHeaders = [Self.contentType: Self.octetStream] request = authenticatedSession.request(endpoint.url, @@ -65,7 +63,7 @@ extension ApiFetcher: RequestDispatchable { encoding: BodyDataEncoding(data: data), headers: headers) case .none: - request = try authenticatedRequest(endpoint, + request = authenticatedRequest(endpoint, method: method, parameters: nil) } diff --git a/Sources/InfomaniakCore/Networking/ApiFetcher.swift b/Sources/InfomaniakCore/Networking/ApiFetcher.swift index 8639253..2a2898c 100644 --- a/Sources/InfomaniakCore/Networking/ApiFetcher.swift +++ b/Sources/InfomaniakCore/Networking/ApiFetcher.swift @@ -46,7 +46,9 @@ open class ApiFetcher { public var authenticatedSession: Session! private let decoder: JSONDecoder - private let bodyEncoder: any ParameterEncoder + private let bodyEncoder: JSONEncoder + + private let jsonParameterEncoder: JSONParameterEncoder public var currentToken: ApiToken? { get { @@ -62,7 +64,7 @@ open class ApiFetcher { public init( decoder: JSONDecoder? = nil, - bodyEncoder: any ParameterEncoder = JSONParameterEncoder.convertToSnakeCase + bodyEncoder: JSONEncoder? = nil ) { if let decoder { self.decoder = decoder @@ -72,7 +74,19 @@ open class ApiFetcher { defaultDecoder.keyDecodingStrategy = .convertFromSnakeCase self.decoder = defaultDecoder } - self.bodyEncoder = bodyEncoder + if let bodyEncoder { + self.bodyEncoder = bodyEncoder + jsonParameterEncoder = JSONParameterEncoder(encoder: bodyEncoder) + } else { + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + encoder.dateEncodingStrategy = .custom { date, encoder in + var container = encoder.singleValueContainer() + try container.encode(Int(date.timeIntervalSince1970)) + } + self.bodyEncoder = encoder + jsonParameterEncoder = JSONParameterEncoder(encoder: encoder) + } } /// Creates a new authenticated session for the given token. @@ -121,12 +135,15 @@ open class ApiFetcher { public func authenticatedRequest(_ endpoint: Endpoint, method: HTTPMethod = .get, - parameters: Parameters? = nil, + parameters: [String: Encodable]? = nil, overrideEncoder: ParameterEncoder? = nil, headers: HTTPHeaders? = nil, - requestModifier: RequestModifier? = nil) throws -> DataRequest { - guard let encodableParameters = parameters as? Encodable else { - throw ErrorDomain.invalidJsonInBody + requestModifier: RequestModifier? = nil) -> DataRequest { + let encodableParameters: EncodableDictionary? + if let parameters { + encodableParameters = EncodableDictionary(parameters) + } else { + encodableParameters = nil } return authenticatedRequest( @@ -145,7 +162,7 @@ open class ApiFetcher { overrideEncoder: ParameterEncoder? = nil, headers: HTTPHeaders? = nil, requestModifier: RequestModifier? = nil) -> DataRequest { - let encoder = overrideEncoder ?? bodyEncoder + let encoder = overrideEncoder ?? jsonParameterEncoder return authenticatedSession .request( endpoint.url, diff --git a/Sources/InfomaniakCore/Networking/EncodableDictionary.swift b/Sources/InfomaniakCore/Networking/EncodableDictionary.swift new file mode 100644 index 0000000..c034356 --- /dev/null +++ b/Sources/InfomaniakCore/Networking/EncodableDictionary.swift @@ -0,0 +1,47 @@ +/* + Infomaniak Core - iOS + Copyright (C) 2025 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import Foundation + +struct EncodableDictionary: Encodable { + private let dictionary: [String: Encodable] + + init(_ dictionary: [String: Encodable]) { + self.dictionary = dictionary + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + for (key, value) in dictionary { + try container.encode(value, forKey: CodingKeys(stringValue: key)) + } + } + + private struct CodingKeys: CodingKey { + var stringValue: String + var intValue: Int? { return nil } + + init(stringValue: String) { + self.stringValue = stringValue + } + + init?(intValue: Int) { + return nil + } + } +}