<commit_before>// MARK: Class implementation

/// Default implementation of a service.
/// Serves your `Call` to a server and parses the respons.
/// Response is delivered to you as a `Result` that you can use in a switch. You get the most detailed results with the functions below.
/// If you want you can use the convenience functions in the extension. They call these functions and print the errors by default. 
/// If you need more control over the errors you can use these functions directly.
/// _Remark_: If you need to cancel, know when everything is done, service request to continue in the background use `ServiceQueue`.
/// _Warning_: The session holds a strong reference to it's delegates. You should invalidate or we do in at `deinit`
open class Service {
    open let configuration: Configuration

    let faroSession: FaroSessionable

    public init(configuration: Configuration, faroSession: FaroSessionable = FaroSession()) {
        self.configuration = configuration
        self.faroSession = faroSession
    }

    // MARK: - Results transformed to Model(s)

    // MARK: - Update

    /// The other `perform` methods create the model. This function updates the model.
    /// - parameter call: gives the details to find the entity on the server
    /// - parameter autostart: by default this is true. This means that `resume()` is called immeditatly on the `URLSessionDataTask` created by this function.
    /// - parameter updateModel: JSON will be given to this model to update
    /// - parameter modelResult: `Result<M: Deserializable>` closure should be called with `case Model(M)` other cases are a failure.
    /// - returns: URLSessionDataTask if the task could not be created that probably means the `URLSession` is invalid.
    @discardableResult
    open func perform<M: Deserializable & Updatable>(_ call: Call, on updateModel: M?, autoStart: Bool = true, modelResult: @escaping (Result<M>) -> ()) -> URLSessionDataTask? {

        return performJsonResult(call, autoStart: autoStart) { (jsonResult: Result<M>) in
            switch jsonResult {
            case .json(let json):
                modelResult(self.handle(json: json, on: updateModel, call: call))
            default:
                modelResult(jsonResult)
                break
            }
        }
    }

    // MARK: - Create

    /// On success create a model and updates it with the received JSON data.
    /// - parameter call: gives the details to find the entity on the server
    /// - parameter autostart: by default this is true. This means that `resume()` is called immeditatly on the `URLSessionDataTask` created by this function.
    /// - parameter modelResult : `Result<M: Deserializable>` closure should be called with `case Model(M)` other cases are a failure.
    /// - returns: URLSessionDataTask if the task could not be created that probably means the `URLSession` is invalid.
    @discardableResult
    open func perform<M: Deserializable>(_ call: Call, autoStart: Bool = true, modelResult: @escaping (Result<M>) -> ()) -> URLSessionDataTask? {

        return performJsonResult(call, autoStart: autoStart) { (jsonResult: Result<M>) in
            switch jsonResult {
            case .json(let json):
                modelResult(self.handle(json: json, call: call))
            default:
                modelResult(jsonResult)
                break
            }
        }
    }

    // MARK: - With Paging information

    /// On success create a model and updates it with the received JSON data. The JSON is also passed to `page` closure and can be inspected for paging information.
    /// - parameter call: gives the details to find the entity on the server
    /// - parameter autostart: by default this is true. This means that `resume()` is called immeditatly on the `URLSessionDataTask` created by this function.
    /// - parameter modelResult : `Result<M: Deserializable>` closure should be called with `case Model(M)` other cases are a failure.
    /// - returns: URLSessionDataTask if the task could not be created that probably means the `URLSession` is invalid.
    @discardableResult
    open func perform<M: Deserializable, P: Deserializable>(_ call: Call, page: @escaping(P?)->(),  autoStart: Bool = true, modelResult: @escaping (Result<M>) -> ()) -> URLSessionDataTask? {

        return performJsonResult(call, autoStart: autoStart) { (jsonResult: Result<M>) in
            switch jsonResult {
            case .json(let json):
                modelResult(self.handle(json: json, call: call))
                page(P(from: json))
            default:
                modelResult(jsonResult)
                break
            }
        }
    }

    // MARK: - JSON results

    /// Handles incomming data and tries to parse the data as JSON.
    /// - parameter call: gives the details to find the entity on the server
    /// - parameter autostart: by default this is true. This means that `resume()` is called immeditatly on the `URLSessionDataTask` created by this function.
    /// - parameter jsonResult: closure is called when valid or invalid json data is received.
    /// - returns: URLSessionDataTask if the task could not be created that probably means the `URLSession` is invalid.
    @discardableResult
    open func performJsonResult<M: Deserializable>(_ call: Call, autoStart: Bool = true, jsonResult: @escaping (Result<M>) -> ()) -> URLSessionDataTask? {

        guard let request = call.request(withConfiguration: configuration) else {
            jsonResult(.failure(FaroError.invalidUrl("\(configuration.baseURL)/\(call.path)")))
            return nil
        }

        let task = faroSession.dataTask(with: request, completionHandler: { (data, response, error) in
            let dataResult = self.handle(data: data, urlResponse: response, error: error) as Result<M>
            switch dataResult {
            case .data(let data):
                self.configuration.adaptor.serialize(from: data) { (serializedResult: Result<M>) in
                    switch serializedResult {
                    case .json(json: let json):
                        jsonResult(.json(json))
                    default:
                        jsonResult(serializedResult)
                    }
                }
            default:
                jsonResult(dataResult)
            }

        })

        guard autoStart else {
            return task
        }

        faroSession.resume(task)
        return task
    }

    // MARK: - No ressponse data

    /// Use this to write to the server when you do not need a data result, just ok.
    /// If you expect a data result use `perform(call:result:)`
    /// - parameter call: should be of a type that does not expect data in the result.
    /// - parameter writeResult: `WriteResult` closure should be called with `.ok` other cases are a failure.
    /// - returns: URLSessionDataTask if the task could not be created that probably means the `URLSession` is invalid.
    @discardableResult
    open func performWrite(_ writeCall: Call, autoStart: Bool = true, writeResult: @escaping (WriteResult) -> ()) -> URLSessionDataTask? {

        guard let request = writeCall.request(withConfiguration: configuration) else {<commit_msg>spelling<commit_after>// MARK: Class implementation

/// Default implementation of a service.
/// Serves your `Call` to a server and parses the respons.
/// Response is delivered to you as a `Result` that you can use in a switch. You get the most detailed results with the functions below.
/// If you want you can use the convenience functions in the extension. They call these functions and print the errors by default. 
/// If you need more control over the errors you can use these functions directly.
/// _Remark_: If you need to cancel, know when everything is done, service request to continue in the background use `ServiceQueue`.
/// _Warning_: The session holds a strong reference to it's delegates. You should invalidate or we do in at `deinit`
open class Service {
    open let configuration: Configuration

    let faroSession: FaroSessionable

    public init(configuration: Configuration, faroSession: FaroSessionable = FaroSession()) {
        self.configuration = configuration
        self.faroSession = faroSession
    }

    // MARK: - Results transformed to Model(s)

    // MARK: - Update

    /// The other `perform` methods create the model. This function updates the model.
    /// - parameter call: gives the details to find the entity on the server
    /// - parameter autostart: by default this is true. This means that `resume()` is called immeditatly on the `URLSessionDataTask` created by this function.
    /// - parameter updateModel: JSON will be given to this model to update
    /// - parameter modelResult: `Result<M: Deserializable>` closure should be called with `case Model(M)` other cases are a failure.
    /// - returns: URLSessionDataTask if the task could not be created that probably means the `URLSession` is invalid.
    @discardableResult
    open func perform<M: Deserializable & Updatable>(_ call: Call, on updateModel: M?, autoStart: Bool = true, modelResult: @escaping (Result<M>) -> ()) -> URLSessionDataTask? {

        return performJsonResult(call, autoStart: autoStart) { (jsonResult: Result<M>) in
            switch jsonResult {
            case .json(let json):
                modelResult(self.handle(json: json, on: updateModel, call: call))
            default:
                modelResult(jsonResult)
                break
            }
        }
    }

    // MARK: - Create

    /// On success create a model and updates it with the received JSON data.
    /// - parameter call: gives the details to find the entity on the server
    /// - parameter autostart: by default this is true. This means that `resume()` is called immeditatly on the `URLSessionDataTask` created by this function.
    /// - parameter modelResult : `Result<M: Deserializable>` closure should be called with `case Model(M)` other cases are a failure.
    /// - returns: URLSessionDataTask if the task could not be created that probably means the `URLSession` is invalid.
    @discardableResult
    open func perform<M: Deserializable>(_ call: Call, autoStart: Bool = true, modelResult: @escaping (Result<M>) -> ()) -> URLSessionDataTask? {

        return performJsonResult(call, autoStart: autoStart) { (jsonResult: Result<M>) in
            switch jsonResult {
            case .json(let json):
                modelResult(self.handle(json: json, call: call))
            default:
                modelResult(jsonResult)
                break
            }
        }
    }

    // MARK: - With Paging information

    /// On success create a model and updates it with the received JSON data. The JSON is also passed to `page` closure and can be inspected for paging information.
    /// - parameter call: gives the details to find the entity on the server
    /// - parameter autostart: by default this is true. This means that `resume()` is called immeditatly on the `URLSessionDataTask` created by this function.
    /// - parameter modelResult : `Result<M: Deserializable>` closure should be called with `case Model(M)` other cases are a failure.
    /// - returns: URLSessionDataTask if the task could not be created that probably means the `URLSession` is invalid.
    @discardableResult
    open func perform<M: Deserializable, P: Deserializable>(_ call: Call, page: @escaping(P?)->(),  autoStart: Bool = true, modelResult: @escaping (Result<M>) -> ()) -> URLSessionDataTask? {

        return performJsonResult(call, autoStart: autoStart) { (jsonResult: Result<M>) in
            switch jsonResult {
            case .json(let json):
                modelResult(self.handle(json: json, call: call))
                page(P(from: json))
            default:
                modelResult(jsonResult)
                break
            }
        }
    }

    // MARK: - JSON results

    /// Handles incomming data and tries to parse the data as JSON.
    /// - parameter call: gives the details to find the entity on the server
    /// - parameter autostart: by default this is true. This means that `resume()` is called immeditatly on the `URLSessionDataTask` created by this function.
    /// - parameter jsonResult: closure is called when valid or invalid json data is received.
    /// - returns: URLSessionDataTask if the task could not be created that probably means the `URLSession` is invalid.
    @discardableResult
    open func performJsonResult<M: Deserializable>(_ call: Call, autoStart: Bool = true, jsonResult: @escaping (Result<M>) -> ()) -> URLSessionDataTask? {

        guard let request = call.request(withConfiguration: configuration) else {
            jsonResult(.failure(FaroError.invalidUrl("\(configuration.baseURL)/\(call.path)")))
            return nil
        }

        let task = faroSession.dataTask(with: request, completionHandler: { (data, response, error) in
            let dataResult = self.handle(data: data, urlResponse: response, error: error) as Result<M>
            switch dataResult {
            case .data(let data):
                self.configuration.adaptor.serialize(from: data) { (serializedResult: Result<M>) in
                    switch serializedResult {
                    case .json(json: let json):
                        jsonResult(.json(json))
                    default:
                        jsonResult(serializedResult)
                    }
                }
            default:
                jsonResult(dataResult)
            }

        })

        guard autoStart else {
            return task
        }

        faroSession.resume(task)
        return task
    }

    // MARK: - WRITE calls (like .POST, .PUT, ...)
    /// Use this to write to the server when you do not need a data result, just ok.
    /// If you expect a data result use `perform(call:result:)`
    /// - parameter call: should be of a type that does not expect data in the result.
    /// - parameter writeResult: `WriteResult` closure should be called with `.ok` other cases are a failure.
    /// - returns: URLSessionDataTask if the task could not be created that probably means the `URLSession` is invalid.
    @discardableResult
    open func performWrite(_ writeCall: Call, autoStart: Bool = true, writeResult: @escaping (WriteResult) -> ()) -> URLSessionDataTask? {

        guard let request = writeCall.request(withConfiguration: configuration) else {
{'fraction_non_alphanumeric': 0.08923395445134576, 'fraction_numerical': 0.0, 'mean_word_length': 2.541300097751711, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 57, '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': '1639487', 'n_tokens_mistral': 3731, 'n_tokens_neox': 3604, 'n_words': 1526}