OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
21.33k stars 6.45k forks source link

[REQ][Swift] Date as URLQueryItem #6906

Open kbrz opened 4 years ago

kbrz commented 4 years ago

Is your feature request related to a problem? Please describe.

When sending a date as URL query param and using ISO 8601 date formatter the resulting string may contain a plus ('+') sign. As per apple documentation this plus sign isn't percent encoded. Most of web servers will translate this plus sign into space and backend will receive invalid date format.

Describe the solution you'd like

Enhance current implementation to replace plus signs in date strings with percent encoded value (%2B).

Describe alternatives you've considered

Another feature request might be adding a possibility to specify multiple date formatter per request body/params and response.

biovolt commented 4 years ago

The same goes for email addresses with a + in which is a valid email address but does not work over the service. We cannot encode the + before the time because that results in it being double encoded.

Or supposed to pass handle this?

matteogazzato commented 3 years ago

For everybody stumbling upon this:

considering this, our solution was to create custom encodings based on ParameterEncoding protocol with percent escape.

In particular, I'm referring to form url encoding and url encoding.

class OpenAPICustomFormURLEncoding: ParameterEncoding {
    func encode(_ urlRequest: URLRequest, with parameters: [String: Any]?) throws -> URLRequest {

        var urlRequest = urlRequest

        var requestBodyComponents = URLComponents()
        requestBodyComponents.queryItems = APIHelper.mapValuesToQueryItemsEscaped(parameters ?? [:])

        if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
            urlRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        }

        urlRequest.httpBody = requestBodyComponents.query?.data(using: .utf8)

        return urlRequest
    }
}

where mapValuesToQueryItemsEscaped(_ source: [String: Any?]) -> [URLQueryItem]? is a utils method added to a APIHelper extension

/// Map passed values to percent escaped query items. This method is a copy / paste `mapValuesToQueryItems`
    /// from `APIHelper`, adding escape through `escape` utils.
    /// - Parameter source: keys - values to escape and used to create query params
    /// - Returns: a `URLQueryItem` list of query items
    public static func mapValuesToQueryItemsEscaped(_ source: [String: Any?]) -> [URLQueryItem]? {
        let destination = source.filter { $0.value != nil }.reduce(into: [URLQueryItem]()) { result, item in
            if let collection = item.value as? [Any?] {
                collection.filter { $0 != nil }.map { "\($0!)" }.forEach { value in
                    result.append(URLQueryItem(name: escape(item.key), value: escape(value)))
                }
            } else if let value = item.value {
                result.append(URLQueryItem(name: escape(item.key), value: escape("\(value)")))
            }
        }

        if destination.isEmpty {
            return nil
        }
        return destination
    }

Finally escape(_ string: String) -> String is another utils method added to the same extension

/// Returns a percent-escaped string following RFC 3986 for a query string key or value.
    ///
    /// RFC 3986 states that the following characters are "reserved" characters.
    ///
    /// - General Delimiters: ":", "#", "[", "]", "@", "?", "/"
    /// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
    ///
    /// In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow
    /// query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/"
    /// should be percent-escaped in the query string.
    ///
    /// - parameter string: The string to be percent-escaped.
    ///
    /// - returns: The percent-escaped string.
    public static func escape(_ string: String) -> String {
        let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
        let subDelimitersToEncode = "!$&'()*+,;="

        var allowedCharacterSet = CharacterSet.urlQueryAllowed
        allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")

        return string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string
    }

The encoding method was taken from Alamofire parameter encoding implementation here.

From our side is perfectly working, consistent and is not depending from OpenAPI generated code.

Hope this helps.