swift-travis

0.3.0

A Travis v3 API client written in Swift
iainsmith/swift-travis

What's New

SwiftNIO support & dynamicMemberLookup

2020-04-13T22:20:02Z
  • Add support for SwiftNIO EventLoopFuture based API
  • Separate out TravisV3Core package for Codable types.
  • Use keypath dynamicMemberLookup on Metadata & Embed
  • Add small example CLI using ArgumentParser
  • Currently depends on a fork of async-http-client until an escaping issue is resolved - PR here
  • Rename Meta -> Metadata

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 a page of results from the paginated API
  • Similarly you can all 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.

Supported swift versions

If you are using Swift 5.1 or newer you can use the latest release If you need support for Swift 4.2 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.

Description

  • Swift Tools 5.1.0
View More Packages from this Author

Dependencies

Last updated: Tue Aug 30 2022 02:44:12 GMT-0500 (GMT-05:00)