Spaces:
Sleeping
Sleeping
hiett
commited on
Commit
·
a8d7371
1
Parent(s):
3771745
Response encoding for single items and lists
Browse files- example/src/index.ts +19 -4
- lib/srh/http/base_router.ex +31 -9
- lib/srh/http/request_validator.ex +15 -0
- lib/srh/http/result_encoder.ex +66 -0
example/src/index.ts
CHANGED
@@ -1,13 +1,28 @@
|
|
1 |
import {Redis} from "@upstash/redis";
|
2 |
|
3 |
const redis = new Redis({
|
4 |
-
url:
|
5 |
token: "example_token",
|
6 |
-
responseEncoding:
|
7 |
});
|
8 |
|
9 |
(async () => {
|
10 |
-
await redis.set("key", "value");
|
11 |
-
const value = await redis.get("
|
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
|