0

When i try to hit Xcode server code coverage API by passing integration ID, instead of JSON response it is downloading a .bz2 file directly. I want to show the file wise coverage report in my custom dashboard using this API.

Is there any way i can get JSOn response from this API (https://developer.apple.com/library/content/documentation/Xcode/Conceptual/XcodeServerAPIReference/CodeCoverage.html) instead of .bz2 file?

Vidhya Sri
  • 1,773
  • 1
  • 17
  • 46

1 Answers1

1

Unfortunately, the API only returns the .bz2 compressed JSON file. Even when specifying a HTTP Header of Accept=application/json.

The only way around this is to decompress the data to access the underlying JSON.

Here's an example of what this could look like on iOS/swift using the framework BZipCompression to decompress the data stream:

import Foundation
import BZipCompression

public class Coverage {
    public typealias CoverageCompletion = (_: Data?, _: Error?) -> Void

    public enum Errors: Error {
        case invalidURL
        case invalidResponse
        case invalidStatusCode
        case invalidData
    }

    static var session: URLSession {
        let session = URLSession(configuration: URLSessionConfiguration.default, delegate: LocalhostSessionDelegate.default, delegateQueue: nil)
        return session
    }

    static public func coverage(forIntegrationWithIdentifier identifier: String, completion: @escaping CoverageCompletion) {
        guard let url = URL(string: "https://localhost:20343/api/integrations/\(identifier)/coverage") else {
            completion(nil, Errors.invalidURL)
            return
        }

        let request = URLRequest(url: url)
        let task = session.dataTask(with: request) { (data, response, error) in
            guard error == nil else {
                completion(nil, error)
                return
            }

            guard let urlResponse = response as? HTTPURLResponse else {
                completion(nil, Errors.invalidResponse)
                return
            }

            guard urlResponse.statusCode == 200 else {
                completion(nil, Errors.invalidStatusCode)
                return
            }

            guard let d = data else {
                completion(nil, Errors.invalidData)
                return
            }

            var decompressedData: Data
            do {
                decompressedData = try self.decompress(data: d)
            } catch let decompressionError {
                completion(nil, decompressionError)
                return
            }

            completion(decompressedData, nil)
        }
        task.resume()
    }

    static internal func decompress(data: Data) throws -> Data {
        let decompressedData = try BZipCompression.decompressedData(with: data)

        guard let decompressedString = String(data: decompressedData, encoding: .utf8) else {
            throw Errors.invalidData
        }

        guard let firstBrace = decompressedString.range(of: "{") else {
           throw Errors.invalidData
        }

        guard let lastBrace = decompressedString.range(of: "}", options: .backwards, range: nil, locale: nil) else {
            throw Errors.invalidData
        }

        let range = decompressedString.index(firstBrace.lowerBound, offsetBy: 0)..<decompressedString.index(lastBrace.lowerBound, offsetBy: 1)
        let json = decompressedString.substring(with: range)

        guard let validData = json.data(using: .utf8) else {
            throw Errors.invalidData
        }

        return validData
    }
}

/// Class implementing the NSURLSessionDelegate which forcefully bypasses untrusted SSL Certificates.
public class LocalhostSessionDelegate: NSObject, URLSessionDelegate {
    static public var `default` = LocalhostSessionDelegate()

    // MARK: - NSURLSessionDelegate
    @objc open func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        guard challenge.previousFailureCount < 1 else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }

        var credentials: URLCredential?
        if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
            if let serverTrust = challenge.protectionSpace.serverTrust {
                credentials = URLCredential(trust: serverTrust)
            }
        }

        completionHandler(.useCredential, credentials)
    }
}

I've noticed that the decompressed data often includes invalid control characters and other garbage at the beginning and end of the valid JSON block. The decompress() cleans up the data before returning it in the completion block.

You may want to check out my swift XCServerAPI framework on GitHub. I'll be adding the Code Coverage endpoint with this exact solution.

richardpiazza
  • 1,487
  • 10
  • 23
  • I am trying to consume this Code coverage API in angular/ionic based web application to display it in dashboard. I need this JSON data over there and not in my iOS app. What should i do in this scenario? Should i ask my web developer to download this .bz2 and convert it to JSON at their end to display the data? – Vidhya Sri Jul 28 '17 at 10:04
  • 1
    You can do the decompression in javascript using something like CompressJS (https://github.com/cscott/compressjs). Once the data is decompressed you should have valid JSON that you can then interact with. Just remember that the Code Coverage API will return a 404 status code for any integration that does not have data. – richardpiazza Jul 28 '17 at 21:39