From 9bfb06c2d0286dec76d7690515ca7b4c8a280ef1 Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Tue, 12 Apr 2016 21:53:17 -0400 Subject: [PATCH 1/6] Add a User(login:) endpoint --- Tentacle/Client.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Tentacle/Client.swift b/Tentacle/Client.swift index 6eb2e2a..3abfa80 100644 --- a/Tentacle/Client.swift +++ b/Tentacle/Client.swift @@ -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)" } } @@ -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 } } @@ -343,6 +350,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 } From 9efda343a08aa73661878ab23d1e08c6c493c2ef Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Tue, 12 Apr 2016 21:55:14 -0400 Subject: [PATCH 2/6] Add test fixtures for users --- Tentacle.xcodeproj/project.pbxproj | 24 ++++++++++++++++++++++++ Tests/Fixture.swift | 23 +++++++++++++++++++++++ Tests/Fixtures/users-mdiep.data | 1 + Tests/Fixtures/users-mdiep.response | Bin 0 -> 2069 bytes Tests/Fixtures/users-test.data | 1 + Tests/Fixtures/users-test.response | Bin 0 -> 2068 bytes 6 files changed, 49 insertions(+) create mode 100644 Tests/Fixtures/users-mdiep.data create mode 100644 Tests/Fixtures/users-mdiep.response create mode 100644 Tests/Fixtures/users-test.data create mode 100644 Tests/Fixtures/users-test.response diff --git a/Tentacle.xcodeproj/project.pbxproj b/Tentacle.xcodeproj/project.pbxproj index 17b114c..23d6210 100644 --- a/Tentacle.xcodeproj/project.pbxproj +++ b/Tentacle.xcodeproj/project.pbxproj @@ -79,6 +79,14 @@ 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 */; }; + 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 */; }; 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 */; }; @@ -152,6 +160,10 @@ BEAB17581C9D062D009F8F58 /* repos-mdiep-MDPSplitView-releases-assets-433845.response */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = "repos-mdiep-MDPSplitView-releases-assets-433845.response"; sourceTree = ""; }; BEB0765C1C8A001C00ABD373 /* GitHubError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubError.swift; sourceTree = ""; }; BEB0765E1C8A019A00ABD373 /* GitHubErrorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubErrorTests.swift; sourceTree = ""; }; + BECB8A911CBDDDBB005D70A6 /* users-mdiep.data */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "users-mdiep.data"; sourceTree = ""; }; + BECB8A921CBDDDBB005D70A6 /* users-mdiep.response */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = "users-mdiep.response"; sourceTree = ""; }; + BECB8A931CBDDDBB005D70A6 /* users-test.data */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "users-test.data"; sourceTree = ""; }; + BECB8A941CBDDDBB005D70A6 /* users-test.response */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = "users-test.response"; sourceTree = ""; }; BEEE47411C91B8DF000FFC21 /* ResourceType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResourceType.swift; sourceTree = ""; }; BEEE47441C91BB3A000FFC21 /* ArgoExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArgoExtensions.swift; sourceTree = ""; }; BEEE474D1C92623E000FFC21 /* Response.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = ""; }; @@ -226,6 +238,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 = ""; @@ -488,12 +504,15 @@ 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 */, @@ -501,6 +520,7 @@ 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; }; @@ -516,12 +536,15 @@ 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 */, @@ -529,6 +552,7 @@ 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; }; diff --git a/Tests/Fixture.swift b/Tests/Fixture.swift index 0e1198e..6aa1b3d 100644 --- a/Tests/Fixture.swift +++ b/Tests/Fixture.swift @@ -123,6 +123,8 @@ struct Fixture { Release.Asset.MDPSplitView_framework_zip, Releases.Carthage[0], Releases.Carthage[1], + User.mdiep, + User.test, ] /// Returns the fixture for the given URL, or nil if no such fixture exists. @@ -191,4 +193,25 @@ struct Fixture { self.pageSize = pageSize } } + + struct User: EndpointFixtureType { + static let mdiep = User(.DotCom, "mdiep") + static let test = User(.DotCom, "test") + + let server: Server + let login: String + + let page: UInt? = nil + let pageSize: UInt? = nil + let contentType = Client.APIContentType + + var endpoint: Client.Endpoint { + return .User(login: login) + } + + init(_ server: Server, _ login: String) { + self.server = server + self.login = login + } + } } diff --git a/Tests/Fixtures/users-mdiep.data b/Tests/Fixtures/users-mdiep.data new file mode 100644 index 0000000..87e4230 --- /dev/null +++ b/Tests/Fixtures/users-mdiep.data @@ -0,0 +1 @@ +{"login":"mdiep","id":1302,"avatar_url":"https://avatars.githubusercontent.com/u/1302?v=3","gravatar_id":"","url":"https://api.github.com/users/mdiep","html_url":"https://github.com/mdiep","followers_url":"https://api.github.com/users/mdiep/followers","following_url":"https://api.github.com/users/mdiep/following{/other_user}","gists_url":"https://api.github.com/users/mdiep/gists{/gist_id}","starred_url":"https://api.github.com/users/mdiep/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mdiep/subscriptions","organizations_url":"https://api.github.com/users/mdiep/orgs","repos_url":"https://api.github.com/users/mdiep/repos","events_url":"https://api.github.com/users/mdiep/events{/privacy}","received_events_url":"https://api.github.com/users/mdiep/received_events","type":"User","site_admin":false,"name":"Matt Diephouse","company":null,"blog":"http://matt.diephouse.com","location":"Grand Rapids, MI","email":"matt@diephouse.com","hireable":null,"bio":null,"public_repos":29,"public_gists":0,"followers":241,"following":6,"created_at":"2008-02-27T23:31:47Z","updated_at":"2016-04-13T01:02:42Z"} \ No newline at end of file diff --git a/Tests/Fixtures/users-mdiep.response b/Tests/Fixtures/users-mdiep.response new file mode 100644 index 0000000000000000000000000000000000000000..98eca75e15d4dfeb57ed863813f6639297de6a60 GIT binary patch literal 2069 zcmZuyd2AF_9DZ+hcPJ_{1p&EaYzHg2)18?;x^1btg>v?Cc4?PF9p`wv9lAR+&de4T zujhrg;6)LU+uNWqYCK6uj4{S&1dS&~jUh&jF~%68hWNIJV&nev+xL6#{oeP!_kF+7 zoPz23g76Wf0>Kdd_!C)f2)UkVTUpN5HX_~k)^Vz<511sK;e6Yf2H@6x&oN6TRn;z9 zEQnGxKVWVgEEFxO$XU~~CZ~g@f*b8f9E|KO$LFOcx zYmm7p8Hh5M?BVo+>UlC3letEjBlB^Y6Y(TwGBXKRVX6uzaWf9#My3kW3_Q*;3ooj> zxVJN1uc;ndPoj$os!#5M0S@AT!qg7BImMW zmxTHHzR{yCwTQB#f04JgGrd-BKfp(_o$0O-vT9Rrym{jAk#>&(%sa{yvwJl$FT~hPG{Ek^k&!ft>4gprE-<3=?20qz7W^o zTFl{v_##|~FUE`TVtfe>V;u9LEXVpfJo*+wzg+^KF)H8>W?4m|5eg zG}241>6iI#yI|_&o-}fa9Qv24`T*i9i(I>)FtgLXYwA9qaaGH6Y}e<<_bE(umX}o@ zwVOrL=Vjy}zbEahe#ujq=~;e->8~tldF>0mAhryd^O+S zt`eXfgy@`!427AQ9l2?R|H_*2f|>jl(@~gd2Fj_Wg3o)d&emDBh3fi7Ije|OVHP+g zjXX@4EvlP&HIG^nLYVbU26!yF>?$>o7tT07FEAxqi0)JELIp+Y>39ALJk=*O+%W9q(n@U zkReGZsl|0kAOK@ADHTr&txaM>tToZp8jrR`<6=|1L0FbbNNq%G6{;OS%#Ox0AE6f+ zVOET?DaU0cK}@g$*(f%}Bz8qdreADki?)GUw1TY<$_XL6${;Kfu`0~Awv3t&v+bre z7-s)bSy!gJGWu7JBLetuN=9QZH!)3wTS;}LO8x+!)@=uQmGLNOBFKpo#4rVZ6)~1O zq!uy+Sy_%tgb&prjaMhF$hIOC9-CQxKy^v=mXe?2lM2K2Mrv}Jq$QFlnv2FnWF&Pd zD&@pjV@${;;|)8h+8HiBYXGkB~tH9ca(fpE1-(Yt^C0~`zmd3bU@Y|I7lqinO>R6 zbd#P)9PSW5po51Vi~L`{f24DcuNtYo29=SK8vD|G_~cU=*S38OK^3S5b3iTNfdm@B z5V#f$gI!<`*bfeYC%`k{Fn9^P0$vA4z)|oPcpJO}-UaW0_rV9?LvS2?3_by$fiJ)b z@E!OGoC1Gh!#!{xJOB^D z$KjLkY4|KW44;QDz!%}m@D=zPd;=bVpTf`Km+&k2HT)KS2T#K9;Sca9_%r+k{tADC zzr$1T5BL{6O#zCcf)q_vQFEy{wS-zt<)|&xjnpvp5_OdNg!+>DhWaf~6_^)T66g%% z0$yNqU~6D+;85Vzz*~Wn!5Kj|SQorJ*cwE^+k*RpM}r>(j|IO76+>>Q6dDd434Ikh z8Tv7FD)eXQG##L)&{OFex{hw7Tj=HV8d{-sdJ}ysJxuSS_kxih1xF)zdMu6wdjH=G C6_`-~ literal 0 HcmV?d00001 diff --git a/Tests/Fixtures/users-test.data b/Tests/Fixtures/users-test.data new file mode 100644 index 0000000..a50eabb --- /dev/null +++ b/Tests/Fixtures/users-test.data @@ -0,0 +1 @@ +{"login":"test","id":383316,"avatar_url":"https://avatars.githubusercontent.com/u/383316?v=3","gravatar_id":"","url":"https://api.github.com/users/test","html_url":"https://github.com/test","followers_url":"https://api.github.com/users/test/followers","following_url":"https://api.github.com/users/test/following{/other_user}","gists_url":"https://api.github.com/users/test/gists{/gist_id}","starred_url":"https://api.github.com/users/test/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/test/subscriptions","organizations_url":"https://api.github.com/users/test/orgs","repos_url":"https://api.github.com/users/test/repos","events_url":"https://api.github.com/users/test/events{/privacy}","received_events_url":"https://api.github.com/users/test/received_events","type":"User","site_admin":false,"name":null,"company":null,"blog":null,"location":null,"email":null,"hireable":null,"bio":null,"public_repos":5,"public_gists":0,"followers":7,"following":0,"created_at":"2010-09-01T10:39:12Z","updated_at":"2014-07-05T07:34:22Z"} \ No newline at end of file diff --git a/Tests/Fixtures/users-test.response b/Tests/Fixtures/users-test.response new file mode 100644 index 0000000000000000000000000000000000000000..2ac706f2f13ca293fcdd11d12ae4ba5cd9fc7465 GIT binary patch literal 2068 zcmZuyYit}>6~1?NckHxjGS0J!n|fkTlQiA&&dff&8xz*?HgV!tXEyf6i95{B-d#^J zp4rU9$(8~a+NLiclv0PZ)J@`+P%0W#ZPlU@R0Kj5LWMLyRU!)zM2MFVs!*X&;O^Rn z)MEdfJ?Gwg?svZXoioL%X}F#s`~*^gV2J+e`2shE9M`a|0%sQwBi(ZkaGIk}7$ltM zJiEFIz!PUKFgu!>J8s=Bh*GpNVH`eUPFi;LsN;J6)Uo5WyFPyRCqDTp{OQl!bMJj8 z?tkFHhaUdy=RSY(3y*y9(Jwvr<fERy8^+hBx6C zJ9T?9;=6=}h=*Kn=Ucah`OdNVn=Ne;73TjUcmJTWU+zE0M+$?=P=l+E?6)q?J^$lF zqYPl)xEzZ!iNSLH_9Lj~juE@S=mlndGS!vN?CAc;&W~n$cJ=0N-@T`A@4o&4ybNE5 zugA;r4R{4!i5c92SK-xo4PJ}a;q~}Nya8{-t@tLq3Ezy{NS>kLJ7gule`K_9VC>+b z@jKOzX+^z+FpD?icHDtEd<))!x8iO1R=geGhQpZ0oj8I8EMf^qaSX?C0w-05*=1WE zvOGR#>2}GmDk`&nF{B{fcMPw_58I}p*G3fN5H*bN()0<$*Ecz~sWKZB&oOk5&pVps zR&B@Q7tg6oOM#a)5A_?9hR4guMc#7)q-I!z z%0r7p)@#e>JgqX`uMwaFgy^z?N-DFi(5SS|e|^tl!hGc}qpC8iN~o;)rpLRE&Th4A z3vC^n=d7+(m1(Q`Me;CVc2YaeYZcU;5W=j>6K~#8negIDRE$Rxf*@pA!_rN^gcQG6 zvg(=Jv0Y`@WI2{X zQaPDSi19=cbxBcC63b#T9!-^!T7s0<7K=&QcuMF=i^*6|BHa^@=Awz-bUZ2aW)o75 zXsu3l)eE!p_S#1nMWrw+M%irDVI@IKu!5M5i|Lre?jFdGiy3y(E}`zCY3oPigiz=! z5f+J9Rpw+auT{cqzhNB-v;S4uP=2`X{ZEb!0emSPkxR;12jhSeuDhh*>ey)vIt8E!Pv zu8Epdl*-XqF_DT%U1G8r6S|_37?lu8N%3;K%4||J0!WCndwsJ`PK*_j>1Zr1HOLvY zY}Yc%Wir90PaPUmdP#ed8%b}Rn~t;uTi;?Hf8r|#$Xs$s=UNq24g zgBRi3@Ev#s&cR>6U%_9)tMIq*ckmDJPw>z1FYsOXSNI z7?q*=C`8pLOifcSQLj@!qpncDq}~lQ1zH1{z(4>6oWQZbeSw+4Y~a^a^@2y@gKE jJLnv}m%fuO(o^)k^fY~%o&k*?1?MgJ`hs0B^qKzvv9y;l literal 0 HcmV?d00001 From 1e287a142073528cfd11b432ddf401ccef4d16d0 Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Tue, 12 Apr 2016 21:56:03 -0400 Subject: [PATCH 3/6] Add an ISO8601 date formatter --- Tentacle.xcodeproj/project.pbxproj | 6 ++++++ Tentacle/FoundationExtensions.swift | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 Tentacle/FoundationExtensions.swift diff --git a/Tentacle.xcodeproj/project.pbxproj b/Tentacle.xcodeproj/project.pbxproj index 23d6210..f24ec59 100644 --- a/Tentacle.xcodeproj/project.pbxproj +++ b/Tentacle.xcodeproj/project.pbxproj @@ -79,6 +79,8 @@ 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 */; }; + BECB8A8E1CBDD919005D70A6 /* FoundationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BECB8A8D1CBDD919005D70A6 /* FoundationExtensions.swift */; }; + BECB8A8F1CBDD91D005D70A6 /* FoundationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BECB8A8D1CBDD919005D70A6 /* FoundationExtensions.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 */; }; @@ -160,6 +162,7 @@ BEAB17581C9D062D009F8F58 /* repos-mdiep-MDPSplitView-releases-assets-433845.response */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = "repos-mdiep-MDPSplitView-releases-assets-433845.response"; sourceTree = ""; }; BEB0765C1C8A001C00ABD373 /* GitHubError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubError.swift; sourceTree = ""; }; BEB0765E1C8A019A00ABD373 /* GitHubErrorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubErrorTests.swift; sourceTree = ""; }; + BECB8A8D1CBDD919005D70A6 /* FoundationExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationExtensions.swift; sourceTree = ""; }; BECB8A911CBDDDBB005D70A6 /* users-mdiep.data */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "users-mdiep.data"; sourceTree = ""; }; BECB8A921CBDDDBB005D70A6 /* users-mdiep.response */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = "users-mdiep.response"; sourceTree = ""; }; BECB8A931CBDDDBB005D70A6 /* users-test.data */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "users-test.data"; sourceTree = ""; }; @@ -277,6 +280,7 @@ BEEE47441C91BB3A000FFC21 /* ArgoExtensions.swift */, BE88E80C1C88C72B0034A112 /* Client.swift */, BE0F40A31C89135D00E3B11A /* Decodable.swift */, + BECB8A8D1CBDD919005D70A6 /* FoundationExtensions.swift */, BEB0765C1C8A001C00ABD373 /* GitHubError.swift */, BE88E8311C88D33D0034A112 /* Release.swift */, BE88E82F1C88CDAB0034A112 /* Repository.swift */, @@ -576,6 +580,7 @@ 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 */, @@ -609,6 +614,7 @@ BE88E8321C88D33D0034A112 /* Release.swift in Sources */, BE88E80D1C88C72B0034A112 /* Client.swift in Sources */, BE0F40A41C89135D00E3B11A /* Decodable.swift in Sources */, + BECB8A8E1CBDD919005D70A6 /* FoundationExtensions.swift in Sources */, BE88E8301C88CDAB0034A112 /* Repository.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Tentacle/FoundationExtensions.swift b/Tentacle/FoundationExtensions.swift new file mode 100644 index 0000000..cff7e9d --- /dev/null +++ b/Tentacle/FoundationExtensions.swift @@ -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 + }() +} From 73e1a975e4a389df2ae0f7b63a683f12a2a93127 Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Tue, 12 Apr 2016 21:57:13 -0400 Subject: [PATCH 4/6] Add a helper to parse dates with Argo --- Tentacle/ArgoExtensions.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Tentacle/ArgoExtensions.swift b/Tentacle/ArgoExtensions.swift index ccbd2fb..450c477 100644 --- a/Tentacle/ArgoExtensions.swift +++ b/Tentacle/ArgoExtensions.swift @@ -7,6 +7,7 @@ // import Argo +import Foundation import Result @@ -33,3 +34,11 @@ internal func decode(object: AnyObject) - internal func toString(number: Int) -> Decoded { return .Success(number.description) } + +internal func toNSDate(string: String) -> Decoded { + if let date = NSDateFormatter.ISO8601.dateFromString(string) { + return .Success(date) + } else { + return .Failure(.Custom("Date is not ISO8601 formatted")) + } +} From 5150f1fdb546f6a6c7eee97b63f2322061071a79 Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Tue, 12 Apr 2016 22:06:54 -0400 Subject: [PATCH 5/6] Add a User struct --- Tentacle.xcodeproj/project.pbxproj | 12 ++++ Tentacle/User.swift | 88 ++++++++++++++++++++++++++++++ Tests/UserTests.swift | 43 +++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 Tentacle/User.swift create mode 100644 Tests/UserTests.swift diff --git a/Tentacle.xcodeproj/project.pbxproj b/Tentacle.xcodeproj/project.pbxproj index f24ec59..6ec66c2 100644 --- a/Tentacle.xcodeproj/project.pbxproj +++ b/Tentacle.xcodeproj/project.pbxproj @@ -79,8 +79,10 @@ 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 */; }; @@ -89,6 +91,8 @@ 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 */; }; @@ -162,11 +166,13 @@ BEAB17581C9D062D009F8F58 /* repos-mdiep-MDPSplitView-releases-assets-433845.response */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = "repos-mdiep-MDPSplitView-releases-assets-433845.response"; sourceTree = ""; }; BEB0765C1C8A001C00ABD373 /* GitHubError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubError.swift; sourceTree = ""; }; BEB0765E1C8A019A00ABD373 /* GitHubErrorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubErrorTests.swift; sourceTree = ""; }; + BECB8A8A1CBDCD17005D70A6 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; BECB8A8D1CBDD919005D70A6 /* FoundationExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationExtensions.swift; sourceTree = ""; }; BECB8A911CBDDDBB005D70A6 /* users-mdiep.data */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "users-mdiep.data"; sourceTree = ""; }; BECB8A921CBDDDBB005D70A6 /* users-mdiep.response */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = "users-mdiep.response"; sourceTree = ""; }; BECB8A931CBDDDBB005D70A6 /* users-test.data */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "users-test.data"; sourceTree = ""; }; BECB8A941CBDDDBB005D70A6 /* users-test.response */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = "users-test.response"; sourceTree = ""; }; + BECB8AA11CBDDF0F005D70A6 /* UserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserTests.swift; sourceTree = ""; }; BEEE47411C91B8DF000FFC21 /* ResourceType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResourceType.swift; sourceTree = ""; }; BEEE47441C91BB3A000FFC21 /* ArgoExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArgoExtensions.swift; sourceTree = ""; }; BEEE474D1C92623E000FFC21 /* Response.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = ""; }; @@ -287,6 +293,7 @@ BEEE47411C91B8DF000FFC21 /* ResourceType.swift */, BEEE474D1C92623E000FFC21 /* Response.swift */, BE88E82C1C88C94D0034A112 /* Server.swift */, + BECB8A8A1CBDCD17005D70A6 /* User.swift */, BE88E7F71C88C6B30034A112 /* Info.plist */, ); path = Tentacle; @@ -303,6 +310,7 @@ BEA86F9C1C9F7E1E0049360B /* RepositoryTests.swift */, BE1E036A1C9AD87F001296C2 /* ResponseTests.swift */, BEA86F991C9F7C230049360B /* ServerTests.swift */, + BECB8AA11CBDDF0F005D70A6 /* UserTests.swift */, BE88E8031C88C6B30034A112 /* Info.plist */, ); path = Tests; @@ -584,6 +592,7 @@ 61377C7B1C8A2B760081FF24 /* GitHubError.swift in Sources */, 61377C7E1C8A2B760081FF24 /* Server.swift in Sources */, 61377C7A1C8A2B760081FF24 /* Decodable.swift in Sources */, + BECB8A901CBDD920005D70A6 /* User.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -597,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 */, ); @@ -614,6 +624,7 @@ 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 */, ); @@ -627,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 */, diff --git a/Tentacle/User.swift b/Tentacle/User.swift new file mode 100644 index 0000000..de038be --- /dev/null +++ b/Tentacle/User.swift @@ -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 { + 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) + } +} diff --git a/Tests/UserTests.swift b/Tests/UserTests.swift new file mode 100644 index 0000000..8044fb9 --- /dev/null +++ b/Tests/UserTests.swift @@ -0,0 +1,43 @@ +// +// UserTests.swift +// Tentacle +// +// Created by Matt Diephouse on 4/12/16. +// Copyright © 2016 Matt Diephouse. All rights reserved. +// + +import Argo +@testable import Tentacle +import XCTest + +class UserTests: XCTestCase { + func testDecodeMdiep() { + let expected = User( + ID: "1302", + login: "mdiep", + URL: NSURL(string: "https://github.com/mdiep")!, + avatarURL: NSURL(string: "https://avatars.githubusercontent.com/u/1302?v=3")!, + name: "Matt Diephouse", + email: "matt@diephouse.com", + websiteURL: NSURL(string: "http://matt.diephouse.com"), + company: nil, + joinedDate: NSDate(timeIntervalSince1970: 1204155107) + ) + XCTAssertEqual(Fixture.User.mdiep.decode(), expected) + } + + func testDecodeTest() { + let expected = User( + ID: "383316", + login: "test", + URL: NSURL(string: "https://github.com/test")!, + avatarURL: NSURL(string: "https://avatars.githubusercontent.com/u/383316?v=3")!, + name: nil, + email: nil, + websiteURL: nil, + company: nil, + joinedDate: NSDate(timeIntervalSince1970: 1283337552) + ) + XCTAssertEqual(Fixture.User.test.decode(), expected) + } +} From 82950a5f169328ec85c636adae7db133e5c6f51a Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Tue, 12 Apr 2016 22:15:53 -0400 Subject: [PATCH 6/6] Add a method to fetch a user by login --- Tentacle/Client.swift | 9 +++++++-- Tests/ClientTests.swift | 5 +++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Tentacle/Client.swift b/Tentacle/Client.swift index 3abfa80..2c9712b 100644 --- a/Tentacle/Client.swift +++ b/Tentacle/Client.swift @@ -229,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. @@ -238,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 @@ -252,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) diff --git a/Tests/ClientTests.swift b/Tests/ClientTests.swift index e33ef9c..4210a6c 100644 --- a/Tests/ClientTests.swift +++ b/Tests/ClientTests.swift @@ -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) + } }