Skip to content

Commit

Permalink
feat: follow supabase-ex release (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
zoedsoupe authored Jan 15, 2025
1 parent e893618 commit 9ec3bb5
Show file tree
Hide file tree
Showing 22 changed files with 479 additions and 778 deletions.
Empty file added .dialyzerignore
Empty file.
129 changes: 128 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ jobs:
lint:
runs-on: ubuntu-latest

env:
MIX_ENV: test

strategy:
matrix:
elixir: [1.18.1]
otp: [27.2]
otp: [27.0]

steps:
- name: Checkout code
Expand Down Expand Up @@ -60,3 +63,127 @@ jobs:

- name: Run Credo
run: mix credo --strict

static-analisys:
runs-on: ubuntu-latest

env:
MIX_ENV: test

strategy:
matrix:
elixir: [1.18.1]
otp: [27.0]

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Elixir
uses: erlef/setup-beam@v1
with:
elixir-version: ${{ matrix.elixir }}
otp-version: ${{ matrix.otp }}

- name: Cache Elixir deps
uses: actions/cache@v1
id: deps-cache
with:
path: deps
key: ${{ runner.os }}-mix-${{ env.MIX_ENV }}-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}

- name: Cache Elixir _build
uses: actions/cache@v1
id: build-cache
with:
path: _build
key: ${{ runner.os }}-build-${{ env.MIX_ENV }}-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}

- name: Install deps
if: steps.deps-cache.outputs.cache-hit != 'true'
run: |
mix local.rebar --force
mix local.hex --force
mix deps.get --only ${{ env.MIX_ENV }}
- name: Compile deps
if: steps.build-cache.outputs.cache-hit != 'true'
run: mix deps.compile --warnings-as-errors

# Don't cache PLTs based on mix.lock hash, as Dialyzer can incrementally update even old ones
# Cache key based on Elixir & Erlang version (also useful when running in matrix)
- name: Restore PLT cache
uses: actions/cache/restore@v3
id: plt_cache
with:
key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-plt
restore-keys: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-plt
path: priv/plts

# Create PLTs if no cache was found
- name: Create PLTs
if: steps.plt_cache.outputs.cache-hit != 'true'
run: mix dialyzer --plt

- name: Save PLT cache
uses: actions/cache/save@v3
if: steps.plt_cache.outputs.cache-hit != 'true'
id: plt_cache_save
with:
key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-plt
path: priv/plts

- name: Run dialyzer
run: mix dialyzer --format github

test:
runs-on: ubuntu-latest

env:
MIX_ENV: test

strategy:
matrix:
elixir: [1.18.1]
otp: [27.0]

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Elixir
uses: erlef/setup-beam@v1
with:
elixir-version: ${{ matrix.elixir }}
otp-version: ${{ matrix.otp }}

- name: Cache Elixir deps
uses: actions/cache@v1
id: deps-cache
with:
path: deps
key: ${{ runner.os }}-mix-${{ env.MIX_ENV }}-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}

- name: Cache Elixir _build
uses: actions/cache@v1
id: build-cache
with:
path: _build
key: ${{ runner.os }}-build-${{ env.MIX_ENV }}-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}

- name: Install deps
if: steps.deps-cache.outputs.cache-hit != 'true'
run: |
mix local.rebar --force
mix local.hex --force
mix deps.get --only ${{ env.MIX_ENV }}
- name: Compile deps
if: steps.build-cache.outputs.cache-hit != 'true'
run: mix deps.compile --warnings-as-errors

- name: Clean build
run: mix clean

- name: Run tests
run: mix test
1 change: 1 addition & 0 deletions .wakatime-project
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
postgrest-ex
120 changes: 29 additions & 91 deletions lib/supabase/postgrest.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ defmodule Supabase.PostgREST do

alias Supabase.Client
alias Supabase.Fetcher
alias Supabase.PostgREST.Builder
alias Supabase.Fetcher.Request
alias Supabase.PostgREST.Error

alias Supabase.PostgREST.FilterBuilder
Expand Down Expand Up @@ -78,7 +78,8 @@ defmodule Supabase.PostgREST do
@impl true
def from(%Client{} = client, table) do
client
|> Builder.new(relation: table)
|> Request.new()
|> Request.with_database_url(table)
|> with_custom_media_type(:default)
end

Expand All @@ -94,8 +95,8 @@ defmodule Supabase.PostgREST do
iex> Supabase.PostgREST.schema(builder, private)
"""
@impl true
def schema(%Builder{} = b, schema) when is_binary(schema) do
%{b | schema: schema}
def schema(%Request{} = b, schema) when is_binary(schema) do
put_in(b.client.db.schema, schema)
end

@doc """
Expand All @@ -112,10 +113,10 @@ defmodule Supabase.PostgREST do
- [PostgREST resource represation docs](https://docs.postgrest.org/en/v12/references/api/resource_representation.html)
"""
@impl true
def with_custom_media_type(%Builder{} = b, media_type)
def with_custom_media_type(%Request{} = b, media_type)
when is_atom(media_type) do
header = @accept_headers[media_type] || @accept_headers[:default]
Builder.add_request_header(b, "accept", header)
Request.with_headers(b, %{"accept" => header})
end

@doc """
Expand All @@ -131,26 +132,7 @@ defmodule Supabase.PostgREST do
- Supabase query execution: https://supabase.com/docs/reference/javascript/performing-queries
"""
@impl true
def execute(%Builder{} = b), do: do_execute(b)

@doc """
Executes the query and returns the result as a JSON-encoded string.
## Parameters
- `builder`: The Builder or Builder instance to execute.
## Examples
iex> PostgREST.execute_string(builder)
## See also
- Supabase query execution and response handling: https://supabase.com/docs/reference/javascript/performing-queries
"""
@impl true
def execute_string(%Builder{} = b) do
with {:ok, body} <- do_execute(b) do
Jason.encode(body)
end
end
def execute(%Request{} = b), do: do_execute(b)

@doc """
Executes the query and maps the resulting data to a specified schema struct, useful for casting the results to Elixir structs.
Expand All @@ -166,88 +148,44 @@ defmodule Supabase.PostgREST do
- Supabase query execution and schema casting: https://supabase.com/docs/reference/javascript/performing-queries
"""
@impl true
def execute_to(%Builder{} = b, schema) when is_atom(schema) do
with {:ok, body} <- do_execute(b) do
if is_list(body) do
{:ok, Enum.map(body, &struct(schema, &1))}
else
{:ok, struct(schema, body)}
end
end
def execute_to(%Request{} = b, schema) when is_atom(schema) do
alias Supabase.PostgREST.SchemaDecoder

Request.with_body_decoder(b, SchemaDecoder, schema: schema)
|> do_execute()
end

@doc """
Executes a query using the Finch HTTP client, formatting the request appropriately. Returns the HTTP request without executing it.
Builds a query using the Finch HTTP client, formatting the request appropriately. Returns the HTTP request without executing it.
## Parameters
- `builder`: The Builder or Builder instance to execute.
- `schema`: Optional schema module to map the results.
## Examples
iex> PostgREST.execute_to_finch_request(builder, User)
iex> PostgREST.execute_to_finch_request(builder)
## See also
- Supabase query execution: https://supabase.com/docs/reference/javascript/performing-queries
"""
@impl true
def execute_to_finch_request(%Builder{client: client} = b) do
headers = Fetcher.apply_client_headers(client, nil, Map.to_list(b.headers))
query = URI.encode_query(b.params)
url = URI.new!(b.url) |> URI.append_query(query)
def execute_to_finch_request(%Request{} = b) do
query = URI.encode_query(b.query)
url = URI.parse(b.url) |> URI.append_query(query)

Supabase.Fetcher.new_connection(b.method, url, b.body, headers)
Finch.build(b.method, url, b.headers, b.body)
end

defp do_execute(%Builder{client: client} = b) do
headers = Fetcher.apply_client_headers(client, nil, Map.to_list(b.headers))
query = URI.encode_query(b.params)
url = URI.new!(b.url) |> URI.append_query(query)
request = request_fun_from_method(b.method)
defp do_execute(%Request{client: client} = b) do
schema = client.db.schema

url
|> request.(b.body, headers)
|> parse_response()
end

defp request_fun_from_method(:get), do: &Supabase.Fetcher.get/3
defp request_fun_from_method(:head), do: &Supabase.Fetcher.head/3
defp request_fun_from_method(:post), do: &Supabase.Fetcher.post/3
defp request_fun_from_method(:delete), do: &Supabase.Fetcher.delete/3
defp request_fun_from_method(:patch), do: &Supabase.Fetcher.patch/3

defp parse_response({:error, reason}), do: {:error, reason}

defp parse_response({:ok, %{status: _, body: ""}}) do
{:ok, nil}
end

defp parse_response({:ok, %{status: status, body: raw, headers: headers}}) do
if json_content?(headers) do
with {:ok, body} <- Jason.decode(raw, keys: :atoms) do
cond do
error_resp?(status) -> {:error, Error.from_raw_body(body)}
success_resp?(status) -> {:ok, body}
end
end
else
{:ok, raw}
end
end

defp json_content?(headers) when is_list(headers) do
headers
|> Enum.find_value(fn
{"content-type", type} -> type
_ -> false
end)
|> String.match?(~r/json/)
end

defp error_resp?(status) do
Kernel.in(status, 400..599)
end
schema_header =
if b.method in [:get, :head],
do: %{"accept-profile" => schema},
else: %{"content-profile" => schema}

defp success_resp?(status) do
Kernel.in(status, 200..399)
b
|> Request.with_error_parser(Error)
|> Request.with_headers(schema_header)
|> Fetcher.request()
end
end
20 changes: 7 additions & 13 deletions lib/supabase/postgrest/behaviour.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,16 @@ defmodule Supabase.PostgREST.Behaviour do
@moduledoc "Defines the interface for the main module Supabase.PostgREST"

alias Supabase.Client
alias Supabase.PostgREST.Builder
alias Supabase.PostgREST.Error
alias Supabase.Fetcher.Request

@type media_type ::
:json | :csv | :openapi | :geojson | :pgrst_plan | :pgrst_object | :pgrst_array

@callback with_custom_media_type(builder, media_type) :: builder
when builder: Builder.t() | Builder.t()
@callback from(Client.t(), relation :: String.t()) :: Builder.t()
@callback schema(Builder.t(), schema :: String.t()) :: Builder.t()
@callback with_custom_media_type(Request.t(), media_type) :: Request.t()
@callback from(Client.t(), relation :: String.t()) :: Request.t()
@callback schema(Request.t(), schema :: String.t()) :: Request.t()

@callback execute(Builder.t() | Builder.t()) :: {:ok, term} | {:error, Error.t()}
@callback execute_string(Builder.t() | Builder.t()) ::
{:ok, binary} | {:error, Error.t() | atom}
@callback execute_to(Builder.t() | Builder.t(), atom) ::
{:ok, term} | {:error, Error.t() | atom}
@callback execute_to_finch_request(Builder.t() | Builder.t()) ::
Finch.Request.t()
@callback execute(Request.t()) :: Supabase.result(term)
@callback execute_to(Request.t(), module) :: Supabase.result(term)
@callback execute_to_finch_request(Request.t()) :: Finch.Request.t()
end
Loading

0 comments on commit 9ec3bb5

Please sign in to comment.