Skip to content

Commit ce9dd25

Browse files
aleksandr-vinjuanpedromoreno
authored andcommitted
Add Edit gist api (#240)
* Add Get a single gist and Get a specific revision of a gist APIs - Add files property to domain.Gist Signed-off-by: Aleksandr Vinokurov <aleksandr.vin@gmail.com> * Update decoder for Gist, add decoder for GistFile Signed-off-by: Aleksandr Vinokurov <aleksandr.vin@gmail.com> * Add Edit a gist api - Add EditGistFile, EditGistRequest to domain Signed-off-by: Aleksandr Vinokurov <aleksandr.vin@gmail.com> * Move EditGistFile encoder to Encoders object Signed-off-by: Aleksandr Vinokurov <aleksandr.vin@gmail.com>
1 parent 34c5a3c commit ce9dd25

14 files changed

Lines changed: 472 additions & 18 deletions

File tree

docs/src/main/tut/gist.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Github4s supports the [Gist API](https://developer.github.com/v3/gists/). As a r
99
with Github4s, you can:
1010

1111
- [Create a gist](#create-a-gist)
12+
- [Get a single gist or specific revision of a gist](#get-a-gist)
1213

1314
The following examples assume the following imports and token:
1415

@@ -58,6 +59,70 @@ The `result` on the right is the created [Gist][gist-scala].
5859

5960
See [the API doc](https://developer.github.com/v3/gists/#create-a-gist) for full reference.
6061

62+
## Get a single gist or specific revision of a gist
63+
64+
You can create a gist using `getGist`; it takes as arguments:
65+
66+
- the gist id (obtained via [creation of a gist](#create-a-gist), for ex.).
67+
- optional sha of the gist revision.
68+
69+
To get a single gist:
70+
71+
```scala
72+
val singleGist = Github(accessToken).gists.getGist("aa5a315d61ae9438b18d")
73+
74+
singleGist.exec[cats.Id, HttpResponse[String]]() match {
75+
case Left(e) => println(s"Something went wrong: ${e.getMessage}")
76+
case Right(r) => println(r.result)
77+
}
78+
```
79+
80+
Similarly, to get a specific revision of a gist:
81+
82+
```scala
83+
val sepcificRevisionGist = Github(accessToken).gists.getGist("aa5a315d61ae9438b18d", Some("4e481528046a016fc11d6e7d8d623b55ea11e372"))
84+
85+
sepcificRevisionGist.exec[cats.Id, HttpResponse[String]]() match {
86+
case Left(e) => println(s"Something went wrong: ${e.getMessage}")
87+
case Right(r) => println(r.result)
88+
}
89+
```
90+
91+
The `result` on the right is the requested [Gist][gist-scala].
92+
93+
See [the API doc](https://developer.github.com/v3/gists/#get-a-single-gist) for full reference.
94+
95+
## Edit a gist
96+
97+
You can edit a gist using `editGist`; it takes as arguments:
98+
99+
- the gist id (obtained via [creation of a gist](#create-a-gist), for ex.).
100+
- the gist description.
101+
- an association of file names and optional file contents where the contents are wrapped in
102+
[EditGistFile][gist-scala]s, if a new file name required, then it must be provided.
103+
104+
To edit a gist (change description, update content of _token.scala_, rename _gh4s.scala_ and remove _token.class_ file):
105+
106+
```scala
107+
import github4s.free.domain.EditGistFile
108+
val files = Map(
109+
"token.scala" -> Some(EditGistFile("lazy val accessToken = sys.env.get(\"GITHUB4S_ACCESS_TOKEN\")")),
110+
"gh4s.scala" -> Some(EditGistFile("val gh = Github(accessToken)", Some("GH4s.scala"))),
111+
"token.class" -> None
112+
)
113+
114+
val updatedGist = Github(accessToken).gists.editGist("aa5a315d61ae9438b18d", "Updated github4s entry point", files)
115+
116+
updatedGist.exec[cats.Id, HttpResponse[String]]() match {
117+
case Left(e) => println(s"Something went wrong: ${e.getMessage}")
118+
case Right(r) => println(r.result)
119+
}
120+
```
121+
122+
The `result` on the right is the updated [Gist][gist-scala].
123+
124+
See [the API doc](https://developer.github.com/v3/gists/#edit-a-gist) for full reference.
125+
61126
As you can see, a few features of the gist endpoint are missing. As a result, if you'd like to see a
62127
feature supported, feel free to create an issue and/or a pull request!
63128

github4s/jvm/src/test/scala/github4s/unit/ApiSpec.scala

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,10 @@
1717
package github4s.unit
1818

1919
import github4s.api._
20-
import github4s.free.domain.{GistFile, Pagination}
20+
import github4s.free.domain.{EditGistFile, GistFile, Pagination}
2121
import github4s.utils.{DummyGithubUrls, MockGithubApiServer, TestUtilsJVM}
2222
import org.scalatest._
2323
import cats.implicits._
24-
2524
import scalaj.http._
2625
import cats.Id
2726
import github4s.jvm.ImplicitsJVM
@@ -517,6 +516,47 @@ class ApiSpec
517516
}
518517
}
519518

519+
"Gists >> GetGist" should "return the single gist for a valid request" in {
520+
val response =
521+
gists.getGist(validGistId, sha = None, headerUserAgent, accessToken)
522+
response should be('right)
523+
524+
response.toOption map { r
525+
r.statusCode shouldBe okStatusCode
526+
}
527+
}
528+
529+
it should "return the specific revision of gist for a valid request" in {
530+
val response =
531+
gists.getGist(validGistId, sha = Some(validGistSha), headerUserAgent, accessToken)
532+
response should be('right)
533+
534+
response.toOption map { r
535+
r.statusCode shouldBe okStatusCode
536+
}
537+
}
538+
539+
"Gists >> EditGist" should "return the provided gist for a valid request" in {
540+
val response =
541+
gists.editGist(
542+
validGistId,
543+
validGistDescription,
544+
Map(
545+
validGistFilename Some(EditGistFile(validGistFileContent)),
546+
validGistOldFilename Some(
547+
EditGistFile(validGistFileContent, Some(validGistNewFilename))),
548+
validGistDeletedFilename None
549+
),
550+
headerUserAgent,
551+
accessToken
552+
)
553+
response should be('right)
554+
555+
response.toOption map { r
556+
r.statusCode shouldBe okStatusCode
557+
}
558+
}
559+
520560
"GitData >> GetReference" should "return the single reference" in {
521561
val response =
522562
gitData.reference(

github4s/jvm/src/test/scala/github4s/utils/MockGithubApiServer.scala

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,78 @@ trait MockGithubApiServer extends MockServerService with FakeResponses with Test
423423
.withStatusCode(unauthorizedStatusCode)
424424
.withBody(unauthorizedResponse))
425425

426+
//Gists >> get a single gist
427+
428+
mockServer
429+
.when(
430+
request
431+
.withMethod("GET")
432+
.withPath(s"/gists/$validGistId")
433+
.withHeader("Authorization", tokenHeader))
434+
.respond(
435+
response
436+
.withStatusCode(okStatusCode)
437+
.withBody(singleGistValidResponse))
438+
439+
mockServer
440+
.when(
441+
request
442+
.withMethod("GET")
443+
.withPath(s"/gists/$validGistId")
444+
.withHeader(not("Authorization")))
445+
.respond(
446+
response
447+
.withStatusCode(unauthorizedStatusCode)
448+
.withBody(unauthorizedResponse))
449+
450+
//Gists >> get a specific revision of a gist
451+
452+
mockServer
453+
.when(
454+
request
455+
.withMethod("GET")
456+
.withPath(s"/gists/$validGistId/$validGistSha")
457+
.withHeader("Authorization", tokenHeader))
458+
.respond(
459+
response
460+
.withStatusCode(okStatusCode)
461+
.withBody(specificRevisionOfGistValidResponse))
462+
463+
mockServer
464+
.when(
465+
request
466+
.withMethod("GET")
467+
.withPath(s"/gists/$validGistId/$validGistSha")
468+
.withHeader(not("Authorization")))
469+
.respond(
470+
response
471+
.withStatusCode(unauthorizedStatusCode)
472+
.withBody(unauthorizedResponse))
473+
474+
//Gists >> edit a gist
475+
476+
mockServer
477+
.when(
478+
request
479+
.withMethod("PATCH")
480+
.withPath(s"/gists/$validGistId")
481+
.withHeader("Authorization", tokenHeader))
482+
.respond(
483+
response
484+
.withStatusCode(okStatusCode)
485+
.withBody(editedGistValidResponse))
486+
487+
mockServer
488+
.when(
489+
request
490+
.withMethod("PATCH")
491+
.withPath(s"/gists/$validGistId")
492+
.withHeader(not("Authorization")))
493+
.respond(
494+
response
495+
.withStatusCode(unauthorizedStatusCode)
496+
.withBody(unauthorizedResponse))
497+
426498
// Git >> References
427499

428500
mockServer

github4s/shared/src/main/scala/github4s/Decoders.scala

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,18 +197,29 @@ object Decoders {
197197
case PRRStateDismissed.value => PRRStateDismissed
198198
}
199199

200+
implicit val decodeGistFile: Decoder[GistFile] = Decoder.instance { c
201+
for {
202+
content c.downField("content").as[String]
203+
} yield
204+
GistFile(
205+
content = content
206+
)
207+
}
208+
200209
implicit val decodeGist: Decoder[Gist] = Decoder.instance { c
201210
for {
202211
url c.downField("url").as[String]
203212
id c.downField("id").as[String]
204213
description c.downField("description").as[String]
205214
public c.downField("public").as[Boolean]
215+
files c.downField("files").as[Map[String, GistFile]]
206216
} yield
207217
Gist(
208218
url = url,
209219
id = id,
210220
description = description,
211-
public = public
221+
public = public,
222+
files = files
212223
)
213224
}
214225

github4s/shared/src/main/scala/github4s/Encoders.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import github4s.free.domain._
2020
import io.circe._
2121
import io.circe.syntax._
2222
import io.circe.generic.auto._
23+
import io.circe.generic.semiauto.deriveEncoder
2324

2425
object Encoders {
2526

@@ -34,4 +35,9 @@ object Encoders {
3435
}
3536

3637
implicit val encodePrrStatus: Encoder[PullRequestReviewState] = Encoder.encodeString.contramap(_.value)
38+
39+
implicit val encodeEditGistFile: Encoder[EditGistFile] = {
40+
deriveEncoder[EditGistFile].mapJsonObject(_.filter(e =>
41+
!(e._1.equals("filename") && e._2.isNull)))
42+
}
3743
}

github4s/shared/src/main/scala/github4s/GithubAPIs.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,19 @@ class GHGists(accessToken: Option[String] = None)(implicit O: GistOps[GitHub4s])
175175
files: Map[String, GistFile]
176176
): GHIO[GHResponse[Gist]] =
177177
O.newGist(description, public, files, accessToken)
178+
179+
def getGist(
180+
gistId: String,
181+
sha: Option[String] = None
182+
): GHIO[GHResponse[Gist]] =
183+
O.getGist(gistId, sha, accessToken)
184+
185+
def editGist(
186+
gistId: String,
187+
description: String,
188+
files: Map[String, Option[EditGistFile]]
189+
): GHIO[GHResponse[Gist]] =
190+
O.editGist(gistId, description, files, accessToken)
178191
}
179192

180193
class GHIssues(accessToken: Option[String] = None)(implicit O: IssueOps[GitHub4s]) {

github4s/shared/src/main/scala/github4s/api/Gists.scala

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import io.circe.generic.auto._
2222
import io.circe.syntax._
2323
import github4s.GithubResponses.GHResponse
2424
import github4s.free.interpreters.Capture
25-
25+
import github4s.Encoders.encodeEditGistFile
2626
import scala.language.higherKinds
2727

2828
/** Factory to encapsulate calls related to Repositories operations */
@@ -56,4 +56,45 @@ class Gists[C, M[_]](
5656
data = NewGistRequest(description, public, files).asJson.noSpaces
5757
)
5858

59+
/**
60+
* Get a single gist or a specific revision of a gist
61+
*
62+
* @param gistId of the gist
63+
* @param sha optional sha of a revision
64+
* @param headers optional user headers to include in the request
65+
* @param accessToken to identify the authenticated user
66+
*/
67+
def getGist(
68+
gistId: String,
69+
sha: Option[String] = None,
70+
headers: Map[String, String] = Map(),
71+
accessToken: Option[String] = None): M[GHResponse[Gist]] =
72+
httpClient.get[Gist](
73+
accessToken,
74+
("gists" :: gistId :: sha.toList).mkString("/"),
75+
headers
76+
)
77+
78+
/**
79+
* Edit a gist
80+
*
81+
* @param gistId of the gist
82+
* @param files map describing the filenames of the Gist and its contents and/or new filenames
83+
* @param headers optional user headers to include in the request
84+
* @param accessToken to identify the authenticated user
85+
*/
86+
def editGist(
87+
gistId: String,
88+
description: String,
89+
files: Map[String, Option[EditGistFile]],
90+
headers: Map[String, String] = Map(),
91+
accessToken: Option[String] = None): M[GHResponse[Gist]] = {
92+
93+
httpClient.patch[Gist](
94+
accessToken,
95+
s"gists/$gistId",
96+
headers,
97+
data = EditGistRequest(description, files).asJson.noSpaces
98+
)
99+
}
59100
}

0 commit comments

Comments
 (0)