Skip to content

Commit 50123b1

Browse files
author
Guillaume "Liam" Petiot
authored
Contributions.of_json returns a result type (#20)
1 parent 06b50ca commit 50123b1

File tree

5 files changed

+69
-56
lines changed

5 files changed

+69
-56
lines changed

Diff for: CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
+ `Graphql.exec` now takes a `request`
1414
+ `Contributions.fetch` has been replaced by `Contributions.request` that builds a `request`
1515
- Add a `~user:User.t` parameter to `Contributions.request` and `Contributions.of_json` (#14, @gpetiot)
16+
- `Contributions.of_json` now returns a result type (#20, @gpetiot)
1617

1718
## 0.2.0
1819

Diff for: bin/main.ml

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ let mtime path =
3434
let get_token () = Token.load (home / ".github" / "github-activity-token")
3535

3636
let show ~from ~user json =
37-
let contribs = Contributions.of_json ~from ~user json in
37+
let* contribs = Contributions.of_json ~from ~user json in
3838
if Contributions.is_empty contribs then
3939
Fmt.epr "(no activity found since %s)@." from
4040
else Fmt.pr "%a@." Contributions.pp contribs

Diff for: lib/contributions.ml

+61-51
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module Json = Yojson.Safe
22

3+
let ( let* ) = Result.bind
34
let ( / ) a b = Json.Util.member b a
45

56
let query user =
@@ -66,8 +67,8 @@ module Datetime = struct
6667
type t = string
6768

6869
let parse = function
69-
| `String s -> s
70-
| x -> Fmt.failwith "Invalid Datatime %a" Json.pp x
70+
| `String s -> Ok s
71+
| x -> Error (`Msg (Fmt.str "Invalid Datatime %a" Json.pp x))
7172
end
7273

7374
module Repo_map = Map.Make (String)
@@ -83,75 +84,84 @@ type item = {
8384

8485
type t = { username : string; activity : item list Repo_map.t }
8586

87+
let to_string x =
88+
Json.Util.to_string_option x
89+
|> Option.to_result ~none:(`Msg (Fmt.str "Expected string, got %a" Json.pp x))
90+
91+
let combine lx =
92+
List.fold_left
93+
(fun acc x ->
94+
let* acc = acc in
95+
let* x = x in
96+
Ok (x :: acc))
97+
(Ok []) lx
98+
8699
let read_issues json =
87100
Json.Util.to_list (json / "nodes")
88101
|> List.filter (( <> ) `Null)
89-
|> List.map @@ fun node ->
90-
let date = Datetime.parse (node / "occurredAt") in
91-
let x = node / "issue" in
92-
let url = x / "url" |> Json.Util.to_string in
93-
let title = x / "title" |> Json.Util.to_string in
94-
let body = x / "body" |> Json.Util.to_string in
95-
let repo = x / "repository" / "nameWithOwner" |> Json.Util.to_string in
96-
{ kind = `Issue; date; url; title; body; repo }
102+
|> List.map (fun node ->
103+
let* date = Datetime.parse (node / "occurredAt") in
104+
let x = node / "issue" in
105+
let* url = x / "url" |> to_string in
106+
let* title = x / "title" |> to_string in
107+
let* body = x / "body" |> to_string in
108+
let* repo = x / "repository" / "nameWithOwner" |> to_string in
109+
Ok { kind = `Issue; date; url; title; body; repo })
110+
|> combine
97111

98112
let read_prs json =
99113
Json.Util.to_list (json / "nodes")
100114
|> List.filter (( <> ) `Null)
101-
|> List.map @@ fun node ->
102-
let date = Datetime.parse (node / "occurredAt") in
103-
let pr = node / "pullRequest" in
104-
let url = pr / "url" |> Json.Util.to_string in
105-
let title = pr / "title" |> Json.Util.to_string in
106-
let body = pr / "body" |> Json.Util.to_string in
107-
let repo = pr / "repository" / "nameWithOwner" |> Json.Util.to_string in
108-
{ kind = `PR; date; url; title; body; repo }
115+
|> List.map (fun node ->
116+
let* date = node / "occurredAt" |> Datetime.parse in
117+
let pr = node / "pullRequest" in
118+
let* url = pr / "url" |> to_string in
119+
let* title = pr / "title" |> to_string in
120+
let* body = pr / "body" |> to_string in
121+
let* repo = pr / "repository" / "nameWithOwner" |> to_string in
122+
Ok { kind = `PR; date; url; title; body; repo })
123+
|> combine
109124

110125
let read_reviews json =
111126
Json.Util.to_list (json / "nodes")
112127
|> List.filter (( <> ) `Null)
113-
|> List.map @@ fun node ->
114-
let date = Datetime.parse (node / "occurredAt") in
115-
let review = node / "pullRequestReview" in
116-
let state = review / "state" |> Json.Util.to_string in
117-
let url = review / "url" |> Json.Util.to_string in
118-
let pr = review / "pullRequest" in
119-
let title = pr / "title" |> Json.Util.to_string in
120-
let body = review / "body" |> Json.Util.to_string in
121-
let repo =
122-
review / "repository" / "nameWithOwner" |> Json.Util.to_string
123-
in
124-
{ kind = `Review state; date; url; title; body; repo }
128+
|> List.map (fun node ->
129+
let* date = node / "occurredAt" |> Datetime.parse in
130+
let review = node / "pullRequestReview" in
131+
let* state = review / "state" |> to_string in
132+
let* url = review / "url" |> to_string in
133+
let pr = review / "pullRequest" in
134+
let* title = pr / "title" |> to_string in
135+
let* body = review / "body" |> to_string in
136+
let* repo = review / "repository" / "nameWithOwner" |> to_string in
137+
Ok { kind = `Review state; date; url; title; body; repo })
138+
|> combine
125139

126140
let read_repos json =
127141
Json.Util.to_list (json / "nodes")
128142
|> List.filter (( <> ) `Null)
129-
|> List.map @@ fun node ->
130-
let date = Datetime.parse (node / "occurredAt") in
131-
let repo = node / "repository" in
132-
let url = repo / "url" |> Json.Util.to_string in
133-
let repo = repo / "nameWithOwner" |> Json.Util.to_string in
134-
{
135-
kind = `New_repo;
136-
date;
137-
url;
138-
title = "Created new repository";
139-
body = "";
140-
repo;
141-
}
143+
|> List.map (fun node ->
144+
let* date = node / "occurredAt" |> Datetime.parse in
145+
let repo = node / "repository" in
146+
let* url = repo / "url" |> to_string in
147+
let* repo = repo / "nameWithOwner" |> to_string in
148+
let title = "Created new repository" in
149+
Ok { kind = `New_repo; date; url; title; body = ""; repo })
150+
|> combine
142151

143152
let of_json ~from ~user json =
144-
let username =
145-
json / "data" / User.response_field user / "login" |> Json.Util.to_string
153+
let* username =
154+
json / "data" / User.response_field user / "login" |> to_string
146155
in
147156
let contribs =
148157
json / "data" / User.response_field user / "contributionsCollection"
149158
in
150-
let items =
151-
read_issues (contribs / "issueContributions")
152-
@ read_prs (contribs / "pullRequestContributions")
153-
@ read_reviews (contribs / "pullRequestReviewContributions")
154-
@ read_repos (contribs / "repositoryContributions")
159+
let* items =
160+
let* issues = read_issues (contribs / "issueContributions") in
161+
let* prs = read_prs (contribs / "pullRequestContributions") in
162+
let* reviews = read_reviews (contribs / "pullRequestReviewContributions") in
163+
let* repos = read_repos (contribs / "repositoryContributions") in
164+
Ok (issues @ prs @ reviews @ repos)
155165
in
156166
let activity =
157167
(* GitHub seems to ignore the time part, so do the filtering here. *)
@@ -165,7 +175,7 @@ let of_json ~from ~user json =
165175
Repo_map.add item.repo (item :: items) acc)
166176
Repo_map.empty
167177
in
168-
{ username; activity }
178+
Ok { username; activity }
169179

170180
let id url =
171181
match Astring.String.cut ~sep:"/" ~rev:true url with

Diff for: lib/contributions.mli

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ type t = { username : string; activity : item list Repo_map.t }
1818
val request :
1919
period:string * string -> user:User.t -> token:Token.t -> Graphql.request
2020

21-
val of_json : from:string -> user:User.t -> Yojson.Safe.t -> t
21+
val of_json :
22+
from:string -> user:User.t -> Yojson.Safe.t -> (t, [ `Msg of string ]) result
2223
(** We pass [from] again here so we can filter out anything that GitHub included by accident. *)
2324

2425
val is_empty : t -> bool

Diff for: test/lib/test_contributions.ml

+4-3
Original file line numberDiff line numberDiff line change
@@ -406,19 +406,20 @@ let test_of_json =
406406
let name = Printf.sprintf "of_json: %s" name in
407407
let test_fun () =
408408
let actual = Contributions.of_json ~from ~user json in
409-
Alcotest.(check Testable.contributions) name expected actual
409+
Alcotest.(check (Alcotest_ext.or_msg Testable.contributions))
410+
name expected actual
410411
in
411412
(name, `Quick, test_fun)
412413
in
413414
[
414415
(let user = User.Viewer in
415416
make_test "no token" ~from:"" ~user
416417
(activity_example_json ~user)
417-
~expected:(contributions_example ~user));
418+
~expected:(Ok (contributions_example ~user)));
418419
(let user = User.User "gpetiot" in
419420
make_test "no token" ~from:"" ~user
420421
(activity_example_json ~user)
421-
~expected:(contributions_example ~user));
422+
~expected:(Ok (contributions_example ~user)));
422423
]
423424

424425
let test_is_empty =

0 commit comments

Comments
 (0)