|
1 | 1 | local M = {}
|
2 |
| -local curl = require("plenary.curl") |
3 |
| -local job = require("plenary.job") |
4 | 2 | local notify = require("forem-nvim.notify")
|
5 | 3 | local article = require("forem-nvim.article")
|
6 | 4 |
|
|
10 | 8 |
|
11 | 9 | local BASE_URL = "https://dev.to/api"
|
12 | 10 |
|
13 |
| -local function handle_async_error(response) |
14 |
| - notify.error("Error: " .. tostring(response.body.error)) |
15 |
| -end |
| 11 | +---@class Response |
| 12 | +---@field status number |
| 13 | +---@field body table |
| 14 | + |
| 15 | +---@alias Method "GET" | "POST" | "PUT" | "DELETE" |
16 | 16 |
|
| 17 | +---@param response Response |
| 18 | +---@param on_success fun(body: table) |
17 | 19 | function M.handle_error(response, on_success)
|
18 | 20 | local start_status = string.sub(response.status, 1, 2)
|
19 | 21 |
|
20 | 22 | if start_status == "20" then
|
21 | 23 | on_success(response.body)
|
22 | 24 | else
|
23 |
| - notify.error("Error: " .. tostring(response.body.error)) |
| 25 | + local error = response |
| 26 | + if response.body then |
| 27 | + error = response.body |
| 28 | + if response.body.error then |
| 29 | + error = response.body.error |
| 30 | + end |
| 31 | + end |
| 32 | + notify.error("Error: " .. tostring(error)) |
| 33 | + end |
| 34 | +end |
| 35 | + |
| 36 | +---@param out vim.SystemCompleted |
| 37 | +---@return Response |
| 38 | +local function system_completed_to_response(out) |
| 39 | + local status_code = nil |
| 40 | + local response_body = nil |
| 41 | + |
| 42 | + if out.stdout then |
| 43 | + -- Extract status code from headers |
| 44 | + status_code = out.stdout:match("HTTP/%d%.?%d?%s+(%d+)") |
| 45 | + |
| 46 | + -- Extract the body from the response (everything after the headers) |
| 47 | + response_body = out.stdout:match("\n\n(.*)") |
| 48 | + end |
| 49 | + |
| 50 | + local body = nil |
| 51 | + -- Only attempt to decode if we have a body |
| 52 | + if response_body and response_body ~= "" then |
| 53 | + -- Protect against JSON decode errors |
| 54 | + body = vim.fn.json_decode(response_body) |
24 | 55 | end
|
| 56 | + |
| 57 | + return { |
| 58 | + status = status_code, |
| 59 | + body = body |
| 60 | + } |
25 | 61 | end
|
26 | 62 |
|
27 |
| -local function request(request_function, endpoint, options) |
| 63 | +---@param method Method |
| 64 | +---@param endpoint string |
| 65 | +---@param options table |
| 66 | +---@param on_exit fun(response: Response)? |
| 67 | +---@return vim.SystemObj |
| 68 | +local function curl(method, endpoint, options, on_exit) |
| 69 | + local headers = options.headers or {} |
| 70 | + local request_body = options.body and { "-d", vim.fn.json_encode(options.body) } or {} |
| 71 | + |
| 72 | + local cmd = vim.iter({ |
| 73 | + "curl", |
| 74 | + "-X", |
| 75 | + method, |
| 76 | + "-i", -- Include headers in the output |
| 77 | + vim.tbl_map(function(header) |
| 78 | + return { "-H", header } |
| 79 | + end, headers), |
| 80 | + request_body, |
| 81 | + BASE_URL .. endpoint |
| 82 | + }):flatten(2):totable() |
| 83 | + |
| 84 | + return vim.system(cmd, { text = true }, function(out) |
| 85 | + vim.schedule(function() |
| 86 | + local response = system_completed_to_response(out) |
| 87 | + if on_exit then |
| 88 | + on_exit(response) |
| 89 | + end |
| 90 | + end) |
| 91 | + end) |
| 92 | +end |
| 93 | + |
| 94 | +---@param method Method |
| 95 | +---@param endpoint string |
| 96 | +---@param options table |
| 97 | +---@return Response |
| 98 | +local function request(method, endpoint, options) |
28 | 99 | local parameters = vim.tbl_extend(
|
29 | 100 | "force",
|
30 | 101 | {
|
31 |
| - url = BASE_URL .. endpoint, |
32 | 102 | headers = {
|
33 |
| - ["api-key"] = M.key(), |
34 |
| - content_type = "application/json", |
35 |
| - accept = "application/vnd.forem.api-v1+json" |
| 103 | + "api-key: " .. M.key(), |
| 104 | + "Content-Type: application/json", |
| 105 | + "Accept: application/vnd.forem.api-v1+json" |
36 | 106 | }
|
37 | 107 | },
|
38 | 108 | options
|
39 | 109 | )
|
40 |
| - local response = request_function(parameters) |
41 |
| - if response.body then |
42 |
| - return vim.tbl_extend( |
43 |
| - "force", |
44 |
| - response, |
45 |
| - { body = vim.fn.json_decode(response.body) } |
46 |
| - ) |
47 |
| - end |
| 110 | + |
| 111 | + local out = curl(method, endpoint, parameters, nil):wait() |
| 112 | + local response = system_completed_to_response(out) |
| 113 | + |
48 | 114 | return response
|
49 | 115 | end
|
50 | 116 |
|
51 |
| -local function request_async(method, endpoint, options, on_success, on_error) |
52 |
| - -- TODO: Check if this could be done with `vim.system` |
53 |
| - return job:new({ |
54 |
| - command = "curl", |
55 |
| - args = { |
56 |
| - "-X", |
57 |
| - method, |
58 |
| - "-H", |
59 |
| - "Content-Type: application/json", |
60 |
| - "-H", |
61 |
| - "Accept: application/vnd.forem.api-v1+json", |
62 |
| - "-H", |
63 |
| - "api-key: " .. tostring(M.key()), |
64 |
| - "-d", |
65 |
| - vim.fn.json_encode(options), |
66 |
| - BASE_URL .. endpoint |
67 |
| - }, |
68 |
| - on_exit = function(this_job, code) |
69 |
| - vim.schedule(function() |
70 |
| - local result = table.concat( |
71 |
| - this_job:result(), |
72 |
| - "\n" |
73 |
| - ) |
74 |
| - local response = vim.fn.json_decode(result) |
75 |
| - if code == 0 then |
76 |
| - on_success(response) |
77 |
| - return |
78 |
| - end |
79 |
| - handle_async_error(response) |
80 |
| - if on_error then |
81 |
| - on_error(response) |
82 |
| - end |
83 |
| - end) |
84 |
| - end |
85 |
| - }):start() |
| 117 | +---@param method Method |
| 118 | +---@param endpoint string |
| 119 | +---@param on_success fun(body: table) |
| 120 | +local function request_async(method, endpoint, on_success) |
| 121 | + local options = { |
| 122 | + headers = { |
| 123 | + "Api-Key: " .. M.key() |
| 124 | + } |
| 125 | + } |
| 126 | + |
| 127 | + curl(method, endpoint, options, function(response) |
| 128 | + M.handle_error( |
| 129 | + response, |
| 130 | + on_success |
| 131 | + ) |
| 132 | + end |
| 133 | + ) |
86 | 134 | end
|
87 | 135 |
|
88 |
| -local function get(endpoint, on_success, on_error) |
89 |
| - return request_async( |
| 136 | +---@param endpoint string |
| 137 | +---@param on_success fun(body: table) |
| 138 | +local function get(endpoint, on_success) |
| 139 | + request_async( |
90 | 140 | "GET",
|
91 | 141 | endpoint,
|
92 |
| - {}, |
93 |
| - on_success, |
94 |
| - on_error |
| 142 | + on_success |
95 | 143 | )
|
96 | 144 | end
|
97 | 145 |
|
| 146 | +---@param endpoint string |
| 147 | +---@param body table |
| 148 | +---@return Response |
98 | 149 | local function put(endpoint, body)
|
99 |
| - return request(curl.put, endpoint, { body = body }) |
| 150 | + return request("PUT", endpoint, { body = body }) |
100 | 151 | end
|
101 | 152 |
|
| 153 | +---@param endpoint string |
| 154 | +---@param body table |
| 155 | +---@return Response |
102 | 156 | local function post(endpoint, body)
|
103 |
| - return request(curl.post, endpoint, { body = body }) |
| 157 | + return request("POST", endpoint, { body = body }) |
104 | 158 | end
|
105 | 159 |
|
106 |
| -function M.my_articles(on_success, on_error) return get("/articles/me/all", on_success, on_error) end |
| 160 | +---@param on_success fun(body: table) |
| 161 | +function M.my_articles(on_success) |
| 162 | + return get("/articles/me/all", on_success) |
| 163 | +end |
107 | 164 |
|
| 165 | +---@param id number |
| 166 | +---@param content string |
| 167 | +---@return Response |
108 | 168 | function M.save_article(id, content)
|
109 | 169 | return put(
|
110 | 170 | "/articles/" .. tostring(id),
|
111 |
| - vim.fn.json_encode({ article = { body_markdown = content } }) |
| 171 | + { article = { body_markdown = content } } |
112 | 172 | )
|
113 | 173 | end
|
114 | 174 |
|
| 175 | +---@param title string |
| 176 | +---@return Response |
115 | 177 | function M.new_article(title)
|
116 | 178 | return post(
|
117 | 179 | "/articles",
|
118 |
| - vim.fn.json_encode({ article = { body_markdown = article.get_template(title) } }) |
| 180 | + { article = { body_markdown = article.get_template(title) } } |
119 | 181 | )
|
120 | 182 | end
|
121 | 183 |
|
122 |
| -function M.feed(on_success, on_error) return get("/articles", on_success, on_error) end |
| 184 | +---@param on_success fun(body: table) |
| 185 | +function M.feed(on_success) |
| 186 | + get("/articles", on_success) |
| 187 | +end |
123 | 188 |
|
124 |
| -function M.get_article(id, on_success, on_error) |
125 |
| - return get( |
| 189 | +---@param id number |
| 190 | +---@param on_success fun(body: table) |
| 191 | +function M.get_article(id, on_success) |
| 192 | + get( |
126 | 193 | "/articles/" .. tostring(id),
|
127 |
| - on_success, |
128 |
| - on_error |
| 194 | + on_success |
129 | 195 | )
|
130 | 196 | end
|
131 | 197 |
|
132 |
| -function M.get_article_by_path(path, on_success, on_error) return get("/articles/" .. path, on_success, on_error) end |
| 198 | +---@param path string |
| 199 | +---@param on_success fun(body: table) |
| 200 | +function M.get_article_by_path(path, on_success) |
| 201 | + get("/articles/" .. path, on_success) |
| 202 | +end |
133 | 203 |
|
134 | 204 | return M
|
0 commit comments