From c083f3418dbaa8b354febf936fe08037a9514d40 Mon Sep 17 00:00:00 2001
From: V1K1NGbg <victor@ilchev.com>
Date: Thu, 22 Aug 2024 17:48:56 -0300
Subject: Teal: convert luarocks.upload.api

---
 src/luarocks/upload/api.tl | 313 +++++++++++++++++++++++++--------------------
 1 file changed, 174 insertions(+), 139 deletions(-)

(limited to 'src')

diff --git a/src/luarocks/upload/api.tl b/src/luarocks/upload/api.tl
index e1413702..dc0b568c 100644
--- a/src/luarocks/upload/api.tl
+++ b/src/luarocks/upload/api.tl
@@ -1,5 +1,22 @@
+local record api
+   record Configuration
+      key: string
+      server: string
+      version: string
+   end
 
-local api = {}
+   record Api
+      load_config: function(Api): Configuration
+      save_config: function(Api): boolean, string
+      check_version: function(Api): boolean, string
+      method: function(Api, string, ...:Parameters): {string : any}, string
+      raw_method: function(Api, string, ...:Parameters): {string : any}, string
+      request: function(Api, string, ?Parameters, ?Parameters): {string : any}, string
+      config: Configuration
+      debug: boolean
+      _server_tool_version: string
+   end
+end
 
 local cfg = require("luarocks.core.cfg")
 local fs = require("luarocks.fs")
@@ -10,43 +27,50 @@ local multipart = require("luarocks.upload.multipart")
 local json = require("luarocks.vendor.dkjson")
 local dir_sep = package.config:sub(1, 1)
 
-local Api = {}
+local type Parameters = multipart.Parameters
+local type Api = api.Api
+local type Args = require("luarocks.core.types.args").Args
+local type Configuration = api.Configuration
+local type File = multipart.File
+
+local type PersistableTable = require("luarocks.core.types.persist").PersistableTable
 
-local function upload_config_file()
+local function upload_config_file(): string
    if not cfg.config_files.user.file then
       return nil
    end
    return (cfg.config_files.user.file:gsub("[\\/][^\\/]+$", dir_sep .. "upload_config.lua"))
 end
 
-function Api:load_config()
+function api.Api:load_config(): Configuration
    local upload_conf = upload_config_file()
    if not upload_conf then return nil end
-   local config, err = persist.load_into_table(upload_conf)
+   local config = persist.load_into_table(upload_conf) as Configuration
    return config
 end
 
-function Api:save_config()
+function api.Api:save_config(): boolean, string
    -- Test configuration before saving it.
-   local res, err = self:raw_method("status")
+   local res, errraw = self:raw_method("status")
    if not res then
-      return nil, err
+      return nil, errraw
    end
-   if res.errors then
-      util.printerr("Server says: " .. tostring(res.errors[1]))
-      return
+   local reserrors = res.errors
+   if reserrors is {string} then
+      return nil, ("Server error: " .. tostring(reserrors[1]))
    end
    local upload_conf = upload_config_file()
    if not upload_conf then return nil end
-   local ok, err = fs.make_dir(dir.dir_name(upload_conf))
+   local ok, errmake = fs.make_dir(dir.dir_name(upload_conf))
    if not ok then
-      return nil, err
+      return nil, errmake
    end
-   persist.save_from_table(upload_conf, self.config)
+   persist.save_from_table(upload_conf, self.config as PersistableTable)
    fs.set_permissions(upload_conf, "read", "user")
+   return true
 end
 
-function Api:check_version()
+function api.Api:check_version(): boolean, string
    if not self._server_tool_version then
       local tool_version = cfg.upload.tool_version
       local res, err = self:request(tostring(self.config.server) .. "/api/tool_version", {
@@ -58,7 +82,7 @@ function Api:check_version()
       if not res.version then
          return nil, "failed to fetch tool version"
       end
-      self._server_tool_version = res.version
+      self._server_tool_version = tostring(res.version)
       if res.force_update then
          return nil, "Your upload client is too out of date to continue, please upgrade LuaRocks."
       end
@@ -69,40 +93,45 @@ function Api:check_version()
    return true
 end
 
-function Api:method(...)
-   local res, err = self:raw_method(...)
+function api.Api:method(path: string, ...: Parameters): {string : any}, string
+   local res, err = self:raw_method(path, ...)
    if not res then
       return nil, err
    end
-   if res.errors then
-      if res.errors[1] == "Invalid key" then
-         return nil, res.errors[1] .. " (use the --api-key flag to change)"
+   local reserrors = res.errors
+   if reserrors is {string} then --! not checking the contents
+      if reserrors[1] == "Invalid key" then
+         return nil, reserrors[1] .. " (use the --api-key flag to change)"
       end
-      local msg = table.concat(res.errors, ", ")
+      local msg = table.concat(reserrors, ", ")
       return nil, "API Failed: " .. msg
    end
    return res
 end
 
-function Api:raw_method(path, ...)
+function api.Api:raw_method(path: string, ...: Parameters): {string : any}, string
    self:check_version()
-   local url = tostring(self.config.server) .. "/api/" .. tostring(cfg.upload.api_version) .. "/" .. tostring(self.config.key) .. "/" .. tostring(path)
+   local url = tostring(self.config.server) .. "/api/" .. tostring(cfg.upload.api_version) .. "/" .. tostring(self.config.key) .. "/" .. path
    return self:request(url, ...)
 end
 
-local function encode_query_string(t, sep)
+local function encode_query_string(t: Parameters, sep?: string): string
    if sep == nil then
       sep = "&"
    end
    local i = 0
-   local buf = { }
+   local buf: {string} = { }
    for k, v in pairs(t) do
-      if type(k) == "number" and type(v) == "table" then
-         k, v = v[1], v[2]
+      local ks, vs: string, string
+      local vf = v
+      if vf is File then
+         ks, vs = k, vf:content()
+      else
+         ks, vs = k, vf
       end
-      buf[i + 1] = multipart.url_escape(k)
+      buf[i + 1] = multipart.url_escape(ks)
       buf[i + 2] = "="
-      buf[i + 3] = multipart.url_escape(v)
+      buf[i + 3] = multipart.url_escape(vs)
       buf[i + 4] = sep
       i = i + 4
    end
@@ -110,141 +139,144 @@ local function encode_query_string(t, sep)
    return table.concat(buf)
 end
 
-local function redact_api_url(url)
-   url = tostring(url)
-   return (url:gsub(".*/api/[^/]+/[^/]+", "")) or ""
+local function redact_api_url(url: any): string
+   local urls = tostring(url)
+   return (urls:gsub(".*/api/[^/]+/[^/]+", "")) or ""
 end
 
 local ltn12_ok, ltn12 = pcall(require, "ltn12")
 if not ltn12_ok then -- If not using LuaSocket and/or LuaSec...
 
-function Api:request(url, params, post_params)
-   local vars = cfg.variables
-
-   if fs.which_tool("downloader") == "wget" then
-      local curl_ok, err = fs.is_tool_available(vars.CURL, "curl")
-      if not curl_ok then
-         return nil, err
-      end
-   end
+   api.Api.request = function(self: Api, url: string, params?: Parameters, post_params?: Parameters): {string : any}, string
+      local vars = cfg.variables
 
-   if not self.config.key then
-      return nil, "Must have API key before performing any actions."
-   end
-   if params and next(params) then
-      url = url .. ("?" .. encode_query_string(params))
-   end
-   local method = "GET"
-   local out
-   local tmpfile = fs.tmpname()
-   if post_params then
-      method = "POST"
-      local curl_cmd = vars.CURL.." "..vars.CURLNOCERTFLAG.." -f -L --silent --user-agent \""..cfg.user_agent.." via curl\" "
-      for k,v in pairs(post_params) do
-         local var = v
-         if type(v) == "table" then
-            var = "@"..v.fname
+      if fs.which_tool("downloader") == "wget" then
+         local curl_ok, err = fs.is_tool_available(vars.CURL, "curl")
+         if not curl_ok then
+            return nil, err
          end
-         curl_cmd = curl_cmd .. "--form \""..k.."="..var.."\" "
       end
-      if cfg.connection_timeout and cfg.connection_timeout > 0 then
-        curl_cmd = curl_cmd .. "--connect-timeout "..tonumber(cfg.connection_timeout).." "
+
+      if not self.config.key then
+         return nil, "Must have API key before performing any actions."
       end
-      local ok = fs.execute_string(curl_cmd..fs.Q(url).." -o "..fs.Q(tmpfile))
-      if not ok then
-         return nil, "API failure: " .. redact_api_url(url)
+      if params and next(params) then
+         url = url .. ("?" .. encode_query_string(params))
       end
-   else
-      local ok, err = fs.download(url, tmpfile)
-      if not ok then
-         return nil, "API failure: " .. tostring(err) .. " - " .. redact_api_url(url)
+      local method = "GET"
+      local out: string
+      local tmpfile = fs.tmpname()
+      if post_params then
+         method = "POST"
+         local curl_cmd = vars.CURL.." "..vars.CURLNOCERTFLAG.." -f -L --silent --user-agent \""..cfg.user_agent.." via curl\" "
+         for k,v in pairs(post_params) do
+            local var: string
+            if v is File then
+               var = "@"..v.fname
+            else
+               var = v
+            end
+            curl_cmd = curl_cmd .. "--form \""..k.."="..var.."\" "
+         end
+         if cfg.connection_timeout and cfg.connection_timeout > 0 then
+         curl_cmd = curl_cmd .. "--connect-timeout "..tonumber(cfg.connection_timeout).." "
+         end
+         local ok = fs.execute_string(curl_cmd..fs.Q(url).." -o "..fs.Q(tmpfile))
+         if not ok then
+            return nil, "API failure: " .. redact_api_url(url)
+         end
+      else
+         local name, err = fs.download(url, tmpfile)
+         if not name then
+            return nil, "API failure: " .. tostring(err) .. " - " .. redact_api_url(url)
+         end
       end
-   end
 
-   local tmpfd = io.open(tmpfile)
-   if not tmpfd then
+      local tmpfd = io.open(tmpfile)
+      if not tmpfd then
+         os.remove(tmpfile)
+         return nil, "API failure reading temporary file - " .. redact_api_url(url)
+      end
+      out = tmpfd:read("*a")
+      tmpfd:close()
       os.remove(tmpfile)
-      return nil, "API failure reading temporary file - " .. redact_api_url(url)
-   end
-   out = tmpfd:read("*a")
-   tmpfd:close()
-   os.remove(tmpfile)
 
-   if self.debug then
-      util.printout("[" .. tostring(method) .. " via curl] " .. redact_api_url(url) .. " ... ")
-   end
+      if self.debug then
+         util.printout("[" .. tostring(method) .. " via curl] " .. redact_api_url(url) .. " ... ")
+      end
 
-   return json.decode(out)
-end
+      return json.decode(out)
+   end
 
 else -- use LuaSocket and LuaSec
 
-local warned_luasec = false
+   local warned_luasec = false
 
-function Api:request(url, params, post_params)
-   local server = tostring(self.config.server)
-   local http_ok, http
-   local via = "luasocket"
-   if server:match("^https://") then
-      http_ok, http = pcall(require, "ssl.https")
-      if http_ok then
-         via = "luasec"
-      else
-         if not warned_luasec then
-            util.printerr("LuaSec is not available; using plain HTTP. Install 'luasec' to enable HTTPS.")
-            warned_luasec = true
+   api.Api.request = function(self: Api, url: string, params?: Parameters, post_params?: Parameters): {string : any}, string
+      local server = tostring(self.config.server)
+      local type Http = require("socket.http")
+      local http_ok, http: boolean, Http
+      local via = "luasocket"
+      if server:match("^https://") then
+         http_ok, http = pcall(require, "ssl.https") as (boolean, Http)
+         if http_ok then
+            via = "luasec"
+         else
+            if not warned_luasec then
+               util.printerr("LuaSec is not available; using plain HTTP. Install 'luasec' to enable HTTPS.")
+               warned_luasec = true
+            end
+            http_ok, http = pcall(require, "socket.http")
+            url = url:gsub("^https", "http")
+            via = "luasocket"
          end
+      else
          http_ok, http = pcall(require, "socket.http")
-         url = url:gsub("^https", "http")
-         via = "luasocket"
       end
-   else
-      http_ok, http = pcall(require, "socket.http")
-   end
-   if not http_ok then
-      return nil, "Failed loading socket library!"
-   end
+      if not http_ok then
+         return nil, "Failed loading socket library!"
+      end
 
-   if not self.config.key then
-      return nil, "Must have API key before performing any actions."
-   end
-   local body
-   local headers = {}
-   if params and next(params) then
-      url = url .. ("?" .. encode_query_string(params))
-   end
-   if post_params then
-      local boundary
-      body, boundary = multipart.encode(post_params)
-      headers["Content-length"] = #body
-      headers["Content-type"] = "multipart/form-data; boundary=" .. tostring(boundary)
-   end
-   local method = post_params and "POST" or "GET"
-   if self.debug then
-      util.printout("[" .. tostring(method) .. " via "..via.."] " .. redact_api_url(url) .. " ... ")
-   end
-   local out = {}
-   local _, status = http.request({
-      url = url,
-      headers = headers,
-      method = method,
-      sink = ltn12.sink.table(out),
-      source = body and ltn12.source.string(body)
-   })
-   if self.debug then
-      util.printout(tostring(status))
-   end
-   local pok, ret, err = pcall(json.decode, table.concat(out))
-   if pok and ret then
-      return ret
+      if not self.config.key then
+         return nil, "Must have API key before performing any actions."
+      end
+      local body: string
+      local headers: {string: string} = {}
+      if params and next(params) then
+         url = url .. ("?" .. encode_query_string(params))
+      end
+      if post_params then
+         local boundary: string
+         body, boundary = multipart.encode(post_params)
+         headers["Content-length"] = tostring(#body)
+         headers["Content-type"] = "multipart/form-data; boundary=" .. tostring(boundary)
+      end
+      local method = post_params and "POST" or "GET"
+      if self.debug then
+         util.printout("[" .. tostring(method) .. " via "..via.."] " .. redact_api_url(url) .. " ... ")
+      end
+      local out = {}
+      local _, status = http.request({
+         url = url,
+         headers = headers,
+         method = method,
+         sink = ltn12.sink.table(out),
+         source = body and ltn12.source.string(body)
+      })
+      if self.debug then
+         util.printout(tostring(status))
+      end
+      local pok, ret = pcall(json.decode, table.concat(out))
+      if pok and ret then
+         return ret
+      end
+      return nil, "API returned " .. tostring(status) .. " - " .. redact_api_url(url)
    end
-   return nil, "API returned " .. tostring(status) .. " - " .. redact_api_url(url)
-end
 
 end
 
-function api.new(args)
-   local self = {}
+function api.new(args: Args): Api, string
+   local self: Api = {}
    setmetatable(self, { __index = Api })
    self.config = self:load_config() or {}
    self.config.server = args.server or self.config.server or cfg.upload.server
@@ -257,7 +289,10 @@ function api.new(args)
                   "and then pass it through the --api-key=<key> flag."
    end
    if args.api_key then
-      self:save_config()
+      local ok, err = self:save_config()
+      if not ok then
+         return nil, err
+      end
    end
    return self
 end
-- 
cgit v1.2.3-55-g6feb