Current section
Files
Jump to
Current section
Files
lib/utils/request.ex
defmodule StarkCore.Utils.Request do
@moduledoc false
alias StarkCore.Utils.JSON
alias StarkCore.Utils.Check
alias StarkCore.Utils.URL
alias StarkCore.User.Project
alias StarkCore.User.Organization
alias StarkCore.Error
@type requestOptions :: %{
payload: map(),
query: map(),
user: Project.t() | Organization.t(),
sdk_version: String.t(),
api_version: String.t(),
language: String.t(),
timeout: number(),
prefix: String.t(),
sdk_name: String.t()
}
@type rawResponse :: %{
status_code: non_neg_integer(),
headers: list({String.t(), String.t()}),
content: any()
}
def fetch(
host,
method,
path,
options \\ []
) do
%{
payload: payload,
query: query,
version: version,
user: user_parameter,
prefix: prefix,
sdk_version: sdk_version,
language: language,
timeout_seconds: timeout_seconds,
limit: limit,
sdk_name: sdk_name
} = Enum.into(
options,
%{
payload: nil,
query: %{},
version: ~c"v2",
user: nil,
prefix: "",
sdk_version: Mix.Project.config()[:version],
language: nil,
timeout_seconds: 15,
limit: nil,
sdk_name: "Core"
}
)
with {:ok, _} <- Check.host(host),
{:ok, user} <- Check.user(user_parameter)
do
request(
user,
method,
URL.get_url(
host,
user.environment,
version,
path,
query
),
payload,
prefix,
sdk_version,
language,
timeout_seconds,
sdk_name
)
|> process_response
end
end
def fetch_raw(
host,
method,
path,
options \\ []
) do
%{
payload: payload,
query: query,
version: version,
user: user_parameter,
prefix: prefix,
sdk_version: sdk_version,
language: language,
timeout_seconds: timeout_seconds,
limit: limit,
sdk_name: sdk_name
} = Enum.into(
options,
%{
payload: nil,
query: %{},
version: ~c"v2",
user: nil,
prefix: nil,
sdk_version: Mix.Project.config()[:version],
language: nil,
timeout_seconds: 15,
limit: nil,
sdk_name: "core"
}
)
with {:ok, _} <- Check.host(host),
{:ok, user} <- Check.user(user_parameter)
do
request(
user,
method,
URL.get_url(
host,
user.environment,
version,
path,
query
|> Map.put(:limit, limit)
),
payload,
prefix,
sdk_version,
language,
timeout_seconds,
sdk_name
)
|> process_raw_response
else
{:error, message} -> {:error, message}
end
end
defp request(
user,
method,
url,
payload,
prefix,
sdk_version,
language,
timeout_seconds,
sdk_name
) do
Application.ensure_all_started(:inets)
Application.ensure_all_started(:ssl)
http_request_opts = [
timeout: timeout_seconds * 1000,
ssl: [
cacerts: :public_key.cacerts_get()
],
]
{:ok, {{~c"HTTP/1.1", status_code, _message}, headers, response_body}} = :httpc.request(
method,
get_request_params(user, url, payload, prefix, sdk_version, language, sdk_name),
http_request_opts,
[]
)
{status_code, response_body, headers}
end
defp get_request_params(user, url, body, prefix, sdk_version, language, sdk_name) when is_nil(body) do
{
String.to_charlist(URI.encode(url)),
get_headers(user, "", prefix, sdk_version, language, sdk_name)
}
end
defp get_request_params(user, url, body, prefix, sdk_version, language, sdk_name) do
{
String.to_charlist(url),
get_headers(user, body, prefix, sdk_version, language, sdk_name),
~c"text/plain",
JSON.encode!(body)
}
end
defp get_headers(user, body, prefix, sdk_version, language, sdk_name) when is_map(body) do
body_string = JSON.encode!(body)
get_headers(user, body_string, prefix, sdk_version, language, sdk_name)
end
defp get_headers(user, body, prefix, sdk_version, language, sdk_name) when is_binary(body) do
access_time = DateTime.utc_now() |> DateTime.to_unix(:second)
signature =
"#{user.access_id}:#{access_time}:#{body}"
|> EllipticCurve.Ecdsa.sign(user.private_key)
|> EllipticCurve.Signature.toBase64()
user_agent = case String.length(prefix || "") > 0 do
true -> ~c"#{prefix}-Elixir-#{System.version()}-SDK-#{sdk_name}-#{sdk_version}"
false -> ~c"Elixir-#{System.version()}-SDK-#{sdk_name}-#{sdk_version}"
end
[
{~c"Access-Id", to_charlist(user.access_id)},
{~c"Access-Time", Integer.to_charlist(access_time)},
{~c"Access-Signature", to_charlist(signature)},
{~c"Content-Type", ~c"application/json"},
{~c"User-Agent", user_agent},
{~c"Accept-Language", Check.language(language)}
]
end
defp process_response({status_code, body, _headers}) do
case status_code do
500 -> {:error, [%Error{code: "internalServerError", message: "Houston, we have a problem."}]}
400 -> {:error,
JSON.decode!(body)["errors"]
|> Enum.map(fn error -> %Error{code: error["code"], message: error["message"]} end)}
200 -> {:ok, body}
_ -> {:error,
[%Error{
code: "unknownError",
message: "Unknown exception encountered: " <> to_string(body)}
]
}
end
end
defp process_raw_response({status_code, body, headers}) do
case status_code do
500 -> {:error, %{
headers: headers,
content: [%Error{code: "internalServerError", message: "Houston, we have a problem."}],
status_code: status_code
}}
400 -> {:error, %{
headers: headers,
status_code: status_code,
content: JSON.decode!(body)["errors"]
|> Enum.map(fn error ->
%Error{code: error["code"], message: error["message"]}
end)
}}
200 -> {:ok, %{headers: headers, content: body, status_code: status_code}}
_ -> {:error,
[%Error{
code: "unknownError",
message: "Unknown exception encountered: " <> to_string(body)}
]
}
end
end
end