Skip to content
This repository was archived by the owner on Jan 28, 2021. It is now read-only.

Commit f2d2f12

Browse files
committed
Initial commit
1 parent 512f008 commit f2d2f12

9 files changed

+432
-0
lines changed

.gitignore

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.eunit
2+
ebin
3+
deps
4+
*.o
5+
*.beam
6+
*.plt
7+
erl_crash.dump

include/etcd_types.hrl

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
-record(error, {
2+
errorCode :: integer(),
3+
message :: binary(),
4+
cause :: binary()
5+
}).
6+
-record(set, {
7+
key :: binary(),
8+
value :: binary(),
9+
prevValue :: binary(),
10+
newKey = false :: boolean(),
11+
expiration :: binary(),
12+
ttl :: integer(),
13+
index :: integer()
14+
}).
15+
-record(get, {
16+
key :: binary(),
17+
value :: binary(),
18+
dir = false :: boolean(),
19+
index :: integer()
20+
}).
21+
-record(delete, {
22+
key :: key(),
23+
prevValue :: binary(),
24+
index :: integer()
25+
}).
26+
27+
-type url() :: string().
28+
-type key() :: string() | binary().
29+
-type value() :: string() | binary().
30+
-type pos_timeout() :: pos_integer() | 'infinity'.
31+
-type result() :: {ok, response() | [response()]} | {http_error, atom()}.
32+
-type response() :: #error{} | #set{} | #get{} | #delete{}.

rebar

116 KB
Binary file not shown.

rebar.config

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{deps, [
2+
{lhttpc, ".*",
3+
{git, "https://github.com/oscarh/lhttpc.git", "master"}},
4+
{jiffy, ".*",
5+
{git, "https://github.com/davisp/jiffy.git", "master"}}
6+
]}.

src/etcd.app.src

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{application, etcd,
2+
[
3+
{description, ""},
4+
{vsn, "1"},
5+
{registered, []},
6+
{applications, [
7+
kernel,
8+
stdlib,
9+
lhttpc,
10+
jiffy
11+
]},
12+
{mod, { etcd_app, []}},
13+
{env, []}
14+
]}.

src/etcd.erl

+261
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
-module(etcd).
2+
3+
-export([start/0, stop/0]).
4+
-export([set/4, set/5]).
5+
-export([test_and_set/5, test_and_set/6]).
6+
-export([get/3]).
7+
-export([delete/3]).
8+
-export([watch/3, watch/4]).
9+
10+
-include("include/etcd_types.hrl").
11+
12+
%% @doc Start application with all depencies
13+
-spec start() -> ok | {error, term()}.
14+
start() ->
15+
case application:ensure_all_started(etcd) of
16+
{ok, _} ->
17+
ok;
18+
{error, Reason} ->
19+
{error, Reason}
20+
end.
21+
22+
%% @doc Stop application
23+
-spec stop() -> ok | {error, term()}.
24+
stop() ->
25+
application:stop(etcd).
26+
27+
%% @spec (Url, Key, Value, Timeout) -> Result
28+
%% Url = string()
29+
%% Key = binary() | string()
30+
%% Value = binary() | string()
31+
%% Timeout = pos_integer() | 'infinity'
32+
%% Result = {ok, response() | [response()]} | {http_error, atom()}.
33+
%% @end
34+
-spec set(url(), key(), value(), pos_timeout()) -> result().
35+
set(Url, Key, Value, Timeout) ->
36+
FullUrl = url_prefix(Url) ++ "/keys" ++ convert_to_string(Key),
37+
Result = post_request(FullUrl, [{"value", Value}], Timeout),
38+
handle_request_result(Result).
39+
40+
%% @spec (Url, Key, Value, TTL, Timeout) -> Result
41+
%% Url = string()
42+
%% Key = binary() | string()
43+
%% Value = binary() | string()
44+
%% TTL = pos_integer()
45+
%% Timeout = pos_integer() | infinity
46+
%% Result = {ok, response() | [response()]} | {http_error, atom()}.
47+
%% @end
48+
-spec set(url(), key(), value(), pos_integer(), pos_timeout()) -> result().
49+
set(Url, Key, Value, TTL, Timeout) ->
50+
FullUrl = url_prefix(Url) ++ "/keys" ++ convert_to_string(Key),
51+
Result = post_request(FullUrl, [{"value", Value}, {"ttl", TTL}], Timeout),
52+
handle_request_result(Result).
53+
54+
%% @spec (Url, Key, PrevValue, Value, Timeout) -> Result
55+
%% Url = string()
56+
%% Key = binary() | string()
57+
%% PrevValue = binary() | string()
58+
%% Value = binary() | string()
59+
%% Timeout = pos_integer() | 'infinity'
60+
%% Result = {ok, response() | [response()]} | {http_error, atom()}.
61+
%% @end
62+
-spec test_and_set(url(), key(), value(), value(), pos_timeout()) -> result().
63+
test_and_set(Url, Key, PrevValue, Value, Timeout) ->
64+
FullUrl = url_prefix(Url) ++ "/keys" ++ convert_to_string(Key),
65+
Result = post_request(FullUrl, [{"value", Value}, {"prevValue", PrevValue}], Timeout),
66+
handle_request_result(Result).
67+
68+
%% @spec (Url, Key, PrevValue, Value, TTL, Timeout) -> Result
69+
%% Url = string()
70+
%% Key = binary() | string()
71+
%% PrevValue = binary() | string()
72+
%% Value = binary() | string()
73+
%% TTL = pos_integer()
74+
%% Timeout = pos_integer() | infinity
75+
%% Result = {ok, response() | [response()]} | {http_error, atom()}.
76+
%% @end
77+
-spec test_and_set(url(), key(), value(), value(), pos_integer(), pos_timeout()) -> result().
78+
test_and_set(Url, Key, PrevValue, Value, TTL, Timeout) ->
79+
FullUrl = url_prefix(Url) ++ "/keys" ++ convert_to_string(Key),
80+
Result = post_request(FullUrl, [{"value", Value}, {"prevValue", PrevValue}, {"ttl", TTL}], Timeout),
81+
handle_request_result(Result).
82+
83+
%% @spec (Url, Key, Timeout) -> Result
84+
%% Url = string()
85+
%% Key = binary() | string()
86+
%% Timeout = pos_integer() | infinity
87+
%% Result = {ok, response() | [response()]} | {http_error, atom()}.
88+
%% @end
89+
-spec get(url(), key(), pos_timeout()) -> result().
90+
get(Url, Key, Timeout) ->
91+
FullUrl = url_prefix(Url) ++ "/keys" ++ convert_to_string(Key),
92+
Result = lhttpc:request(FullUrl, get, [], Timeout),
93+
handle_request_result(Result).
94+
95+
%% @spec (Url, Key, Timeout) -> Result
96+
%% Url = string()
97+
%% Key = binary() | string()
98+
%% Timeout = pos_integer() | infinity
99+
%% Result = {ok, response() | [response()]} | {http_error, atom()}.
100+
%% @end
101+
-spec delete(url(), key(), pos_timeout()) -> result().
102+
delete(Url, Key, Timeout) ->
103+
FullUrl = url_prefix(Url) ++ "/keys" ++ convert_to_string(Key),
104+
Result = lhttpc:request(FullUrl, delete, [], Timeout),
105+
handle_request_result(Result).
106+
107+
%% @spec (Url, Key, Timeout) -> Result
108+
%% Url = string()
109+
%% Key = binary() | string()
110+
%% Timeout = pos_integer() | infinity
111+
%% Result = {ok, response() | [response()]} | {http_error, atom()}.
112+
%% @end
113+
-spec watch(url(), key(), pos_timeout()) -> result().
114+
watch(Url, Key, Timeout) ->
115+
FullUrl = url_prefix(Url) ++ "/watch" ++ convert_to_string(Key),
116+
Result = lhttpc:request(FullUrl, get, [], Timeout),
117+
handle_request_result(Result).
118+
119+
%% @spec (Url, Key, Index, Timeout) -> Result
120+
%% Url = string()
121+
%% Key = binary() | string()
122+
%% Index = pos_integer()
123+
%% Timeout = pos_integer() | infinity
124+
%% Result = {ok, response() | [response()]} | {http_error, atom()}.
125+
%% @end
126+
-spec watch(url(), key(), pos_integer(), pos_timeout()) -> result().
127+
watch(Url, Key, Index, Timeout) ->
128+
FullUrl = url_prefix(Url) ++ "/watch" ++ convert_to_string(Key),
129+
Result = post_request(FullUrl, [{"index", Index}], Timeout),
130+
handle_request_result(Result).
131+
132+
%% @private
133+
convert_to_string(Value) when is_integer(Value) ->
134+
integer_to_list(Value);
135+
convert_to_string(Value) when is_binary(Value) ->
136+
binary_to_list(Value);
137+
convert_to_string(Value) when is_list(Value) ->
138+
Value.
139+
140+
%% @private
141+
encode_params(Pairs) ->
142+
List = [ http_uri:encode(convert_to_string(Key)) ++ "=" ++ http_uri:encode(convert_to_string(Value)) || {Key, Value} <- Pairs ],
143+
binary:list_to_bin(string:join(List, "&")).
144+
145+
%% @private
146+
url_prefix(Url) ->
147+
Url ++ "/v1".
148+
149+
%% @private
150+
post_request(Url, Pairs, Timeout) ->
151+
Body = encode_params(Pairs),
152+
Headers = [{"Content-Type", "application/x-www-form-urlencoded"}],
153+
lhttpc:request(Url, post, Headers, Body, Timeout).
154+
155+
%% @private
156+
parse_response(Decoded) when is_list(Decoded) ->
157+
[ parse_response_inner(Pairs) || {Pairs} <- Decoded ];
158+
parse_response(Decoded) when is_tuple(Decoded) ->
159+
{Pairs} = Decoded,
160+
IsError = lists:keyfind(<<"errorCode">>, 1, Pairs),
161+
case IsError of
162+
{_, ErrorCode} ->
163+
parse_error_response(Pairs, #error{ errorCode = ErrorCode });
164+
false ->
165+
parse_response_inner(Pairs)
166+
end.
167+
168+
%% @private
169+
parse_response_inner(Pairs) ->
170+
{_, Action} = lists:keyfind(<<"action">>, 1, Pairs),
171+
case Action of
172+
<<"SET">> ->
173+
parse_set_response(Pairs, #set{});
174+
<<"GET">> ->
175+
parse_get_response(Pairs, #get{});
176+
<<"DELETE">> ->
177+
parse_delete_response(Pairs, #delete{})
178+
end.
179+
180+
%% @private
181+
parse_error_response([], Acc) ->
182+
Acc;
183+
parse_error_response([Pair | Tail], Acc) ->
184+
{_, ErrorCode, Message, Cause} = Acc,
185+
case Pair of
186+
{<<"message">>, Message1} ->
187+
parse_error_response(Tail, {error, ErrorCode, Message1, Cause});
188+
{<<"cause">>, Cause1} ->
189+
parse_error_response(Tail, {error, ErrorCode, Message, Cause1});
190+
_ ->
191+
parse_error_response(Tail, Acc)
192+
end.
193+
194+
%% @private
195+
parse_set_response([], Acc) ->
196+
Acc;
197+
parse_set_response([Pair | Tail], Acc) ->
198+
{_, Key, Value, PrevValue, NewKey, Expiration, TTL, Index} = Acc,
199+
case Pair of
200+
{<<"key">>, Key1} ->
201+
parse_set_response(Tail, {set, Key1, Value, PrevValue, NewKey, Expiration, TTL, Index});
202+
{<<"value">>, Value1} ->
203+
parse_set_response(Tail, {set, Key, Value1, PrevValue, NewKey, Expiration, TTL, Index});
204+
{<<"prevValue">>, PrevValue1} ->
205+
parse_set_response(Tail, {set, Key, Value, PrevValue1, NewKey, Expiration, TTL, Index});
206+
{<<"newKey">>, NewKey1} ->
207+
parse_set_response(Tail, {set, Key, Value, PrevValue, NewKey1, Expiration, TTL, Index});
208+
{<<"expiration">>, Expiration1} ->
209+
parse_set_response(Tail, {set, Key, Value, PrevValue, NewKey, Expiration1, TTL, Index});
210+
{<<"ttl">>, TTL1} ->
211+
parse_set_response(Tail, {set, Key, Value, PrevValue, NewKey, Expiration, TTL1, Index});
212+
{<<"index">>, Index1} ->
213+
parse_set_response(Tail, {set, Key, Value, PrevValue, NewKey, Expiration, TTL, Index1});
214+
_ ->
215+
parse_set_response(Tail, Acc)
216+
end.
217+
218+
%% @private
219+
parse_get_response([], Acc) ->
220+
Acc;
221+
parse_get_response([Pair | Tail], Acc) ->
222+
{_, Key, Value, Dir, Index} = Acc,
223+
case Pair of
224+
{<<"key">>, Key1} ->
225+
parse_get_response(Tail, {get, Key1, Value, Dir, Index});
226+
{<<"value">>, Value1} ->
227+
parse_get_response(Tail, {get, Key, Value1, Dir, Index});
228+
{<<"dir">>, Dir1} ->
229+
parse_get_response(Tail, {get, Key, Value, Dir1, Index});
230+
{<<"index">>, Index1} ->
231+
parse_get_response(Tail, {get, Key, Value, Dir, Index1});
232+
_ ->
233+
parse_get_response(Tail, Acc)
234+
end.
235+
236+
%% @private
237+
parse_delete_response([], Acc) ->
238+
Acc;
239+
parse_delete_response([Pair | Tail], Acc) ->
240+
{_, Key, PrevValue, Index} = Acc,
241+
case Pair of
242+
{<<"key">>, Key1} ->
243+
parse_delete_response(Tail, {delete, Key1, PrevValue, Index});
244+
{<<"prevValue">>, PrevValue1} ->
245+
parse_delete_response(Tail, {delete, Key, PrevValue1, Index});
246+
{<<"index">>, Index1} ->
247+
parse_delete_response(Tail, {delete, Key, PrevValue, Index1});
248+
_ ->
249+
parse_delete_response(Tail, Acc)
250+
end.
251+
252+
%% @private
253+
handle_request_result(Result) ->
254+
case Result of
255+
{ok, {{_StatusCode, _ReasonPhrase}, _Hdrs, ResponseBody}} ->
256+
Decoded = jiffy:decode(ResponseBody),
257+
{ok, parse_response(Decoded)};
258+
{error, Reason} ->
259+
{http_error, Reason}
260+
end.
261+

src/etcd_app.erl

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-module(etcd_app).
2+
3+
-behaviour(application).
4+
5+
%% Application callbacks
6+
-export([start/2, stop/1]).
7+
8+
%% ===================================================================
9+
%% Application callbacks
10+
%% ===================================================================
11+
12+
start(_StartType, _StartArgs) ->
13+
etcd_sup:start_link().
14+
15+
stop(_State) ->
16+
ok.

src/etcd_sup.erl

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
-module(etcd_sup).
3+
4+
-behaviour(supervisor).
5+
6+
%% API
7+
-export([start_link/0]).
8+
9+
%% Supervisor callbacks
10+
-export([init/1]).
11+
12+
%% Helper macro for declaring children of supervisor
13+
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
14+
15+
%% ===================================================================
16+
%% API functions
17+
%% ===================================================================
18+
19+
start_link() ->
20+
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
21+
22+
%% ===================================================================
23+
%% Supervisor callbacks
24+
%% ===================================================================
25+
26+
init([]) ->
27+
{ok, { {one_for_one, 5, 10}, []} }.
28+

0 commit comments

Comments
 (0)