package dune-release

  1. Overview
  2. Docs

Source file github_v3_api.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
open Bos_setup

let is_handled errors (affix, _) =
  List.exists
    (fun error ->
      match Json.string_field ~field:"message" error with
      | Ok x -> String.is_prefix ~affix x
      | Error _ -> false)
    errors

let pp_break_then_string ?(pre = "") ?(post = "") fs = function
  | Ok x -> Fmt.fmt "@;%s%S%s" fs pre x post
  | Error _ -> Fmt.nop fs ()

let pp_errors fs errors =
  List.iter
    (fun error ->
      let message = Json.string_field ~field:"message" error in
      let code = Json.string_field ~field:"code" error in
      pp_break_then_string ~pre:"- Error message: " fs message;
      pp_break_then_string ~pre:"- Code: " fs code)
    errors

let handle_errors json ~try_ ~on_ok ~default_msg ~handled_errors =
  match try_ json with
  | Ok x -> Ok (on_ok x)
  | Error err -> (
      let errors =
        match Json.list_field ~field:"errors" json with
        | Ok errors -> errors
        | Error _ -> []
      in
      match List.find_opt (is_handled errors) handled_errors with
      | Some (_, ret) -> Ok ret
      | None -> (
          match Json.string_field ~field:"message" json with
          | Ok message ->
              let documentation_url =
                Json.string_field ~field:"documentation_url" json
              in
              R.error_msgf
                "@[<v 2>Github API error:@ %s@;Github API returned: %S%a%a@]"
                default_msg message
                (pp_break_then_string ~pre:"See the documentation "
                   ~post:" that might help you resolve this error.")
                documentation_url pp_errors errors
          | Error _ -> Error err))

let with_auth ~token Curl.{ url; meth; args } =
  let auth_header =
    Curl_option.Header (Printf.sprintf "Authorization: token %s" token)
  in
  Curl.{ url; meth; args = auth_header :: args }

module Release = struct
  module Request = struct
    let get ~version ~user ~repo =
      let url =
        strf "https://api.github.com/repos/%s/%s/releases/tags/%s" user repo
          version
      in
      let args =
        let open Curl_option in
        [ Location; Silent; Show_error; Config `Stdin; Dump_header `Ignore ]
      in
      Curl.{ url; meth = `GET; args }

    let create ~version ~tag ~msg ~user ~repo ~draft =
      let json =
        Yojson.Basic.to_string
          (`Assoc
            [
              ("tag_name", `String tag);
              ("name", `String version);
              ("body", `String msg);
              ("draft", `Bool draft);
            ])
      in
      let url = strf "https://api.github.com/repos/%s/%s/releases" user repo in
      let args =
        let open Curl_option in
        [
          Location;
          Silent;
          Show_error;
          Config `Stdin;
          Dump_header `Ignore;
          Data (`Data json);
        ]
      in
      Curl.{ url; meth = `POST; args }

    let undraft ~owner ~repo ~release_id =
      let json = Yojson.Basic.to_string (`Assoc [ ("draft", `Bool false) ]) in
      let url =
        strf "https://api.github.com/repos/%s/%s/releases/%i" owner repo
          release_id
      in
      let args =
        let open Curl_option in
        [
          Location;
          Silent;
          Show_error;
          Config `Stdin;
          Dump_header `Ignore;
          Data (`Data json);
        ]
      in
      Curl.{ url; meth = `PATCH; args }
  end

  module Response = struct
    let same_name name json =
      match Json.string_field ~field:"name" json with
      | Ok name' -> String.equal name name'
      | Error _ -> false

    let browser_download_url ~name json =
      handle_errors json
        ~try_:(fun json ->
          Json.list_field ~field:"assets" json >>= fun assets ->
          match List.find_opt (same_name name) assets with
          | Some json -> Json.string_field ~field:"browser_download_url" json
          | None -> R.error_msg "No asset matches the release")
        ~on_ok:(fun x -> x)
        ~default_msg:
          (Format.sprintf
             "Could not retrieve archive download URL for asset %s from \
              response"
             name)
        ~handled_errors:[]

    let release_id json =
      handle_errors json
        ~try_:(Json.int_field ~field:"id")
        ~on_ok:(fun x -> x)
        ~default_msg:"Could not retrieve release ID from response"
        ~handled_errors:[]
  end
end

module Archive = struct
  module Request = struct
    let upload ~archive ~user ~repo ~release_id =
      let url =
        strf "https://uploads.github.com/repos/%s/%s/releases/%d/assets?name=%s"
          user repo release_id (Fpath.filename archive)
      in
      let args =
        let open Curl_option in
        [
          Location;
          Silent;
          Show_error;
          Config `Stdin;
          Dump_header `Ignore;
          Header "Content-Type:application/x-tar";
          Data_binary (`File (Fpath.to_string archive));
        ]
      in
      Curl.{ url; meth = `POST; args }
  end

  module Response = struct
    let browser_download_url json =
      handle_errors json
        ~try_:(Json.string_field ~field:"browser_download_url")
        ~on_ok:(fun x -> x)
        ~default_msg:"Could not retrieve archive download URL from response"
        ~handled_errors:[]

    let name json =
      handle_errors json
        ~try_:(Json.string_field ~field:"name")
        ~on_ok:(fun x -> x)
        ~default_msg:"Could not retrieve asset name from response"
        ~handled_errors:[]
  end
end

module Pull_request = struct
  module Request = struct
    let open_ ~title ~fork_owner ~branch ~body ~opam_repo ~draft =
      let base, repo = opam_repo in
      let url = strf "https://api.github.com/repos/%s/%s/pulls" base repo in
      let json =
        Yojson.Basic.to_string
          (`Assoc
            [
              ("title", `String title);
              ("base", `String "master");
              ("body", `String body);
              ("head", `String (strf "%s:%s" fork_owner branch));
              ("draft", `Bool draft);
            ])
      in
      let args =
        let open Curl_option in
        [
          Silent;
          Show_error;
          Config `Stdin;
          Dump_header `Ignore;
          Data (`Data json);
        ]
      in
      Curl.{ url; meth = `POST; args }
  end

  module Response = struct
    let html_url json =
      handle_errors json
        ~try_:(Json.string_field ~field:"html_url")
        ~on_ok:(fun x -> `Url x)
        ~default_msg:"Could not retrieve pull request URL from response"
        ~handled_errors:[ ("A pull request already exists", `Already_exists) ]

    let number json =
      handle_errors json
        ~try_:(Json.int_field ~field:"number")
        ~on_ok:(fun x -> x)
        ~default_msg:"Could not retrieve pull request number from response"
        ~handled_errors:[]
  end
end
OCaml

Innovation. Community. Security.