hiett commited on
Commit
a8d7371
·
1 Parent(s): 3771745

Response encoding for single items and lists

Browse files
example/src/index.ts CHANGED
@@ -1,13 +1,28 @@
1
  import {Redis} from "@upstash/redis";
2
 
3
  const redis = new Redis({
4
- url: process.env.REDIS_CONNECTION_URL,
5
  token: "example_token",
6
- responseEncoding: false,
7
  });
8
 
9
  (async () => {
10
- await redis.set("key", "value");
11
- const value = await redis.get("key");
12
  console.log(value); // value
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  })();
 
1
  import {Redis} from "@upstash/redis";
2
 
3
  const redis = new Redis({
4
+ url: "http://127.0.0.1:8080",
5
  token: "example_token",
6
+ // responseEncoding: true,
7
  });
8
 
9
  (async () => {
10
+ // await redis.set("key", "value");
11
+ const value = await redis.get("foo");
12
  console.log(value); // value
13
+
14
+ // Run a pipeline operation
15
+ const pipelineResponse = await redis.pipeline()
16
+ .set("amazing-key", "bar")
17
+ .get("amazing-key")
18
+ .del("amazing-other-key")
19
+ .del("random-key-that-doesnt-exist")
20
+ .srandmember("random-key-that-doesnt-exist")
21
+ .sadd("amazing-set", "item1", "item2", "item3", "bar", "foo", "example")
22
+ .smembers("amazing-set")
23
+ // .evalsha("aijsojiasd", [], [])
24
+ .get("foo")
25
+ .exec();
26
+
27
+ console.log(pipelineResponse);
28
  })();
lib/srh/http/base_router.ex CHANGED
@@ -2,6 +2,7 @@ defmodule Srh.Http.BaseRouter do
2
  use Plug.Router
3
  alias Srh.Http.RequestValidator
4
  alias Srh.Http.CommandHandler
 
5
 
6
  plug(:match)
7
  plug(Plug.Parsers, parsers: [:json], pass: ["application/json"], json_decoder: Jason)
@@ -12,27 +13,30 @@ defmodule Srh.Http.BaseRouter do
12
  end
13
 
14
  post "/" do
15
- conn
16
- |> handle_extract_auth(&CommandHandler.handle_command(conn, &1))
17
- |> handle_response(conn)
18
  end
19
 
20
  post "/pipeline" do
21
- conn
22
- |> handle_extract_auth(&CommandHandler.handle_command_array(conn, &1))
23
- |> handle_response(conn)
24
  end
25
 
26
  post "/multi-exec" do
27
- conn
28
- |> handle_extract_auth(&CommandHandler.handle_command_transaction_array(conn, &1))
29
- |> handle_response(conn)
30
  end
31
 
32
  match _ do
33
  send_resp(conn, 404, "Endpoint not found")
34
  end
35
 
 
 
 
 
 
 
 
 
 
36
  defp handle_extract_auth(conn, success_lambda) do
37
  case conn
38
  |> get_req_header("authorization")
@@ -45,6 +49,24 @@ defmodule Srh.Http.BaseRouter do
45
  end
46
  end
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  defp handle_response(response, conn) do
49
  %{code: code, message: message, json: json} =
50
  case response do
 
2
  use Plug.Router
3
  alias Srh.Http.RequestValidator
4
  alias Srh.Http.CommandHandler
5
+ alias Srh.Http.ResultEncoder
6
 
7
  plug(:match)
8
  plug(Plug.Parsers, parsers: [:json], pass: ["application/json"], json_decoder: Jason)
 
13
  end
14
 
15
  post "/" do
16
+ do_command_request(conn, &CommandHandler.handle_command(&1, &2))
 
 
17
  end
18
 
19
  post "/pipeline" do
20
+ do_command_request(conn, &CommandHandler.handle_command_array(&1, &2))
 
 
21
  end
22
 
23
  post "/multi-exec" do
24
+ do_command_request(conn, &CommandHandler.handle_command_transaction_array(&1, &2))
 
 
25
  end
26
 
27
  match _ do
28
  send_resp(conn, 404, "Endpoint not found")
29
  end
30
 
31
+ defp do_command_request(conn, success_lambda) do
32
+ encoding_enabled = handle_extract_encoding(conn)
33
+
34
+ conn
35
+ |> handle_extract_auth(&success_lambda.(conn, &1))
36
+ |> handle_encoding_step(encoding_enabled)
37
+ |> handle_response(conn)
38
+ end
39
+
40
  defp handle_extract_auth(conn, success_lambda) do
41
  case conn
42
  |> get_req_header("authorization")
 
49
  end
50
  end
51
 
52
+ defp handle_extract_encoding(conn) do
53
+ case conn
54
+ |> get_req_header("upstash-encoding")
55
+ |> RequestValidator.validate_encoding_header() do
56
+ {:ok, _encoding_enabled} -> true
57
+ {:error, _} -> false # it's not required to be present
58
+ end
59
+ end
60
+
61
+ defp handle_encoding_step(response, encoding_enabled) do
62
+ case encoding_enabled do
63
+ true ->
64
+ # We need to use the encoder to
65
+ ResultEncoder.encode_response(response)
66
+ false -> response
67
+ end
68
+ end
69
+
70
  defp handle_response(response, conn) do
71
  %{code: code, message: message, json: json} =
72
  case response do
lib/srh/http/request_validator.ex CHANGED
@@ -26,6 +26,21 @@ defmodule Srh.Http.RequestValidator do
26
 
27
  defp do_validate_pipeline_item(_), do: :error
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  def validate_bearer_header(header_value_array) when is_list(header_value_array) do
30
  do_validate_bearer_header(header_value_array)
31
  end
 
26
 
27
  defp do_validate_pipeline_item(_), do: :error
28
 
29
+ def validate_encoding_header(header_value_array) when is_list(header_value_array) do
30
+ do_validate_encoding_header(header_value_array)
31
+ end
32
+
33
+ # This has been broken up like this to future-proof different encoding modes in the future
34
+ defp do_validate_encoding_header([first_item | rest]) do
35
+ case first_item do
36
+ "base64" -> {:ok, true}
37
+
38
+ _ -> do_validate_encoding_header(rest)
39
+ end
40
+ end
41
+
42
+ defp do_validate_encoding_header([]), do: {:error, :not_found}
43
+
44
  def validate_bearer_header(header_value_array) when is_list(header_value_array) do
45
  do_validate_bearer_header(header_value_array)
46
  end
lib/srh/http/result_encoder.ex ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defmodule Srh.Http.ResultEncoder do
2
+
3
+ # Errors don't get encoded, we need to skip over those
4
+ def encode_response({:redis_error, error_result_map}) do
5
+ {:redis_error, error_result_map}
6
+ end
7
+
8
+ # List-based responses, they will contain multiple entries
9
+ # It's important to note that this is DIFFERENT from a list of values,
10
+ # as it's a list of separate command responses. Each is a map that either
11
+ # Contains a result or an error
12
+ def encode_response({:ok, result_list}) when is_list(result_list) do
13
+ # Each one of these entries needs to be encoded
14
+ {:ok, encode_response_list(result_list, [])}
15
+ end
16
+
17
+ # Single item response
18
+ def encode_response({:ok, %{result: result_value}}) do
19
+ {:ok, %{result: encode_result_value(result_value)}}
20
+ end
21
+
22
+ ## RESULT LIST ENCODING ##
23
+
24
+ defp encode_response_list([current | rest], encoded_responses) do
25
+ encoded_current_entry = case current do
26
+ %{result: value} ->
27
+ %{result: encode_result_value(value)} # Encode the value
28
+ %{error: error_message} ->
29
+ %{error: error_message} # We don't encode errors
30
+ end
31
+
32
+ encode_response_list(rest, [encoded_current_entry | encoded_responses])
33
+ end
34
+
35
+ defp encode_response_list([], encoded_responses) do
36
+ Enum.reverse(encoded_responses)
37
+ end
38
+
39
+ ## RESULT VALUE ENCODING ##
40
+
41
+ # Numbers are ignored
42
+ defp encode_result_value(value) when is_number(value), do: value
43
+
44
+ # Null/nil is ignored
45
+ defp encode_result_value(value) when is_nil(value), do: value
46
+
47
+ # Strings / blobs (any binary data) is encoded to Base64
48
+ defp encode_result_value(value) when is_binary(value), do: Base.encode64(value)
49
+
50
+ defp encode_result_value(arr) when is_list(arr) do
51
+ encode_result_value_list(arr, [])
52
+ end
53
+
54
+ ## RESULT VALUE LIST ENCODING ##
55
+
56
+ # Arrays can have values that are encoded, or aren't, based on whats laid out above
57
+ defp encode_result_value_list([current | rest], encoded_responses) do
58
+ encoded_value = encode_result_value(current)
59
+ encode_result_value_list(rest, [encoded_value | encoded_responses])
60
+ end
61
+
62
+ defp encode_result_value_list([], encoded_responses) do
63
+ # There are no responses left, and since we add them backwards, we need to flip the list
64
+ Enum.reverse(encoded_responses)
65
+ end
66
+ end