Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions lib/cloud_cache/adapter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ defmodule CloudCache.Adapter do
opts :: options()
) :: {:ok, term()} | {:error, term()}

@callback list_objects(
bucket :: bucket(),
opts :: options()
) :: {:ok, term()} | {:error, term()}

@callback pre_sign(
bucket :: bucket(),
object :: object(),
Expand Down Expand Up @@ -131,6 +136,10 @@ defmodule CloudCache.Adapter do
adapter.copy_object(dest_bucket, dest_object, src_bucket, src_object, opts)
end

def list_objects(adapter, bucket, opts \\ []) do
adapter.list_objects(bucket, opts)
end

# Multipart Upload API

def pre_sign_part(adapter, bucket, object, upload_id, part_number, opts \\ []) do
Expand Down
30 changes: 30 additions & 0 deletions lib/cloud_cache/adapters/s3.ex
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,32 @@ defmodule CloudCache.Adapters.S3 do
end
end

@impl true
@doc """
...
"""
def list_objects(bucket, opts \\ []) do
opts = Keyword.merge(@default_options, opts)

sandbox? = opts[:s3][:sandbox_enabled] === true

if not sandbox? or sandbox_disabled?() do
case bucket |> S3.list_objects(opts) |> perform(opts) do
{:ok, _} = result ->
result

{:error, reason} ->
{:error,
ErrorMessage.service_unavailable("service temporarily unavailable", %{
bucket: bucket,
reason: reason
})}
end
else
sandbox_list_objects_response(bucket, opts)
end
end

@impl true
@doc """
...
Expand Down Expand Up @@ -953,6 +979,10 @@ defmodule CloudCache.Adapters.S3 do
to: CloudCache.Adapters.S3.Testing.S3Sandbox,
as: :put_object_response

defdelegate sandbox_list_objects_response(bucket, opts),
to: CloudCache.Adapters.S3.Testing.S3Sandbox,
as: :list_objects_response

defdelegate sandbox_copy_object_response(
dest_bucket,
dest_object,
Expand Down
39 changes: 39 additions & 0 deletions lib/cloud_cache/adapters/s3/sandbox.ex
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,41 @@ if Code.ensure_loaded?(SandboxRegistry) do
end
end

@doc """
Returns the registered response function for `list_objects/2` in the
context of the calling process.
"""
def list_objects_response(bucket, opts \\ []) do
doc_examples =
[
"fn -> ...",
"fn (object) -> ...",
"fn (object, options) -> ..."
]

func = find!(:list_objects, bucket, doc_examples)

case :erlang.fun_info(func)[:arity] do
0 ->
func.()

1 ->
func.(bucket)

2 ->
func.(bucket, opts)

_ ->
raise """
This function's signature is not supported: #{inspect(func)}

Please provide a function with one of the following arities (0-#{length(doc_examples) - 1}):

#{Enum.map_join(doc_examples, "\n", &(" " <> &1))}
"""
end
end

@doc """
Returns the registered response function for `copy_object/5` in the
context of the calling process.
Expand Down Expand Up @@ -660,6 +695,10 @@ if Code.ensure_loaded?(SandboxRegistry) do
set_responses(:put_object, tuples)
end

def set_list_objects_responses(tuples) do
set_responses(:list_objects, tuples)
end

def set_copy_object_responses(tuples) do
set_responses(:copy_object, tuples)
end
Expand Down
37 changes: 37 additions & 0 deletions test/cloud_cache/adapters/s3_sandbox_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,43 @@ defmodule CloudCache.Adapters.S3.Testing.S3SandboxTest do
end
end

describe "list_objects/2" do
test "returns list of objects on success" do
S3Sandbox.set_list_objects_responses([
{@bucket,
fn ->
{:ok,
%{
body: %{
contents: [
%{
key: "test-object",
last_modified: ~U[2025-08-30 01:00:00.000000Z],
etag: "etag"
}
]
},
headers: %{}
}}
end}
])

assert {:ok,
%{
body: %{
contents: [
%{
key: "test-object",
last_modified: ~U[2025-08-30 01:00:00.000000Z],
etag: "etag"
}
]
},
headers: %{}
}} = S3.list_objects(@bucket, @options)
end
end

describe "copy_object/3" do
test "returns object metadata on success" do
S3Sandbox.set_copy_object_responses([
Expand Down
19 changes: 19 additions & 0 deletions test/cloud_cache/adapters/s3_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,25 @@ defmodule CloudCache.Adapters.S3Test do
end
end

describe "list_objects/2" do
test "returns list of objects on success" do
src_object = "test_#{:erlang.unique_integer()}.txt"

assert {:ok, _} = LocalStack.put_object(@bucket, src_object, "content", [])

assert {:ok,
%{
body: %{
contents: contents
},
headers: headers
}} = S3.list_objects(@bucket, @options)

assert Enum.any?(contents, fn content -> content.key === src_object end)
assert headers
end
end

describe "pre_sign/3" do
test "returns a presigned URL and metadata on success" do
assert {:ok,
Expand Down