<commit_before>import Foundation
import FirebaseAuth
import FirebaseMessaging

public typealias MyTBARequestCompletionBlock = (_ data: Data?, _ error: Error?) -> Void

private struct Constants {
    struct APIConstants {
        static let baseURL = URL(string: "https://tbatv-prod-hrd.appspot.com/clientapi/tbaClient/v9/")!
    }
}

public enum MyTBAError: Error {
    case error(String)
}

extension MyTBAError: LocalizedError {
    public var errorDescription: String? {
        switch self {
        case .error(message: let message):
            // TODO: This, unlike the name says, isn't localized
            return message
        }
    }
}

class MyTBA {

    // This shouldn't be public, but we can't fake the auth stuff for testing because Firebase blows
    var authToken: String? {
        didSet {
            // Only disaptch on changed state
            if oldValue == authToken {
                return
            }

            authenticationProvider.post { (observer) in
                if authToken == nil {
                    observer.unauthenticated()
                } else {
                    observer.authenticated()
                }
            }
        }
    }

    public var isAuthenticated: Bool {
        return authToken != nil
    }

    public init(uuid: String, deviceName: String, urlSession: URLSession? = nil) {
        self.uuid = uuid
        self.deviceName = deviceName
        self.urlSession = urlSession ?? URLSession(configuration: .default)

        // Block gets called on init - ignore the init call
        var initCall = true
        Auth.auth().addIDTokenDidChangeListener { (_, user) in
            if initCall {
                initCall = false
                return
            }

            if let user = user {
                user.getIDToken(completion: { (token, _) in
                    self.authToken = token
                })
            } else {
                self.authToken = nil
            }
        }
    }
    var authenticationProvider = Provider<MyTBAAuthenticationObservable>()
    private var urlSession: URLSession
    internal var uuid: String
    internal var deviceName: String

    static var jsonEncoder: JSONEncoder {
        let jsonEncoder = JSONEncoder()
        jsonEncoder.keyEncodingStrategy = .convertToSnakeCase
        return jsonEncoder
    }

    static var jsonDecoder: JSONDecoder {
        let jsonDecoder = JSONDecoder()
        jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
        return jsonDecoder
    }

    func callApi<T: MyTBAResponse>(method: String, bodyData: Data? = nil, completion: @escaping (T?, Error?) -> Void) -> URLSessionDataTask {
        let apiURL = URL(string: method, relativeTo: Constants.APIConstants.baseURL)!
        var request = URLRequest(url: apiURL)
        request.httpMethod = "POST"

        if let authToken = authToken {
            request.addValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization")
        }

        #if DEBUG
        if let bodyData = bodyData, let dataString = try? JSONSerialization.jsonObject(with: bodyData, options: []) {
            print("POST \(method): \(dataString)")
        }
        #endif

        request.httpBody = bodyData

        let task = urlSession.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
            if let error = error {
                completion(nil, error)
            } else if let data = data {
                #if DEBUG
                if let dataString = try? JSONSerialization.jsonObject(with: data, options: []) {
                    print(dataString)
                }
                #endif

                let response = try! MyTBA.jsonDecoder.decode(T.self, from: data)
                if let baseResponse = response as? MyTBABaseResponse {
                    completion(response, error ?? baseResponse.error)
                } else {
                    completion(response, error)
                }
            } else {
                completion(nil, MyTBAError.error("Unexpected response from myTBA API"))
            }
        }
        task.resume()
        return task
    }

}

protocol MyTBAAuthenticationObservable {
    func authenticated()
    func unauthenticated()
}
<commit_msg>Allow invalid API responses from myTBA (#515)<commit_after>import Crashlytics
import Foundation
import FirebaseAuth
import FirebaseMessaging

public typealias MyTBARequestCompletionBlock = (_ data: Data?, _ error: Error?) -> Void

private struct Constants {
    struct APIConstants {
        static let baseURL = URL(string: "https://tbatv-prod-hrd.appspot.com/clientapi/tbaClient/v9/")!
    }
}

public enum MyTBAError: Error {
    case error(String)
}

extension MyTBAError: LocalizedError {
    public var errorDescription: String? {
        switch self {
        case .error(message: let message):
            // TODO: This, unlike the name says, isn't localized
            return message
        }
    }
}

class MyTBA {

    // This shouldn't be public, but we can't fake the auth stuff for testing because Firebase blows
    var authToken: String? {
        didSet {
            // Only disaptch on changed state
            if oldValue == authToken {
                return
            }

            authenticationProvider.post { (observer) in
                if authToken == nil {
                    observer.unauthenticated()
                } else {
                    observer.authenticated()
                }
            }
        }
    }

    public var isAuthenticated: Bool {
        return authToken != nil
    }

    public init(uuid: String, deviceName: String, urlSession: URLSession? = nil) {
        self.uuid = uuid
        self.deviceName = deviceName
        self.urlSession = urlSession ?? URLSession(configuration: .default)

        // Block gets called on init - ignore the init call
        var initCall = true
        Auth.auth().addIDTokenDidChangeListener { (_, user) in
            if initCall {
                initCall = false
                return
            }

            if let user = user {
                user.getIDToken(completion: { (token, _) in
                    self.authToken = token
                })
            } else {
                self.authToken = nil
            }
        }
    }
    var authenticationProvider = Provider<MyTBAAuthenticationObservable>()
    private var urlSession: URLSession
    internal var uuid: String
    internal var deviceName: String

    static var jsonEncoder: JSONEncoder {
        let jsonEncoder = JSONEncoder()
        jsonEncoder.keyEncodingStrategy = .convertToSnakeCase
        return jsonEncoder
    }

    static var jsonDecoder: JSONDecoder {
        let jsonDecoder = JSONDecoder()
        jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
        return jsonDecoder
    }

    func callApi<T: MyTBAResponse>(method: String, bodyData: Data? = nil, completion: @escaping (T?, Error?) -> Void) -> URLSessionDataTask {
        let apiURL = URL(string: method, relativeTo: Constants.APIConstants.baseURL)!
        var request = URLRequest(url: apiURL)
        request.httpMethod = "POST"

        if let authToken = authToken {
            request.addValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization")
        }

        #if DEBUG
        if let bodyData = bodyData, let dataString = try? JSONSerialization.jsonObject(with: bodyData, options: []) {
            print("POST \(method): \(dataString)")
        }
        #endif

        request.httpBody = bodyData

        let task = urlSession.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
            if let error = error {
                completion(nil, error)
            } else if let data = data {
                #if DEBUG
                if let dataString = try? JSONSerialization.jsonObject(with: data, options: []) {
                    print(dataString)
                }
                #endif

                var decodedResponse: T?
                do {
                    decodedResponse = try MyTBA.jsonDecoder.decode(T.self, from: data)
                } catch {
                    Crashlytics.sharedInstance().recordError(error)
                    completion(nil, error)
                    return
                }

                if let myTBAResponse = decodedResponse as? MyTBABaseResponse {
                    completion(decodedResponse, error ?? myTBAResponse.error)
                } else {
                    completion(decodedResponse, error)
                }
            } else {
                completion(nil, MyTBAError.error("Unexpected response from myTBA API"))
            }
        }
        task.resume()
        return task
    }

}

protocol MyTBAAuthenticationObservable {
    func authenticated()
    func unauthenticated()
}

{'fraction_non_alphanumeric': 0.08199502375028274, 'fraction_numerical': 0.000565482922415743, 'mean_word_length': 1.8001899936668777, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 13, 'https://': 2, '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': '1558902', 'n_tokens_mistral': 2343, 'n_tokens_neox': 2212, 'n_words': 622}