<commit_before>//
//  API.swift
//  Pods
//
//  Created by <NAME> on 12/21/16.
//
//

import Foundation

/// Response promise from an API
public typealias Response<T> = Promise<T, APIError>

/// Allowed Methods for HTTP Requests
public enum HTTPMethod: String {
    case get = "GET"
    case put = "PUT"
    case post = "POST"
    case delete = "DELETE"
}

/// API Body
public protocol API {
    /// Endpoint Reference
    associatedtype Endpoint: APIEndpoint
    /// Base URL for the api
    var baseURL: String { get }
    /// Headers that should be included into every single request
    var baseHeaders: [String:String] { get }
    /// Queries that should be included into every single request
    var baseQueries: [String:String] { get }
    /// Will be called before performing a Request for people who like to go deep into the metal
    func willPerform(request: inout URLRequest)
    /// Will allow you to prepare more customizable URL Sessions
    func session(for method: HTTPMethod, at endpoint: Endpoint) -> URLSession
}

public extension API {
    
    /// URL Object for the API
    var base: URL! {
        return URL(string: baseURL)
    }
    
    /// Default is empty
    var baseHeaders: [String:String] {
        return .empty
    }
    
    /// Default is empty
    var baseQueries: [String:String] {
        return .empty
    }
    
    /// Default does nothing
    func willPerform(request: inout URLRequest) {
        // Do Nothing
    }
    
    /// Default is the shared session
    func session(for method: HTTPMethod, at endpoint: Endpoint) -> URLSession {
        return .shared
    }
    
}

public extension API {
    
    /**
     Will do a simple Request
     
     - Parameter method: HTTP Method
     - Parameter endpoint: endpoint of the API it should be sent to
     - Parameter arguments: arguments encoded into the endpoint string
     - Parameter headers: HTTP Headers that should be added to the request
     - Parameter auth: Authentication Manager for the request
     - Parameter body: Data that should be sent in the HTTP Body
     - Parameter acceptableStatusCodes: HTTP Status Codes that mean a succesfull request was done
     - Parameter completionQueue: Queue in which the promise should be run
     
     - Returns: Promise of Data
     */
    public func doDataRequest(with method: HTTPMethod = .get,
                       to endpoint: Endpoint,
                       arguments: [String:CustomStringConvertible] = .empty,
                       headers: [String:CustomStringConvertible] = .empty,
                       queries: [String:CustomStringConvertible] = .empty,
                       auth: Auth = NoAuth.standard,
                       body: Data? = nil,
                       acceptableStatusCodes: [Int] = [200],
                       completionQueue: DispatchQueue = .main) -> Data.Result {
        
        let requestString = arguments ==> endpoint.rawValue ** { string, argument in
            return string.replacingOccurrences(of: "{\(argument.key)}", with: argument.value.description)
        }
        
        let url = (baseQueries + queries >>= { $0.description }) ==> base.appendingPathComponent(requestString) ** { url, query in
            return url.appendingQuery(key: query.key, value: query.value)
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = method.rawValue
        request.httpBody = body
        
        (baseHeaders + headers >>= { $0.description }) => {
            request.addValue($1, forHTTPHeaderField: $0)
        }
        
        return .new(completionQueue: completionQueue) { promise in
            
            auth.apply(to: request).onSuccess { request in
                var request = request
                self.willPerform(request: &request)
                let session = self.session(for: method, at: endpoint)
                let task = session.dataTask(with: request) { (data, response, error) in
                    if let error = error {
                        if let error = error as? URLError, error.code == .timedOut {
                            promise.error(with: .timeout)
                        } else {
                            promise.error(with: .unknown(error: error))
                        }
                        return
                    }
                    let statusCode = (response as? HTTPURLResponse)?.statusCode ?? 200
                    guard acceptableStatusCodes.contains(statusCode) else {
                        promise.error(with: .invalidStatus(code: statusCode, data: data))
                        return
                    }
                    if let data = data {
                        promise.success(with: data)
                    } else {
                        promise.error(with: .noData)
                    }
                }
                task.resume()
            }
            
        }
    }<commit_msg>Better nesting of API call for authentication<commit_after>//
//  API.swift
//  Pods
//
//  Created by <NAME> on 12/21/16.
//
//

import Foundation

/// Response promise from an API
public typealias Response<T> = Promise<T, APIError>

/// Allowed Methods for HTTP Requests
public enum HTTPMethod: String {
    case get = "GET"
    case put = "PUT"
    case post = "POST"
    case delete = "DELETE"
}

/// API Body
public protocol API {
    /// Endpoint Reference
    associatedtype Endpoint: APIEndpoint
    /// Base URL for the api
    var baseURL: String { get }
    /// Headers that should be included into every single request
    var baseHeaders: [String:String] { get }
    /// Queries that should be included into every single request
    var baseQueries: [String:String] { get }
    /// Will be called before performing a Request for people who like to go deep into the metal
    func willPerform(request: inout URLRequest)
    /// Will allow you to prepare more customizable URL Sessions
    func session(for method: HTTPMethod, at endpoint: Endpoint) -> URLSession
}

public extension API {
    
    /// URL Object for the API
    var base: URL! {
        return URL(string: baseURL)
    }
    
    /// Default is empty
    var baseHeaders: [String:String] {
        return .empty
    }
    
    /// Default is empty
    var baseQueries: [String:String] {
        return .empty
    }
    
    /// Default does nothing
    func willPerform(request: inout URLRequest) {
        // Do Nothing
    }
    
    /// Default is the shared session
    func session(for method: HTTPMethod, at endpoint: Endpoint) -> URLSession {
        return .shared
    }
    
}

public extension API {
    
    /**
     Will do a simple Request
     
     - Parameter method: HTTP Method
     - Parameter endpoint: endpoint of the API it should be sent to
     - Parameter arguments: arguments encoded into the endpoint string
     - Parameter headers: HTTP Headers that should be added to the request
     - Parameter auth: Authentication Manager for the request
     - Parameter body: Data that should be sent in the HTTP Body
     - Parameter acceptableStatusCodes: HTTP Status Codes that mean a succesfull request was done
     - Parameter completionQueue: Queue in which the promise should be run
     
     - Returns: Promise of Data
     */
    public func doDataRequest(with method: HTTPMethod = .get,
                       to endpoint: Endpoint,
                       arguments: [String:CustomStringConvertible] = .empty,
                       headers: [String:CustomStringConvertible] = .empty,
                       queries: [String:CustomStringConvertible] = .empty,
                       auth: Auth = NoAuth.standard,
                       body: Data? = nil,
                       acceptableStatusCodes: [Int] = [200],
                       completionQueue: DispatchQueue = .main) -> Data.Result {
        
        let requestString = arguments ==> endpoint.rawValue ** { string, argument in
            return string.replacingOccurrences(of: "{\(argument.key)}", with: argument.value.description)
        }
        
        let url = (baseQueries + queries >>= { $0.description }) ==> base.appendingPathComponent(requestString) ** { url, query in
            return url.appendingQuery(key: query.key, value: query.value)
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = method.rawValue
        request.httpBody = body
        
        (baseHeaders + headers >>= { $0.description }) => {
            request.addValue($1, forHTTPHeaderField: $0)
        }
        
        return auth.apply(to: request).nested { request, promise in
            var request = request
            self.willPerform(request: &request)
            let session = self.session(for: method, at: endpoint)
            let task = session.dataTask(with: request) { (data, response, error) in
                if let error = error {
                    if let error = error as? URLError, error.code == .timedOut {
                        promise.error(with: .timeout)
                    } else {
                        promise.error(with: .unknown(error: error))
                    }
                    return
                }
                let statusCode = (response as? HTTPURLResponse)?.statusCode ?? 200
                guard acceptableStatusCodes.contains(statusCode) else {
                    promise.error(with: .invalidStatus(code: statusCode, data: data))
                    return
                }
                if let data = data {
                    promise.success(with: data)
                } else {
                    promise.error(with: .noData)
                }
            }
            task.resume()
        }
    }
{'fraction_non_alphanumeric': 0.08606472892187178, 'fraction_numerical': 0.0032982890125747267, 'mean_word_length': 1.7355511700028192, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5881488', 'n_tokens_mistral': 2468, 'n_tokens_neox': 2399, 'n_words': 817}