Skip to content

Commit

Permalink
Merge pull request #27 from mdiep/users
Browse files Browse the repository at this point in the history
Add a method to fetch a user by login
  • Loading branch information
mdiep committed Apr 13, 2016
2 parents 7c6bc70 + 82950a5 commit 8431308
Show file tree
Hide file tree
Showing 12 changed files with 247 additions and 2 deletions.
42 changes: 42 additions & 0 deletions Tentacle.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,20 @@
BEAB175E1C9D0AB8009F8F58 /* repos-mdiep-MDPSplitView-releases-assets-433845.response in Resources */ = {isa = PBXBuildFile; fileRef = BEAB17581C9D062D009F8F58 /* repos-mdiep-MDPSplitView-releases-assets-433845.response */; };
BEB0765D1C8A001C00ABD373 /* GitHubError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEB0765C1C8A001C00ABD373 /* GitHubError.swift */; };
BEB076601C8A019E00ABD373 /* GitHubErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEB0765E1C8A019A00ABD373 /* GitHubErrorTests.swift */; };
BECB8A8B1CBDCD17005D70A6 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = BECB8A8A1CBDCD17005D70A6 /* User.swift */; };
BECB8A8E1CBDD919005D70A6 /* FoundationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BECB8A8D1CBDD919005D70A6 /* FoundationExtensions.swift */; };
BECB8A8F1CBDD91D005D70A6 /* FoundationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BECB8A8D1CBDD919005D70A6 /* FoundationExtensions.swift */; };
BECB8A901CBDD920005D70A6 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = BECB8A8A1CBDCD17005D70A6 /* User.swift */; };
BECB8A991CBDDE4B005D70A6 /* users-mdiep.data in Resources */ = {isa = PBXBuildFile; fileRef = BECB8A911CBDDDBB005D70A6 /* users-mdiep.data */; };
BECB8A9A1CBDDE4B005D70A6 /* users-mdiep.response in Resources */ = {isa = PBXBuildFile; fileRef = BECB8A921CBDDDBB005D70A6 /* users-mdiep.response */; };
BECB8A9B1CBDDE4B005D70A6 /* users-test.data in Resources */ = {isa = PBXBuildFile; fileRef = BECB8A931CBDDDBB005D70A6 /* users-test.data */; };
BECB8A9C1CBDDE4B005D70A6 /* users-test.response in Resources */ = {isa = PBXBuildFile; fileRef = BECB8A941CBDDDBB005D70A6 /* users-test.response */; };
BECB8A9D1CBDDE4C005D70A6 /* users-mdiep.data in Resources */ = {isa = PBXBuildFile; fileRef = BECB8A911CBDDDBB005D70A6 /* users-mdiep.data */; };
BECB8A9E1CBDDE4C005D70A6 /* users-mdiep.response in Resources */ = {isa = PBXBuildFile; fileRef = BECB8A921CBDDDBB005D70A6 /* users-mdiep.response */; };
BECB8A9F1CBDDE4C005D70A6 /* users-test.data in Resources */ = {isa = PBXBuildFile; fileRef = BECB8A931CBDDDBB005D70A6 /* users-test.data */; };
BECB8AA01CBDDE4C005D70A6 /* users-test.response in Resources */ = {isa = PBXBuildFile; fileRef = BECB8A941CBDDDBB005D70A6 /* users-test.response */; };
BECB8AA31CBDDF0F005D70A6 /* UserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BECB8AA11CBDDF0F005D70A6 /* UserTests.swift */; };
BECB8AA41CBDDF0F005D70A6 /* UserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BECB8AA11CBDDF0F005D70A6 /* UserTests.swift */; };
BEEE47421C91B8DF000FFC21 /* ResourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEEE47411C91B8DF000FFC21 /* ResourceType.swift */; };
BEEE47431C91B8DF000FFC21 /* ResourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEEE47411C91B8DF000FFC21 /* ResourceType.swift */; };
BEEE47451C91BB3A000FFC21 /* ArgoExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEEE47441C91BB3A000FFC21 /* ArgoExtensions.swift */; };
Expand Down Expand Up @@ -152,6 +166,13 @@
BEAB17581C9D062D009F8F58 /* repos-mdiep-MDPSplitView-releases-assets-433845.response */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = "repos-mdiep-MDPSplitView-releases-assets-433845.response"; sourceTree = "<group>"; };
BEB0765C1C8A001C00ABD373 /* GitHubError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubError.swift; sourceTree = "<group>"; };
BEB0765E1C8A019A00ABD373 /* GitHubErrorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubErrorTests.swift; sourceTree = "<group>"; };
BECB8A8A1CBDCD17005D70A6 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
BECB8A8D1CBDD919005D70A6 /* FoundationExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationExtensions.swift; sourceTree = "<group>"; };
BECB8A911CBDDDBB005D70A6 /* users-mdiep.data */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "users-mdiep.data"; sourceTree = "<group>"; };
BECB8A921CBDDDBB005D70A6 /* users-mdiep.response */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = "users-mdiep.response"; sourceTree = "<group>"; };
BECB8A931CBDDDBB005D70A6 /* users-test.data */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "users-test.data"; sourceTree = "<group>"; };
BECB8A941CBDDDBB005D70A6 /* users-test.response */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = "users-test.response"; sourceTree = "<group>"; };
BECB8AA11CBDDF0F005D70A6 /* UserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserTests.swift; sourceTree = "<group>"; };
BEEE47411C91B8DF000FFC21 /* ResourceType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResourceType.swift; sourceTree = "<group>"; };
BEEE47441C91BB3A000FFC21 /* ArgoExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArgoExtensions.swift; sourceTree = "<group>"; };
BEEE474D1C92623E000FFC21 /* Response.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -226,6 +247,10 @@
BE0F40DB1C8CA13400E3B11A /* repos-mdiep-NonExistent-releases-tags-tag.response */,
BE0F40E81C8CA1DB00E3B11A /* repos-torvalds-linux-releases-tags-v4.4.data */,
BE0F40E91C8CA1DB00E3B11A /* repos-torvalds-linux-releases-tags-v4.4.response */,
BECB8A911CBDDDBB005D70A6 /* users-mdiep.data */,
BECB8A921CBDDDBB005D70A6 /* users-mdiep.response */,
BECB8A931CBDDDBB005D70A6 /* users-test.data */,
BECB8A941CBDDDBB005D70A6 /* users-test.response */,
);
path = Fixtures;
sourceTree = "<group>";
Expand Down Expand Up @@ -261,12 +286,14 @@
BEEE47441C91BB3A000FFC21 /* ArgoExtensions.swift */,
BE88E80C1C88C72B0034A112 /* Client.swift */,
BE0F40A31C89135D00E3B11A /* Decodable.swift */,
BECB8A8D1CBDD919005D70A6 /* FoundationExtensions.swift */,
BEB0765C1C8A001C00ABD373 /* GitHubError.swift */,
BE88E8311C88D33D0034A112 /* Release.swift */,
BE88E82F1C88CDAB0034A112 /* Repository.swift */,
BEEE47411C91B8DF000FFC21 /* ResourceType.swift */,
BEEE474D1C92623E000FFC21 /* Response.swift */,
BE88E82C1C88C94D0034A112 /* Server.swift */,
BECB8A8A1CBDCD17005D70A6 /* User.swift */,
BE88E7F71C88C6B30034A112 /* Info.plist */,
);
path = Tentacle;
Expand All @@ -283,6 +310,7 @@
BEA86F9C1C9F7E1E0049360B /* RepositoryTests.swift */,
BE1E036A1C9AD87F001296C2 /* ResponseTests.swift */,
BEA86F991C9F7C230049360B /* ServerTests.swift */,
BECB8AA11CBDDF0F005D70A6 /* UserTests.swift */,
BE88E8031C88C6B30034A112 /* Info.plist */,
);
path = Tests;
Expand Down Expand Up @@ -488,19 +516,23 @@
buildActionMask = 2147483647;
files = (
BE0F40E41C8CA13900E3B11A /* repos-Carthage-Carthage-releases-tags-0.15.data in Resources */,
BECB8A9F1CBDDE4C005D70A6 /* users-test.data in Resources */,
BE0F40E61C8CA13900E3B11A /* repos-mdiep-NonExistent-releases-tags-tag.data in Resources */,
BECB8A9D1CBDDE4C005D70A6 /* users-mdiep.data in Resources */,
BE0F40E71C8CA13900E3B11A /* repos-mdiep-NonExistent-releases-tags-tag.response in Resources */,
BE0F40EF1C8CA1E100E3B11A /* repos-torvalds-linux-releases-tags-v4.4.response in Resources */,
BE1E03671C964F98001296C2 /* repos-Carthage-Carthage-releases.page-1-per_page-30.response in Resources */,
BE1E03701C9CF849001296C2 /* repos-mdiep-MDPSplitView-releases-tags-1.0.2.data in Resources */,
BEAB175C1C9D0AB7009F8F58 /* repos-mdiep-MDPSplitView-releases-assets-433845.response in Resources */,
BECB8AA01CBDDE4C005D70A6 /* users-test.response in Resources */,
BE1E03661C964F98001296C2 /* repos-Carthage-Carthage-releases.page-1-per_page-30.data in Resources */,
BE1E03681C964F98001296C2 /* repos-Carthage-Carthage-releases.page-2-per_page-30.data in Resources */,
BEAB175B1C9D0AB7009F8F58 /* repos-mdiep-MDPSplitView-releases-assets-433845.data in Resources */,
BE0F40EE1C8CA1E100E3B11A /* repos-torvalds-linux-releases-tags-v4.4.data in Resources */,
BE1E03721C9CF849001296C2 /* repos-mdiep-MDPSplitView-releases-tags-1.0.2.response in Resources */,
BE0F40E51C8CA13900E3B11A /* repos-Carthage-Carthage-releases-tags-0.15.response in Resources */,
BE1E03691C964F98001296C2 /* repos-Carthage-Carthage-releases.page-2-per_page-30.response in Resources */,
BECB8A9E1CBDDE4C005D70A6 /* users-mdiep.response in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -516,19 +548,23 @@
buildActionMask = 2147483647;
files = (
BE0F40E01C8CA13800E3B11A /* repos-Carthage-Carthage-releases-tags-0.15.data in Resources */,
BECB8A9B1CBDDE4B005D70A6 /* users-test.data in Resources */,
BE0F40E21C8CA13800E3B11A /* repos-mdiep-NonExistent-releases-tags-tag.data in Resources */,
BECB8A991CBDDE4B005D70A6 /* users-mdiep.data in Resources */,
BE0F40E31C8CA13800E3B11A /* repos-mdiep-NonExistent-releases-tags-tag.response in Resources */,
BE0F40ED1C8CA1E000E3B11A /* repos-torvalds-linux-releases-tags-v4.4.response in Resources */,
BE1E03631C964F98001296C2 /* repos-Carthage-Carthage-releases.page-1-per_page-30.response in Resources */,
BE1E036F1C9CF849001296C2 /* repos-mdiep-MDPSplitView-releases-tags-1.0.2.data in Resources */,
BEAB175E1C9D0AB8009F8F58 /* repos-mdiep-MDPSplitView-releases-assets-433845.response in Resources */,
BECB8A9C1CBDDE4B005D70A6 /* users-test.response in Resources */,
BE1E03621C964F98001296C2 /* repos-Carthage-Carthage-releases.page-1-per_page-30.data in Resources */,
BE1E03641C964F98001296C2 /* repos-Carthage-Carthage-releases.page-2-per_page-30.data in Resources */,
BEAB175D1C9D0AB8009F8F58 /* repos-mdiep-MDPSplitView-releases-assets-433845.data in Resources */,
BE0F40EC1C8CA1E000E3B11A /* repos-torvalds-linux-releases-tags-v4.4.data in Resources */,
BE1E03711C9CF849001296C2 /* repos-mdiep-MDPSplitView-releases-tags-1.0.2.response in Resources */,
BE0F40E11C8CA13800E3B11A /* repos-Carthage-Carthage-releases-tags-0.15.response in Resources */,
BE1E03651C964F98001296C2 /* repos-Carthage-Carthage-releases.page-2-per_page-30.response in Resources */,
BECB8A9A1CBDDE4B005D70A6 /* users-mdiep.response in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -552,9 +588,11 @@
61377C7C1C8A2B760081FF24 /* Release.swift in Sources */,
BEEE474F1C92623E000FFC21 /* Response.swift in Sources */,
61377C7D1C8A2B760081FF24 /* Repository.swift in Sources */,
BECB8A8F1CBDD91D005D70A6 /* FoundationExtensions.swift in Sources */,
61377C7B1C8A2B760081FF24 /* GitHubError.swift in Sources */,
61377C7E1C8A2B760081FF24 /* Server.swift in Sources */,
61377C7A1C8A2B760081FF24 /* Decodable.swift in Sources */,
BECB8A901CBDD920005D70A6 /* User.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -568,6 +606,7 @@
BEA86F9E1C9F7E1E0049360B /* RepositoryTests.swift in Sources */,
6190818B1C8A2DB7001BE2F8 /* (null) in Sources */,
619081891C8A2DB7001BE2F8 /* GitHubErrorTests.swift in Sources */,
BECB8AA41CBDDF0F005D70A6 /* UserTests.swift in Sources */,
BE1E036C1C9AD87F001296C2 /* ResponseTests.swift in Sources */,
6190818A1C8A2DB7001BE2F8 /* ReleaseTests.swift in Sources */,
);
Expand All @@ -585,6 +624,8 @@
BE88E8321C88D33D0034A112 /* Release.swift in Sources */,
BE88E80D1C88C72B0034A112 /* Client.swift in Sources */,
BE0F40A41C89135D00E3B11A /* Decodable.swift in Sources */,
BECB8A8B1CBDCD17005D70A6 /* User.swift in Sources */,
BECB8A8E1CBDD919005D70A6 /* FoundationExtensions.swift in Sources */,
BE88E8301C88CDAB0034A112 /* Repository.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -597,6 +638,7 @@
BEA86F9D1C9F7E1E0049360B /* RepositoryTests.swift in Sources */,
BEB076601C8A019E00ABD373 /* GitHubErrorTests.swift in Sources */,
BE0F40A01C89098E00E3B11A /* Fixture.swift in Sources */,
BECB8AA31CBDDF0F005D70A6 /* UserTests.swift in Sources */,
BE2B6A6C1C8B854F0080BFEB /* ClientTests.swift in Sources */,
BEA86F9A1C9F7C230049360B /* ServerTests.swift in Sources */,
BE0F40A21C8909EB00E3B11A /* ReleaseTests.swift in Sources */,
Expand Down
9 changes: 9 additions & 0 deletions Tentacle/ArgoExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

import Argo
import Foundation
import Result


Expand All @@ -33,3 +34,11 @@ internal func decode<T: Decodable where T == T.DecodedType>(object: AnyObject) -
internal func toString(number: Int) -> Decoded<String> {
return .Success(number.description)
}

internal func toNSDate(string: String) -> Decoded<NSDate> {
if let date = NSDateFormatter.ISO8601.dateFromString(string) {
return .Success(date)
} else {
return .Failure(.Custom("Date is not ISO8601 formatted"))
}
}
18 changes: 16 additions & 2 deletions Tentacle/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,17 @@ public final class Client {
// https://developer.github.com/v3/repos/releases/#list-releases-for-a-repository
case ReleasesInRepository(owner: String, repository: String)

// https://developer.github.com/v3/users/#get-a-single-user
case User(login: String)

var path: String {
switch self {
case let .ReleaseByTagName(owner, repo, tag):
return "/repos/\(owner)/\(repo)/releases/tags/\(tag)"
case let .ReleasesInRepository(owner, repo):
return "/repos/\(owner)/\(repo)/releases"
case let .User(login):
return "/users/\(login)"
}
}

Expand All @@ -174,6 +179,8 @@ public final class Client {
return owner.hashValue ^ repo.hashValue ^ tag.hashValue
case let .ReleasesInRepository(owner, repo):
return owner.hashValue ^ repo.hashValue
case let .User(login):
return login.hashValue
}
}

Expand Down Expand Up @@ -222,7 +229,7 @@ public final class Client {
/// https://developer.github.com/v3/repos/releases/#list-releases-for-a-repository
public func releasesInRepository(repository: Repository, page: UInt = 1, perPage: UInt = 30) -> SignalProducer<(Response, [Release]), Error> {
precondition(repository.server == server)
return fetchMany(Endpoint.ReleasesInRepository(owner: repository.owner, repository: repository.name), page: page, pageSize: perPage)
return fetchMany(.ReleasesInRepository(owner: repository.owner, repository: repository.name), page: page, pageSize: perPage)
}

/// Fetch the release corresponding to the given tag in the given repository.
Expand All @@ -231,7 +238,7 @@ public final class Client {
/// `.DoesNotExist` error. This is indistinguishable from a nonexistent tag.
public func releaseForTag(tag: String, inRepository repository: Repository) -> SignalProducer<(Response, Release), Error> {
precondition(repository.server == server)
return fetchOne(Endpoint.ReleaseByTagName(owner: repository.owner, repository: repository.name, tag: tag))
return fetchOne(.ReleaseByTagName(owner: repository.owner, repository: repository.name, tag: tag))
}

/// Downloads the indicated release asset to a temporary file, returning the URL to the file on
Expand All @@ -245,6 +252,11 @@ public final class Client {
.mapError(Error.NetworkError)
}

/// Fetch the user with the given login.
public func userWithLogin(login: String) -> SignalProducer<(Response, User), Error> {
return fetchOne(.User(login: login))
}

/// Fetch an endpoint from the API.
private func fetch(endpoint: Endpoint, page: UInt?, pageSize: UInt?) -> SignalProducer<(Response, AnyObject), Error> {
let URL = NSURL(server, endpoint, page: page, pageSize: pageSize)
Expand Down Expand Up @@ -343,6 +355,8 @@ internal func ==(lhs: Client.Endpoint, rhs: Client.Endpoint) -> Bool {
return owner1 == owner2 && repo1 == repo2 && tag1 == tag2
case let (.ReleasesInRepository(owner1, repo1), .ReleasesInRepository(owner2, repo2)):
return owner1 == owner2 && repo1 == repo2
case let (.User(login1), .User(login2)):
return login1 == login2
default:
return false
}
Expand Down
19 changes: 19 additions & 0 deletions Tentacle/FoundationExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// FoundationExtensions.swift
// Tentacle
//
// Created by Matt Diephouse on 4/12/16.
// Copyright © 2016 Matt Diephouse. All rights reserved.
//

import Foundation

extension NSDateFormatter {
@nonobjc public static var ISO8601: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.locale = NSLocale(localeIdentifier:"en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
formatter.timeZone = NSTimeZone(abbreviation:"UTC")
return formatter
}()
}
88 changes: 88 additions & 0 deletions Tentacle/User.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//
// User.swift
// Tentacle
//
// Created by Matt Diephouse on 4/12/16.
// Copyright © 2016 Matt Diephouse. All rights reserved.
//

import Argo
import Curry

/// A User on GitHub.
public struct User: Hashable, CustomStringConvertible {
/// The unique ID of the user.
public let ID: String

/// The user's login/username.
public let login: String

/// The URL of the user's GitHub page.
public let URL: NSURL

/// The URL of the user's avatar.
public let avatarURL: NSURL

/// The user's name if they've set one.
public let name: String?

/// The user's public email address if they've set one.
public let email: String?

/// The URL of the user's website if they've set one
public let websiteURL: NSURL?

/// The user's company if they've set one.
public let company: String?

/// The date that the user joined GitHub.
public let joinedDate: NSDate

public var hashValue: Int {
return ID.hashValue
}

public var description: String {
return login
}

public init(ID: String, login: String, URL: NSURL, avatarURL: NSURL, name: String?, email: String?, websiteURL: NSURL?, company: String?, joinedDate: NSDate) {
self.ID = ID
self.login = login
self.URL = URL
self.avatarURL = avatarURL
self.name = name
self.email = email
self.websiteURL = websiteURL
self.company = company
self.joinedDate = joinedDate
}
}

public func ==(lhs: User, rhs: User) -> Bool {
return lhs.ID == rhs.ID
&& lhs.login == rhs.login
&& lhs.URL == rhs.URL
&& lhs.avatarURL == rhs.avatarURL
&& lhs.name == rhs.name
&& lhs.email == rhs.email
&& lhs.websiteURL == rhs.websiteURL
&& lhs.company == rhs.company
&& lhs.joinedDate == rhs.joinedDate
}

extension User: ResourceType {
public static func decode(j: JSON) -> Decoded<User> {
let f = curry(self.init)
return f
<^> (j <| "id" >>- toString)
<*> j <| "login"
<*> j <| "html_url"
<*> j <| "avatar_url"
<*> j <|? "name"
<*> j <|? "email"
<*> j <|? "blog"
<*> j <|? "company"
<*> (j <| "created_at" >>- toNSDate)
}
}
5 changes: 5 additions & 0 deletions Tests/ClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,9 @@ class ClientTests: XCTestCase {
.single()!
XCTAssertEqual(result.value, Fixture.Release.Asset.MDPSplitView_framework_zip.data)
}

func testUserWithLogin() {
let fixture = Fixture.User.mdiep
ExpectFixtures(client.userWithLogin(fixture.login), fixture)
}
}
Loading

0 comments on commit 8431308

Please sign in to comment.