Skip to content

Commit b214947

Browse files
committed
wip tests
1 parent b92dc6e commit b214947

15 files changed

Lines changed: 296 additions & 117 deletions

File tree

.credo.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@
113113
{Credo.Check.Readability.TrailingBlankLine, []},
114114
{Credo.Check.Readability.TrailingWhiteSpace, []},
115115
{Credo.Check.Readability.UnnecessaryAliasExpansion, []},
116-
{Credo.Check.Readability.VariableNames, []},
116+
# {Credo.Check.Readability.VariableNames, []}, # needs to raise PR in :oapi_generator
117117
{Credo.Check.Readability.WithSingleClause, []},
118118

119119
#

.github/workflows/ci.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ on:
44
push:
55
branches: ["main"]
66
pull_request:
7-
branches: ["main"]
87

98
jobs:
109
# https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# OpenApiTypesense
22

3+
## TODO
4+
- Custom Http client adapter (currently [`Req`](https://hexdocs.pm/req))
5+
- Tests for each modules not completed yet.
6+
37
## Installation
48

59
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
@@ -8,7 +12,7 @@ by adding `open_api_typesense` to your list of dependencies in `mix.exs`:
812
```elixir
913
def deps do
1014
[
11-
{:open_api_typesense, "~> 0.1"}
15+
{:open_api_typesense, "~> 0.2"}
1216
]
1317
end
1418
```

config/config.exs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ if Mix.env() in [:dev, :test] do
66
host: "localhost",
77
port: 8108,
88
scheme: "http"
9+
end
910

11+
if Mix.env() == :dev do
1012
config :oapi_generator,
1113
default: [
1214
output: [

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ services:
1010
- ./typesense-data:/data
1111
command: "--data-dir /data --api-key=xyz --enable-cors"
1212
typesense_dashboard:
13-
image: ghcr.io/bfritscher/typesense-dashboard:latest
13+
image: ghcr.io/bfritscher/typesense-dashboard:1.9.3
1414
container_name: typesense_dashboard
1515
restart: on-failure
1616
ports:

lib/open_api_typesense/client.ex

Lines changed: 113 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,27 @@
11
defmodule OpenApiTypesense.Client do
2-
@moduledoc since: "0.1.0"
2+
@moduledoc since: "0.2.0"
33
@moduledoc """
44
Http client for Typesense server.
55
"""
66

77
alias OpenApiTypesense.Connection
88

9-
@typedoc since: "0.1.0"
10-
@type request_body() :: iodata() | nil
9+
@typedoc since: "0.2.0"
10+
@type response() ::
11+
{:ok, any()}
12+
| {:error, OpenApiTypesense.ApiResponse.t()}
13+
| {:error, String.t()}
14+
| :error
1115

12-
@typedoc since: "0.1.0"
13-
@type request_method() :: :get | :post | :delete | :patch | :put
14-
15-
@typedoc since: "0.1.0"
16-
@type request_path() :: String.t()
17-
18-
@doc since: "0.1.0"
16+
@doc since: "0.2.0"
1917
@spec get_host :: String.t() | nil
2018
def get_host, do: Application.get_env(:open_api_typesense, :host)
2119

22-
@doc since: "0.1.0"
20+
@doc since: "0.2.0"
2321
@spec get_scheme :: String.t() | nil
2422
def get_scheme, do: Application.get_env(:open_api_typesense, :scheme)
2523

26-
@doc since: "0.1.0"
24+
@doc since: "0.2.0"
2725
@spec get_port :: non_neg_integer() | nil
2826
def get_port do
2927
Application.get_env(:open_api_typesense, :port)
@@ -38,87 +36,129 @@ defmodule OpenApiTypesense.Client do
3836
> function will still return the key and accessible inside
3937
> shell (assuming bad actors [pun unintended `:/`] can get in as well).
4038
"""
41-
@doc since: "0.1.0"
39+
@doc since: "0.2.0"
4240
@spec api_key :: String.t() | nil
4341
def api_key, do: Application.get_env(:open_api_typesense, :api_key)
4442

45-
def run(opts \\ %{}) do
46-
conn = Connection.new()
43+
@doc """
44+
Command for making http requests.
45+
46+
> #### On using this function {: .info}
47+
> Functions e.g. `OpenApiTypesense.Health.health` don't need to explicitly pass
48+
> a `connection` unless you want to use custom `connection`. See [`README`](/README.md) for
49+
> more details or `OpenApiTypesense.Connection` module.
50+
51+
## Options
52+
53+
- `:body`: Payload for passing as request body (defaults to `nil`).
54+
- `:url`: Request path.
55+
- `:method`: Request method (e.g. `:get`, `:post`, `:put`, `:patch`, `:delete`). Defaults to `:get`.
56+
- `:query`: Request query params (defaults to `%{}`).
57+
58+
## Examples
59+
iex> alias OpenApiTypesense.Client
60+
61+
iex> connection = %OpenApiTypesense.Connection{
62+
...> host: "localhost",
63+
...> api_key: "some_api_key",
64+
...> port: "8108",
65+
...> scheme: "http"
66+
...> }
67+
68+
iex> opts = %{
69+
...> args: [],
70+
...> call: {OpenApiTypesense.Health, :health},
71+
...> url: "/health",
72+
...> method: :get,
73+
...> response: [{200, {OpenApiTypesense.HealthStatus, :t}}],
74+
...> opts: opts
75+
...> }
76+
77+
iex> Client.request(connection, opts)
78+
{:ok, %OpenApiTypesense.HealthStatus{ok: true}}
79+
"""
80+
@doc since: "0.2.0"
81+
@spec request(Connection.t(), map()) :: response()
82+
def request(conn, opts \\ %{}) do
4783
# Req.Request.append_error_steps and its retry option are used here.
4884
# options like retry, max_retries, etc. can be found in:
4985
# https://hexdocs.pm/req/Req.Steps.html#retry/1
5086
# NOTE: look at source code in Github
51-
retry = fn request ->
87+
retry =
5288
if Mix.env() === :test do
53-
{req, resp_or_err} = request
54-
5589
# disabled in order to cut time in tests
56-
req = %{req | options: %{retry: false}}
90+
false
91+
else
92+
:safe_transient
93+
end
5794

58-
Req.Steps.retry({req, resp_or_err})
95+
max_retries =
96+
if Mix.env() === :test do
97+
# disabled in order to cut time in tests
98+
0
5999
else
60-
Req.Steps.retry(request)
100+
# default
101+
3
61102
end
62-
end
63103

64104
url =
65105
%URI{
66106
scheme: conn.scheme,
67107
host: conn.host,
68108
port: conn.port,
69-
path: opts[:path],
70-
query: URI.encode_query(opts[:query] || %{})
109+
path: opts[:url],
110+
query: URI.encode_query(opts[:opts] || %{})
71111
}
72112

73-
response =
74-
%Req.Request{
75-
body: opts[:body] || nil,
113+
{_req, resp} =
114+
[
76115
method: opts[:method] || :get,
77-
url: url
78-
}
79-
|> Req.Request.put_header("x-typesense-api-key", HttpClient.api_key())
80-
|> Req.Request.put_header("content-type", opts[:content_type] || "application/json")
81-
|> Req.Request.append_error_steps(retry: retry)
82-
|> Req.Request.run!()
83-
84-
case response.status in 200..299 do
85-
true ->
86-
body =
87-
if opts[:content_type] === "text/plain",
88-
do: String.split(response.body, "\n", trim: true) |> Enum.map(&Jason.decode!/1),
89-
else: Jason.decode!(response.body)
90-
91-
{:ok, body}
92-
93-
false ->
94-
{:error, Jason.decode!(response.body)["message"]}
95-
end
96-
97-
# @spec request(map) :: {:ok, term} | {:error, term} | Operation.t()
98-
# def request(%{opts: opts} = info) do
99-
# %Operation{
100-
# request_method: method,
101-
# request_server: server,
102-
# request_url: url
103-
# } = operation = Operation.new(info)
104-
105-
# metadata = %{
106-
# client: __MODULE__,
107-
# info: info,
108-
# request_method: method,
109-
# request_server: server,
110-
# request_url: url
111-
# }
112-
113-
# :telemetry.span([:oapi_github, :request], metadata, fn ->
114-
# result = reduce_stack(operation)
115-
116-
# if Config.wrap(opts) do
117-
# wrap_result(result, metadata)
118-
# else
119-
# {result, Map.put(metadata, :response_code, 0)}
120-
# end
121-
# end)
122-
# end
116+
body: opts[:body] || nil,
117+
url: url,
118+
retry: retry,
119+
max_retries: max_retries,
120+
decode_json: [keys: :atoms!]
121+
]
122+
|> Req.new()
123+
|> Req.Request.put_header("x-typesense-api-key", api_key())
124+
|> Req.Request.run_request()
125+
126+
parse_resp(resp, opts.response)
127+
end
128+
129+
defp parse_resp(%Req.TransportError{} = error, _opts_resp) do
130+
{:error, Exception.message(error)}
131+
end
132+
133+
defp parse_resp(%Req.HTTPError{} = error, _opts_resp) do
134+
{:error, Exception.message(error)}
135+
end
136+
137+
defp parse_resp(resp, opts_resp) do
138+
{code, values} =
139+
opts_resp
140+
|> Enum.find(fn {code, _values} ->
141+
code === resp.status
142+
end)
143+
144+
parse_values(code, values, resp.body)
145+
end
146+
147+
defp parse_values(code, values, body) when is_list(values) do
148+
status = if code in 200..299, do: :ok, else: :error
149+
150+
resp =
151+
values
152+
|> Enum.map(fn {module, _func_name} ->
153+
struct(module, body)
154+
end)
155+
156+
{status, resp}
157+
end
158+
159+
defp parse_values(code, {module, _func_name} = _values, body) do
160+
status = if code in 200..299, do: :ok, else: :error
161+
162+
{status, struct(module, body)}
123163
end
124164
end
Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,59 @@
11
defmodule OpenApiTypesense.Connection do
2-
def new do
3-
nil
2+
@moduledoc since: "0.2.0"
3+
@moduledoc """
4+
Fetches credentials either from application env or map.
5+
"""
6+
alias OpenApiTypesense.Client
7+
8+
@derive {Inspect, except: [:api_key]}
9+
defstruct [:host, :api_key, :port, :scheme]
10+
11+
@typedoc since: "0.2.0"
12+
@type t() :: %{
13+
host: binary() | nil,
14+
api_key: binary() | nil,
15+
port: non_neg_integer() | nil,
16+
scheme: binary() | nil
17+
}
18+
19+
@doc """
20+
Setting new connection or using the default config.
21+
22+
> #### On using this function {: .info}
23+
> Functions e.g. `OpenApiTypesense.Health.health` don't need to explicitly pass this
24+
> unless you want to use another connection. Also, `api_key` is hidden when invoking
25+
> this function.
26+
27+
## Examples
28+
iex> alias OpenApiTypesense.Connection
29+
30+
iex> conn = Connection.new()
31+
%OpenApiTypesense.Connection{
32+
host: "localhost",
33+
port: 8108,
34+
scheme: "http",
35+
...
36+
}
37+
"""
38+
@doc since: "0.2.0"
39+
@spec new(connection :: t() | map()) :: %__MODULE__{}
40+
def new(connection \\ defaults()) when is_map(connection) do
41+
%__MODULE__{
42+
host: Map.get(connection, :host),
43+
api_key: Map.get(connection, :api_key),
44+
port: Map.get(connection, :port),
45+
scheme: Map.get(connection, :scheme)
46+
}
47+
end
48+
49+
@doc since: "0.2.0"
50+
@spec defaults :: map()
51+
defp defaults do
52+
%{
53+
host: Client.get_host(),
54+
api_key: Client.api_key(),
55+
port: Client.get_port(),
56+
scheme: Client.get_scheme()
57+
}
458
end
559
end

0 commit comments

Comments
 (0)