Skip to content

iainsmith/swift-travis

Repository files navigation

swift-travis

A Swift interface to the travis-ci v3 API. Supports travis-ci.org, travis-ci.com & on premise deployments.

Build Status | Read the docs

Installation

  • Install with SPM .package(url: "https://github.com/iainsmith/swift-travis", from: "0.3.0"),
  • The swift-travis package has the following 3 libraries you can use in your application.
Use-case interface target
iOS & Mac apps URLSession .product(name: "TravisClient", package: "swift-travis")
CLI & Server APPs EventLoopFuture .product(name: "TravisClientNIO", package: "swift-travis")
Build your own package Codable structs .product(name: "TravisV3Core", package: "swift-travis")

Quick example

import TravisClient

let key: String = ProcessInfo().environment["TRAVIS_TOKEN]"!
let client = TravisClient(token: key, host: .org)

client.userBuilds(query: query) { result in
  switch result {
    case let .success(builds):
      builds.count
      builds.first?.pullRequestTitle
    case let .failure(error):
      // error handling
  }
}

Swift NIO examples

import TravisClientNIO

let key: String = ProcessInfo().environment["TRAVIS_TOKEN]"!
let client = TravisClient(token: key, host: .org) // You can also pass an `EventLoopGroup`

let builds = try client.builds(forRepository: repo).wait()
print(builds.count)
print(builds.first?.pullRequestTitle)

Travis API Concepts.

The api mirrors the names & concepts from the official Travis API documentation.

Minimal vs Standard Representation.

Each model object has two representations. A standard representation that includes all the attributes and a minimal representation that includes some attributes.

public struct MinimalJob: Codable, Minimal {
  public typealias Full = Job
  public let id: Int
}

public struct Job: Codable {
  public let id: Int
  public let number: String
  public let state: String
  // 10 other properties
}

Minimal vs Standard Job Example Travis documentation

If you need more information you can load a standard representation using the client.follow(embed:completion:) method

let build: Meta<Build>
let minimalJob: Embed<MinimalJob> = build.jobs.first! // don't do this in production code

client.follow(embed: minimalJob) { fullJob in
    print(fullJob.state)
}
Modelling the hypermedia API

The Travis v3 API uses a custom hypermedia API spec, that is described on their website. The TravisV3Core targets has a generic Metadata<Object> struct.

@dynamicMemberLookup
public struct Metadata<Object: Codable>: Codable {
  public let type: String
  public let path: String
  public let pagination: Pagination<Object>?
  public let object: Object
}

let builds: Metadata<[Build]>
// dynamicMemberLookup means we can often use Metadata<[Build]> as [Build]
builds.count == builds.object.count

Metadata gives us direct access to the Pagination data and the underlying Object through dynamicMemberLookup.

The travis API often nests resources which is modelled with the Embed<Object> struct.

@dynamicMemberLookup
public struct Embed<Object: Codable>: Codable {
  public let type: String
  public let path: String?
  public let object: Object
}

struct Build {
  public let repository: Embed<MinimalRepository>
  public let branch: Embed<MinimalBranch>
  public let commit: Embed<MinimalCommit>
}

let build: Metadata<Build>
let branchName: String = build.branch.name
Links
  • You can call client.follow(page:) to load the next page of results from the paginated API
  • Similarly you can call client.follow(embed:) to fetch the full version of a MinimalResource. e.g MinimalBranch -> Branch.
import TravisClient

client.activeBuilds { (result: Result<MetaData<[Build]>, TravisError>) in

    /// You can also switch over the result
    switch result {
    case success(let builds: MetaData<[Build]>)
        // Find the number of active builds
        builds.count

        // Find the jobs associated with this build
        guard let job: Embed<MinimalJob> = jobs.first else { return }

        // Each API call returns one resource that has a 'standard representation' full object in this case supports hyper media so you can easily load the full object in a second request.
        client.follow(embed: job) { (jobResult: Result<MetaData<Job>>) in
            print(jobResult)
        }

        // Or follow a paginated request
        client.follow(page: builds.pagination.next) { nextPage in
            print(nextPage)
        }

    case error(let error):
        // handle error
        print(error)
    }
}

Running the tests

# JSON parsing tests
> swift test

# Hit the travis.org API
> TRAVIS_TOKEN=YOUR_TOKEN_HERE swift test

The Integration tests only run if you have a TRAVIS_TOKEN environment variable set. This uses XCTSkipIf which requires Xcode 11.4. Alternatively you can call swift test --filter TravisClientTests.JSONTests

Supported swift versions

If you are using Swift 5.1 or newer you can use the latest release If you need support for Swift 5 or older, use version 0.2.0

TODO

  • Support paginated requests
  • Add User Model
  • Add Simple Query parameters
  • Add Stages Model
  • Add more typed sort parameters
  • Support Type safe Eager Loading.